将联合字段中的位解释为C / C ++中的不同数据类型

时间:2018-07-02 07:19:43

标签: c++ c bit-manipulation unions

我正在尝试将联合位作为不同的数据类型进行访问。例如:

    typedef union {
    uint64_t x;
    uint32_t y[2];
    }test;

    test testdata;
    testdata.x = 0xa;
    printf("uint64_t: %016lx\nuint32_t: %08x %08x\n",testdata.x,testdata.y[0],testdata.y[1]);
    printf("Addresses:\nuint64_t: %016lx\nuint32_t: %p %p\n",&testdata.x,&testdata.y[0],&testdata.y[1]);

输出为

uint64_t: 000000000000000a
uint32_t: 0000000a 00000000
Addresses:
uint64_t: 00007ffe09d594e0
uint32_t: 0x7ffe09d594e0 0x7ffe09d594e4

y指向的起始地址与x的起始地址相同。由于两个字段使用相同的位置,因此x的值不应该为00000000 0000000a吗?

为什么这没有发生?在具有不同数据类型的不同字段的联盟中,如何进行内部转换?

需要执行什么操作才能使用联合以与uint64_t中相同的顺序检索uint32_t中的原始位?

编辑: 如注释中所述,C ++提供了未定义的行为。 它在C中如何工作?我们真的可以做到吗?

1 个答案:

答案 0 :(得分:6)

我将首先解释您的实施过程中发生的情况。

您正在uint64_t值和2个uint32_t值的数组之间进行 type punning 。根据结果​​,您的系统是低位字节序的,并通过简单地重新解释字节表示形式来高兴地接受这种类型的修剪。并且0x0a的字节表示为小端uint64_t

Byte number  0    1    2    3    4    5    6    7  
Value        0x0a 0x00 0x00 0x00 0x00 0x00 0x00 0x00

little endian中的最低有效字节具有最低地址。现在很明显为什么uint32_t[2]表示为{ 0x0a, 0x00 }

但是您所做的只是合法的C语言。

C语言:

C11表示为6.5.2.3结构和联合成员:

  

3后缀表达式,后跟.。运算符和标识符指定成员   结构或联合对象。该值是命名成员的值, 95),如果是,则为左值   第一个表达式是左值。

95)注释明确指出:

  

如果用于读取联合对象的内容的成员与上次使用该成员的成员不同   将值存储在对象中,值的对象表示的适当部分将重新解释   作为新类型的对象表示形式(如6.2.6中所述(有时称为“类型   punning’)。这可能是陷阱的表示形式。

因此,即使注释不是规范性的,它们的目的也是弄清楚标准的解释方式=>您的代码有效,并且在定义uint64_t和{{1}的小端系统上具有定义的行为}类型。

C ++语言:

C ++在这方面更加严格。用于C ++ 17的n4659草案在[basic.lval]中说:

  

8如果程序尝试通过除以下任意一个以外的glvalue来访问对象的存储值   以下类型的行为未定义: 56
  (8.1)—对象的动态类型,
  (8.2)—对象的动态类型的cv限定版本,
  (8.3)—与对象的动态类型相似的类型(定义见7.5),
  (8.4)—类型,它是与对象的动态类型相对应的有符号或无符号类型,
  (8.5)—一种类型,是与动态类型的CV限定版本相对应的有符号或无符号类型   对象的
  (8.6)—集合或联合类型,在其元素中包括上述类型之一或非静态   数据成员(包括递归地包括子聚合的元素或非静态数据成员或   包含工会),
  (8.7)—一种类型,它是对象的动态类型的(可能是cv限定的)基类类型,
  (8.8)— char,unsigned char或std :: byte类型。

注释 56 明确说:

  

此列表的目的是指定对象可能会别名也可能不会别名的那些情况。

因为 punning 从未在C ++标准中引用,并且struct / union部分不包含C的 re-interpretation 的等效项,这意味着读入C ++的成员值(不是最后一个写入的成员)会调用未定义的行为。


当然,常见的编译器实现可以同时编译C和C ++,并且它们中的大多数甚至在C ++源代码中也接受C习惯用法,这与gcc C ++编译器欣然接受C ++源文件中的VLA的原因相同。毕竟,未定义的行为包括预期的结果...但是您不应依赖于此作为可移植代码。