我正在尝试以不同的数据类型访问 union 位。例如:
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
吗?
为什么这没有发生?在具有不同数据类型的不同字段的 Union 中如何进行内部转换?
需要做什么才能使用 union 以与 uint64_t 中相同的顺序检索 uint32_t 中的确切原始位?
编辑: 如评论中所述,C++ 给出了未定义的行为。 它在 C 中是如何工作的?我们真的可以做到吗?
最佳答案
我将首先解释您的实现过程中发生了什么。
您正在 uint64_t
值和 2 个 uint32_t
值的数组之间进行类型双关。根据结果,您的系统是小字节序的,并且很乐意通过简单地重新解释字节表示来接受这种类型的双关语。 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 结构和 union 成员所述:
3 A postfix expression followed by the . operator and an identifier designates a member of a structure or union object. The value is that of the named member,95) and is an lvalue if the first expression is an lvalue.
95) 注释明确指出:
If the member used to read the contents of a union object is not the same as the member last used to store a value in the object, the appropriate part of the object representation of the value is reinterpreted as an object representation in the new type as described in 6.2.6 (a process sometimes called ‘‘type punning’’). This might be a trap representation.
因此,即使注释不是规范的,它们的目的是明确解释标准的方式 => 你的代码是有效的,并且在定义 uint64_t
和 uint32_t
类型。
C++ 语言:
C++ 在这方面更为严格。 C++17 的 n4659 草案在 [basic.lval] 中说:
8 If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined:56
(8.1) — the dynamic type of the object,
(8.2) — a cv-qualified version of the dynamic type of the object,
(8.3) — a type similar (as defined in 7.5) to the dynamic type of the object,
(8.4) — a type that is the signed or unsigned type corresponding to the dynamic type of the object,
(8.5) — a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,
(8.6) — an aggregate or union type that includes one of the aforementioned types among its elements or nonstatic data members (including, recursively, an element or non-static data member of a subaggregate or contained union),
(8.7) — a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,
(8.8) — a char, unsigned char, or std::byte type.
注释 56 明确表示:
The intent of this list is to specify those circumstances in which an object may or may not be aliased.
由于 双关 在 C++ 标准中从未被引用,并且由于结构/union 部分不包含 C 的重新解释 的等价物,这意味着读入C++ 不是最后写入的成员的值调用未定义的行为。
当然,常见的编译器实现同时编译 C 和 C++,并且大多数甚至在 C++ 源代码中也接受 C 惯用语,原因与 gcc C++ 编译器乐于在 C++ 源文件中接受 VLA 的原因相同。毕竟,未定义的行为 包含预期的结果...但是您不应该依赖它来获取可移植代码。
关于c++ - 将 union 字段中的位解释为 C/C++ 中的不同数据类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51130932/