Rak*_*esh 3 c c++ bit-manipulation unions
我试图访问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]);
Run Code Online (Sandbox Code Playgroud)
输出是
uint64_t: 000000000000000a
uint32_t: 0000000a 00000000
Addresses:
uint64_t: 00007ffe09d594e0
uint32_t: 0x7ffe09d594e0 0x7ffe09d594e4
Run Code Online (Sandbox Code Playgroud)
指向的起始地址y与起始地址相同x.由于两个字段使用相同的位置,因此值不应该x是00000000 0000000a?
为什么不发生这种情况?内部转换如何在具有不同数据类型的不同字段的联盟中发生?
需要做什么来使用联合以与uint64_t中相同的顺序检索精确的原始位作为uint32_t?
编辑:如评论中所述,C++提供了未定义的行为.它在C中如何工作?我们真的可以这样做吗?
我将首先解释您的实现中会发生什么.
您正在值和2个值的数组之间进行类型惩罚.根据结果,您的系统是小端,并且很乐意通过简单地重新解释字节表示来接受类型惩罚.而作为一个小端的字节表示是:uint64_tuint32_t0x0auint64_t
Byte number 0 1 2 3 4 5 6 7
Value 0x0a 0x00 0x00 0x00 0x00 0x00 0x00 0x00
Run Code Online (Sandbox Code Playgroud)
little endian中的最低有效字节具有最低地址.现在很明显为什么uint32_t[2]代表是{ 0x0a, 0x00 }.
但是你所做的只是在C语言中是合法的.
C11表示为6.5.2.3结构和工会成员:
3后缀表达式后跟.运算符和标识符指定结构或联合对象的成员.该值是指定成员的值,95)如果第一个表达式是左值,则它是左值.
该95)注明确地说:
如果用于读取联合对象的内容的部件是不一样的最后用于存储在该对象的值的部件,该值的对象表示的适当部分被重新解释为在新的类型的对象表示为在6.2.6中描述(一个过程有时被称为''punning'').这可能是陷阱表示.
因此,即使注释不是规范性的,它们的目的是明确标准应该被解释的方式=>代码是有效的并且在小端系统定义uint64_t和uint32_t类型上定义了行为.
C++在那部分更严格.草案n4659 for C++ 17在[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明确地说:
此列表的目的是指定对象可能或可能没有别名的情况.
由于在C++标准中从未引用过双关语,并且结构/联合部分不包含C 的重新解释的等价物,这意味着在C++中读取不是上次写入的成员的值会调用undefined行为.
当然,常见的编译器实现编译C和C++,并且即使在C++源代码中,大多数也接受C语言,因为gcc C++编译器很乐意接受C++源文件中的VLA.毕竟,未定义的行为包括预期的结果......但是您不应该依赖于可移植代码.
| 归档时间: |
|
| 查看次数: |
151 次 |
| 最近记录: |