读取不是最近在 GCC 中编写的成员是未定义的行为吗?
Is reading a member that wasn't the most recently written in GCC undefined behavior?
c++参考资料对联合有如下解释,这个问题有趣的部分用粗体表示:
联合的大小仅与容纳其最大数据成员所需的大小相同。其他数据成员分配的字节数与最大成员的一部分相同。分配的细节是由实现定义的,从最近没有写入的联合成员中读取是未定义的行为。作为非标准语言扩展,许多编译器实现了读取联合的非活动成员的能力。
现在,如果我在Linux Mint 18上用g++ -std=c++11
编译以下代码,我得到以下输出(由printf
语句旁边的注释给出):
#include <cstdio>
using namespace std;
union myUnion {
int var1; // 32 bits
long int var2; // 64 bits
char var3; // 8 bits
}; // union size is 64 bits (size of largest member)
int main()
{
myUnion a;
a.var1 = 10;
printf("a is %ld bits and has value %dn",sizeof(a)*8,a.var1); // ...has value 10
a.var2 = 123456789;
printf("a is %ld bits and has value %ldn",sizeof(a)*8,a.var2); // ...has value 123456789
a.var3 = 'y';
printf("a is %ld bits and has value %cn",sizeof(a)*8,a.var3); // ...has value y
printf("a is %ld bits and has value %ldn",sizeof(a)*8,a.var2); //... has value 123456789, why???
return 0;
}
在return 0
之前的行上,读取a.var2
给出的不是'y'
字符的ASCII小数(这是我所期望的,我对联合不熟悉),而是它最初定义的值。基于上面引用的cppreference.com,我是否应该理解这是未定义的行为,因为它不是标准的,而是GCC的特定实现?
编辑
正如下面伟大的答案所指出的那样,我在printf
语句之后的评论中犯了一个复制错误,就在return 0
语句之前。正确的版本是:
printf("a is %ld bits and has value %ldn",sizeof(a)*8,a.var2); //... has value 123456889, why???
。7变成了8,因为前8位被'y'
字符的ASCII值覆盖,即121
(二进制的0111 1001
)。我将保留上面代码中的内容,以便与由此产生的重要讨论保持一致。
关于未定义行为的有趣之处在于,它与"随机"行为非常不同。当处理未定义的行为时,编译器会有自己决定使用的行为,并且每次都倾向于显示相同的行为。
例如:IDEOne对这段代码有自己的解释:http://ideone.com/HO5id6a is 32 bits and has value 10
a is 32 bits and has value 123456789
a is 32 bits and has value y
a is 32 bits and has value 123456889
你可能会注意到一些有趣的事情发生在那里(撇开事实,对于IDEOne的编译器,long int
是32位而不是64位)。它仍然显示第4行读取与第2行读取类似,但是值实际上略有变化。似乎发生的情况是,'y'
的char
值在联合中被设置,但它没有改变任何其他位。当我把它切换到long long int
而不是long int
时,我得到了类似的行为。
您可能想要检查,在您的示例中,第4行与之前的是否完全相同。我有点怀疑这是不是真的。
无论如何,为了回答您的特定问题,TL;DR是在GCC中,写入联合只更改与您要写入的特定成员相关的位,并且不能保证更改/清除所有其他位。当然,与任何与ub相关的东西一样,不要假设任何其他编译器(甚至是同一编译器的后续版本!)的行为相同。
您打印的只是同一内存区域的一部分:
myUnion a;
a.var2 = -1;
printf("a is %ld bits and has value %ld = 0x%lxn",
sizeof(a)*8, a.var2, a.var2);
a.var3 = 'y';
printf("a is %ld bits and has value %c = 0x%xn",
sizeof(a)*8, a.var3, a.var3);
printf("a is %ld bits and has value %ld = 0x%lxn",
sizeof(a)*8, a.var2, a.var2);
示例输出
a is 64 bits and has value -1 = 0xffffffffffffffff
a is 64 bits and has value y = 0x79
a is 64 bits and has value -135 = 0xffffffffffffff79
为了清晰起见,我已经将您的123456789
替换为最大值。这同样适用于你的号码:
a is 64 bits and has value 123456789 = 0x75bcd15
a is 64 bits and has value y = 0x79
a is 64 bits and has value 123456889 = 0x75bcd79
同样,原始值的第一个字节(特别是0x15
)被0x79
(y
字符)替换,因此原始数字被修改。
显然,a.var2
被强制转换为整个内存区域的long int
, a.var3
-被强制转换为char
,即仅仅是联合内存的第一个字节。
long int (64) = (long int) u
****************************************************************
int (32) = a.var2 = (int) u
********************************
char (1) = a.var1 = (char) u
*
Byte no.: 0 ........................................................... 63
^
('y' = 0x79) (0xcd) (0x5b) (0x07)
文档中的行实际上意味着对联合成员的最后赋值指定了联合的值,其余的内存被认为是垃圾。尽管如此,我们通常可以观察到为联合分配的内存中的剩余部分。
你确定你写的是什么?
在ubuntu 64位的GCC 5.4.0中,我得到:
a is 64 bits and has value 10
a is 64 bits and has value 123456789
a is 64 bits and has value y
a is 64 bits and has value 123456889
var2是64位大小,通过改变var3的值,你修改了var2的最后一个字节。当您使用%x:
打印时,会更清楚。a is 64 bits and var1 has value a
a is 64 bits and var2 has value 75bcd15
a is 64 bits and var3 has value 79
a is 64 bits and var2 has value 75bcd79
var1, var2和var3具有相同的内存方向,并且对于大多数计算机(Intel/Amd)来说,您的体系结构是Little Endian,修改var3会改变var2和var1的较低重要字节,因为它们共享相同的内存地址。
值得注意的是,C11标准§6.5.2.3,注释95(第83页)说:
如果用于读取联合对象内容的成员与最后用于在对象中存储值的成员不同,则该值的对象表示的适当部分将被重新解释为6.2.6中描述的新类型的对象表示(此过程有时称为"类型双关语")。这可能是一个陷阱。
这就是我所看到的,即使编译为c++ 11(与Apple LLVM版本8.0.0 (clang-800.0.38)):
a is 64 bits and has value 10
a is 64 bits and has value 123456789
a is 64 bits and has value y
a is 64 bits and has value 123456889
注意最后一个值不是 123456789,而是123456889,因为最低有效位字节被
覆盖了a.var3 = 'y';
将0x15
替换为0x79
(== 'y'
)
- 将成员函数保留为未定义
- 使用多个源文件时对类成员函数的未定义引用
- 对专用模板成员的未定义引用
- 访问从联合与另一个成员集复制的联合中的一个成员是否未定义或未指定?
- 默认移动成员定义为已删除,而未定义特殊成员?
- 对类的静态成员的未定义引用
- 我正在尝试在我的类中创建一个静态成员,但编译器警告我它是未定义的
- C++:私有类指针成员返回未定义的值
- 未定义的行为错误:对成员变量的更改仅在某些上下文中可见
- 标头中的成员变量未定义
- 大小未定义为类成员的向量
- 如何解决对自己的C++成员函数的未定义引用?
- 对成员函数和变量的未定义引用
- C++对已继承的受保护类成员的未定义引用
- 从其后声明的另一个成员数据初始化成员数据是否为未定义行为
- 成员初始值设定项列表中的递增是否会生成未定义的行为?
- 对静态 constexpr 成员的未定义引用仅由值使用
- 静态 constexpr 模板成员在专用时提供未定义的引用
- 使用指向未定义成员函数的指针时的未定义引用
- 具有未定义成员函数返回类型的模板实例化