如何访问一个 16 位变量作为两个 8 位变量?和两个 8 位变量作为一个 16 位变量

How to access a 16 bits variable as two 8 bits variables ? And two 8 bits variables as one 16 bit variable

我正在使用 C++17。

假设我有两个变量 ab。这些变量的类型为 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

你能解释一下我在这里做错了什么吗?有任何危险信号或更好的方法吗?

谢谢!

在你的情况下,使用 union 和 struct 更好更容易理解。这是一个例子:

#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

这是因为字节顺序wiki

如您所见,这段代码决定了内存中的字节顺序

uint16_t x = 0x0001; 
std::cout << (*((uint8_t*)&x) ? "little" : "big") << "-endian\n";

尝试交换号码

#include <memory>
#include <iostream>


int main()
{
    uint16_t x = 0x0001;
    std::cout << (*((uint8_t*)&x) ? "little" : "big") << "-endian\n";

    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

还有一些其他方法可以在两个 8 位值和一个 16 位值之间进行转换。

但请注意,直接寻址 16 位值中单个字节的每个解决方案的结果取决于执行它的机器的字节顺序。例如,英特尔使用 'little endian',其中首先存储最低有效位。其他机器可能使用 'big endian' 并首先存储最高有效位。

使用bitshift and 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

使用 bitshift 和 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;

在 Intel 机器上,这将输出 255 (0xff),因为 Intel 使用“little endian”,其中首先存储最低有效位。所以a是ab的低字节,b是高字节。

如果将并集重新定义为

union conv
{
    struct {
        uint8_t b, a;
    };
    uint16_t ab;
};

上面的示例在 Intel 机器上会输出 65280 (0xff00),因为现在 b 代表 ab 的最低有效 8 位,而 a 代表最高有效位。

结合联合和位域,您还可以访问 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;
};

一种没有未定义行为的可移植方式,将 2 uint8_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;
}

请注意,所有依赖于将 uint16_t 转换为 uint8_t* 的方法只是碰巧起作用,因为 uint8_tunsigned charchar 的类型别名类型非常特殊,因为它们可以作为任何其他类型的别名。此方法打破了严格的别名,并在转换为大于 uint8_t 的任何其他类型时导致未定义的行为,例如当您将 uint64_t 转换为 uint32_t*uint16_t*.

有关详细信息,请参阅 What is the Strict Aliasing Rule and Why do we care?