我正在使用C++17。
假设我有两个变量a
和b
。这些变量的类型为uint8_t
。我希望能够uint8_t访问它们,但也uint16_t。
例如:
#include <memory>
int main()
{
uint8_t a = 0xFF;
uint8_t b = 0x00;
uint16_t ab; // Should be 0xFF00
}
我认为使用数组将是一个很好的解决方案,因为两个变量在内存中应该彼此相邻。所以我这样做了:
#include <memory>
int main()
{
uint8_t data[] = {0xFF, 0x00};
uint8_t * a = data;
uint8_t * b = data + sizeof(uint8_t);
uint16_t * ab = reinterpret_cast<uint16_t*>(data);
std::cout << std::hex << (int) *a << "n";
std::cout << std::hex << (int) *b << "n";
std::cout << std::hex << (int) *ab << "n";
}
输出:
ff
0
ff
但我希望:
ff
0
ff00
你能解释一下我在这里做错了什么,有什么危险信号或更好的方法吗?
谢谢!
还有其他几种方法可以在两个 8 位和一个 16 位值之间进行转换。
但请注意,直接寻址 16 位值中单个字节的每个解决方案的结果取决于执行它的机器的字节顺序。例如,英特尔使用"小字节序",其中最低有效位首先存储。其他机器可能使用"大端序"并首先存储最重要的位。
使用位移和 OR 计算 16 位值
const uint8_t a = 0xff;
const uint8_t b = 0x00;
const uint16_t ab = a | (b << 8); // works because b is promoted to int before being shifted
使用位移和and
计算 8 位值
const uint16_t ab = 0xff;
const uint8_t a = ab & 0xff;
const uint8_t b = ab >> 8;
直接寻址单词的字节
uint16_t ab;
auto& a = reinterpret_cast<uint8_t*>(&ab)[0];
auto& b = reinterpret_cast<uint8_t*>(&ab)[1];
使用联合
这是标准明确不允许的(但也在任何地方都这样做)
声明以下联合:
union conv
{
struct {
uint8_t a, b;
};
uint16_t ab;
};
现在,您可以使用它将两个 8 位值组合成一个 16 位值:
conv c;
c.a = 0xFF;
c.b = 0x00;
std::cout << c.ab << std::endl;
在英特尔机器上,这将输出 255 (0xff),因为英特尔使用"小字节序",其中最低有效位首先存储。所以 a 是 ab 的低字节,b 是高字节。
如果将联合重新定义为
union conv
{
struct {
uint8_t b, a;
};
uint16_t ab;
};
上面的示例将在英特尔机器上输出 65280 (0xff00),因为现在 b 表示 ab 的最低有效值 8 位,a 表示最重要的 8 位。
结合联合和位域,您还可以访问 16 位值的每个位:
union bitconv
{
struct {
uint16_t
b0 : 1, b1 : 1, b2 : 1, b3 : 1, b4 : 1, b5 : 1, b6 : 1, b7 : 1,
b8 : 1, b9 : 1, b10 : 1, b11 : 1, b12 : 1, b13 : 1, b14 : 1, b15 : 1;
};
uint16_t word;
};
一种没有未定义行为的便携式方式,可以将 2uint8_t
打包到uint16_t
中并返回:
int main() {
uint8_t a = 0xFF;
uint8_t b = 0x00;
// from a and b to ab
uint16_t ab = a * 0x100 + b;
// from ab to a and b
a = ab / 0x100 & 0xff;
b = ab & 0xff;
}
请注意,所有依赖于强制转换uint8_t*
uint16_t
的方法都只能工作,因为uint8_t
是unsigned char
的类型别名,char
类型相当特殊,因为它们可以别名任何其他类型。此方法打破了严格的混叠,并在转换为大于uint8_t
的任何其他类型时导致未定义的行为,例如,当您将uint64_t
转换为uint32_t*
或uint16_t*
时。
有关更多详细信息,请参阅什么是严格别名规则和我们为什么关心?
这是因为字节顺序维基。
如您所见,此代码确定内存中的字节顺序
uint16_t x = 0x0001;
std::cout << (*((uint8_t*)&x) ? "little" : "big") << "-endiann";
尝试交换号码
#include <memory>
#include <iostream>
int main()
{
uint16_t x = 0x0001;
std::cout << (*((uint8_t*)&x) ? "little" : "big") << "-endiann";
uint8_t data[] = { 0x00, 0xFF };
uint8_t* a = data;
uint8_t* b = data + sizeof(uint8_t);
uint16_t* ab = reinterpret_cast<uint16_t*>(data);
std::cout << std::hex << (int)*a << "n";
std::cout << std::hex << (int)*b << "n";
std::cout << std::hex << (int)*ab << "n";
}
结果
little-endian
0
ff
ff00
在您的情况下,使用联合和结构更好,更容易理解。下面是一个示例:
#include <iostream>
struct mystruct {
uint8_t a;
uint8_t b;
} ;
union my_unino{
mystruct x;
uint16_t ab ;
}data;
int main(){
data.x.a=0x1;
data.x.b=0xff;
std::cout<<std::hex<<(int)data.x.a<<std::endl;
std::cout<<std::hex<<data.ab<<std::endl;
return 0;
}
结果:
1
ff01