C++ reference对于工会有以下explanation,这个问题的有趣部分用粗体显示:
联盟只有拥有其最大数据成员所需的大小。其他数据成员以与该最大成员的一部分相同的字节分配。该分配的细节是实现定义的,并且它的未定义行为是从最近编写的联合成员中读取的。许多编译器作为非标准语言扩展实现了读取联合的非活动成员的能力。
现在,如果我使用g++ -std=c++11
以下代码在Linux Mint 18上编译,我得到以下输出(由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 %d\n",sizeof(a)*8,a.var1); // ...has value 10
a.var2 = 123456789;
printf("a is %ld bits and has value %ld\n",sizeof(a)*8,a.var2); // ...has value 123456789
a.var3 = 'y';
printf("a is %ld bits and has value %c\n",sizeof(a)*8,a.var3); // ...has value y
printf("a is %ld bits and has value %ld\n",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 %ld\n",sizeof(a)*8,a.var2); //... has value 123456889, why???
即。 7变为8,因为前8位被'y'
字符的ASCII值覆盖,即121
(二进制为0111 1001
)。我会在上面的代码中保留它,以便与由此产生的精彩讨论保持一致。
答案 0 :(得分:4)
关于未定义行为的有趣之处在于它与&#34;随机&#34;非常明显不同。行为。编译器将会在处理未定义的行为时决定使用它们,并且每次都会表现出相同的行为。
例证:IDEOne对此代码有自己的解释:http://ideone.com/HO5id6
a 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行,但该值实际上略有改变。似乎已经发生的事情是char
的{{1}}值已在联合中设置,但它并没有改变任何其他位。当我将其切换为'y'
而不是long long int
时,我遇到了类似的行为。
在您的示例中,您可能需要检查第4行与完全之前是否相同。我有点怀疑,实际情况就是这样。
无论如何,要回答你的具体问题,TL; DR就是在GCC中,写一个联盟只会改变与你写的特定成员相关的位,而且它不是保证改变/清除所有其他位。当然,就像UB相关的任何事情一样,不要假设任何其他编译器(甚至是同一编译器的更高版本!)的行为都是一样的。
答案 1 :(得分:3)
您只打印同一记忆区域的一部分:
AppDomain.CurrentDomain.FirstChanceException += new EventHandler<System.Runtime.ExceptionServices.FirstChanceExceptionEventArgs>(CurrentDomain_FirstChanceException);
private void CurrentDomain_FirstChanceException(object sender, FirstChanceExceptionEventArgs e)
{
Dispatcher.BeginInvoke(new Action(() => MessageBox.Show("Error Occurred \n\r" + e.Exception.Message + "\n\r" + e.Exception.StackTrace, "ERROR", MessageBoxButton.OK, MessageBoxImage.Error)));
}
示例输出
myUnion a;
a.var2 = -1;
printf("a is %ld bits and has value %ld = 0x%lx\n",
sizeof(a)*8, a.var2, a.var2);
a.var3 = 'y';
printf("a is %ld bits and has value %c = 0x%x\n",
sizeof(a)*8, a.var3, a.var3);
printf("a is %ld bits and has value %ld = 0x%lx\n",
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
docs中的行实际上意味着对union成员的最后一个赋值指定了union的值,其余的内存被认为是垃圾。虽然,我们通常可以观察分配给整个联盟的内存中的剩余物。
答案 2 :(得分:2)
对于它的价值,C11标准§6.5.2.3,注释95(第83页)说:
如果用于读取union对象内容的成员与上次用于在对象中存储值的成员不同,则该值的对象表示的相应部分将被重新解释为对象表示形式。 6.2.6中描述的新类型(有时称为''punning''的过程)。这可能是陷阱表示。
这就是我所看到的,即使编译为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
请注意,最后一个值不 123456 7 89,但123456 8 89,因为最低有效字节被
a.var3 = 'y';
将0x15
替换为0x79
(== 'y'
)。
答案 3 :(得分:2)
你确定你得到了你写的东西吗?
在使用GCC 5.4.0的ubuntu 64位中,我得到:
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的不太重要的字节,因为它们共享相同的内存地址。