前些天有个年轻的同事问我:杰伦杰伦,为什么相同的code,同一个变量在Windows和Linux上打印出来的结果不一样啊?我没搞明白为什么有这个问题,那不就说明实际上这个变量的值在Windows/Linux上不一样啊!于是找来相关code看了一眼,简化后如下:
union MyUnion
{
int i;
float f;
};
int main()
{
MyUnion u;
u.i = 0xdeadbeef;
printf("i %x\nf %x\n", u.i, u.f);
}
“打印结果i的值是相同的,但是f的值不一样。”
啊?这是个union,i的值一样一样当然意味着f的值实际上也是一样的。再细看,我大惊失色,怎么能用%x输出float类型呢?不应该用%f吗?这是undefined behavior啊!
“那为什么两边打印的f结果不一样呢。”
“那就是不同系统的调用约定不一样了。”
MSVC
我们首先得知道Windows上x86-64的调用约定。根据Wikipedia上的x86 calling conventions,前四个整型参数依次使用寄存器%rcx
,%rdx
,%r8
,%r9
,前四个浮点参数依次使用%xmm0
,%xmm1
,%xmm2
,%xmm3
寄存器。如果参数表没有指明原型,则调用者应当在整型的寄存器和浮点的寄存器各存一次。
再来看下Windows上的对应的汇编代码:
MyUnion u;
u.i = 0xdeadbeef;
00007FF788A94CDD mov dword ptr [u],0DEADBEEFh
printf("i %x\nf %x\n", u.i, u.f);
00007FF788A94CE4 cvtss2sd xmm0,dword ptr [u]
00007FF788A94CE9 movaps xmm2,xmm0
00007FF788A94CEC movq r8,xmm2
00007FF788A94CF1 mov edx,dword ptr [u]
00007FF788A94CF4 lea rcx,[string "i %x\nf %x\n" (07FF788A99CA8h)]
00007FF788A94CFB call printf (07FF788A91190h)
可以看到,printf第一个参数字符串"i %x\nf %x\n"
的地址被保存到了%rcx
里;u.i
的值被直接保存到了%edx
寄存器里,%edx
是调用约定的第二个整数寄存器;对于u.f
,在调用printf前,先通过cvtss2sd
指令将整型转换成了浮点型保存到浮点寄存器%xmm0
中,再把该值分别保存到%xmm2
和%r8
里。此时%r8
里的数据为0xc3d5b7dde0000000。由于需要的%x是个32位整型,printf会去查找对应的整型寄存器,即输出%r8
的低32位,所以最后看到的结果是e0000000:
i deadbeef
f e0000000
GCC
Linux调用约定的寄存器依次为%rdi
,%rsi
,%rdx
,%rcx
,%r8
,%r9
,浮点寄存器依次为%xmm0
到%xmm7
。
再来看下用gcc得到的汇编:
11c4: c7 45 f4 ef be ad de movl $0xdeadbeef,-0xc(%rbp)
11cb: f3 0f 10 45 f4 movss -0xc(%rbp),%xmm0
11d0: f3 0f 5a c0 cvtss2sd %xmm0,%xmm0
11d4: 8b 45 f4 mov -0xc(%rbp),%eax
11d7: 89 c6 mov %eax,%esi
11d9: 48 8d 3d 25 0e 00 00 lea 0xe25(%rip),%rdi # 2005 <_ZStL19piecewise_construct+0x1>
11e0: b8 01 00 00 00 mov $0x1,%eax
字面值0xdeadbeef被保存到了地址为-0xc(%rbp)
的内存中,然后又被复制到了%esi
寄存器作为参数u.i
。该值也被复制到了xmm0
中,随后原地做了一次类型转换。但是print需要的仍然是%x,所以在打印u.f
时会去查找用来保存第三个参数的寄存器%rdx
,而%rdx
里的内容完全是随机的,所以得到结果也是随机的。
小结
如果你对float类型的二进制表达感兴趣,可以将对它取地址再将它转成int类型,而不是直接通过整型去打印它。
printf("i %x\nf %x\n", u.i, *(int*)&u.f);
讲了半天,其实MSVC和GCC在编译时都会检测这个UB并给出warning。所以大家还是不要对编译器的warning熟视无睹啊,千万别去踩UB的累,要是出了问题就是“代码不规范,你我两行泪”,浪费很多不必要的时间。
0 条评论