C-C下的长短数据类型赋值
今天无意间碰到的问题,希望能得到解释:
#include "stdio.h"
void main()
{
long long i;
unsigned long d;
long a;
i=4294967295;
d=i;
a=i;
printf("%d,%xn",i,d);
printf("%xn",d);
d=65535;
printf("%dn",d);
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
你看一下,C和汇编的对照。
long long i;
unsigned long d;
long a;
i=4294967295;
8048395: c7 45 e8 ff ff ff ff movl $0xffffffff,0xffffffe8(%ebp)
804839c: c7 45 ec 00 00 00 00 movl $0x0,0xffffffec(%ebp)
d=i;
80483a3: 8b 45 e8 mov 0xffffffe8(%ebp),%eax
80483a6: 89 45 f0 mov %eax,0xfffffff0(%ebp)
a=i;
80483a9: 8b 45 e8 mov 0xffffffe8(%ebp),%eax
80483ac: 89 45 f4 mov %eax,0xfffffff4(%ebp)
printf("%d,%xn",i,d);
80483af: 8b 45 f0 mov 0xfffffff0(%ebp),%eax
80483b2: 89 44 24 0c mov %eax,0xc(%esp)
80483b6: 8b 45 e8 mov 0xffffffe8(%ebp),%eax
80483b9: 8b 55 ec mov 0xffffffec(%ebp),%edx
80483bc: 89 44 24 04 mov %eax,0x4(%esp)
80483c0: 89 54 24 08 mov %edx,0x8(%esp)
80483c4: c7 04 24 e0 84 04 08 movl $0x80484e0,(%esp)
80483cb: e8 c8 fe ff ff call 8048298 <printf@plt>
printf("%xn",d);
80483d0: 8b 45 f0 mov 0xfffffff0(%ebp),%eax
80483d3: 89 44 24 04 mov %eax,0x4(%esp)
80483d7: c7 04 24 e7 84 04 08 movl $0x80484e7,(%esp)
80483de: e8 b5 fe ff ff call 8048298 <printf@plt>
d=65535;
80483e3: c7 45 f0 ff ff 00 00 movl $0xffff,0xfffffff0(%ebp)
printf("%dn",d);
80483ea: 8b 45 f0 mov 0xfffffff0(%ebp),%eax
80483ed: 89 44 24 04 mov %eax,0x4(%esp)
80483f1: c7 04 24 eb 84 04 08 movl $0x80484eb,(%esp)
80483f8: e8 9b fe ff ff call 8048298 <printf@plt>
具体的解析:
1. i是64位的,d是32位的,都以小端模式存储;
2. printf语句是从右开始解析的,所以首先将32位全f的d压到printf的函数栈里;
3. 然后将64位的i分两次压到函数栈中去;
4. 执行输出的时候,因为是%d,所以只解析了32位,后面的%x,又解析了32位;
所以,总的意思就是说,d的值确实是全f(这一点从你又一次的printf可以发现d被正确打印了),你第一次的printf是把64位的i分成了两半打印了出来,结果你认为一半是i一半是d,其实d的值根本没有被解析。
你把语句改成
printf("%d,%d,%xn",i,d,d);
你就会发现是-1,0,ffffffff
但是这样显然不正确,所以,你既要正确输出i又要正确输出d,就应该改成
printf("%lld, %xn",i,d);
首先,i是long long类型,占64位,赋值4294967295之后,i的地址空间的存储情况就是:
ff ff ff ff 00 00 00 00
d是unsigned long类型,占32位,取i的低32位赋值,则d的地址空间的存储情况就是:
ff ff ff ff
也就是d=4294967295
a是long类型,占32位,取i的低32位赋值,则a的地址空间的存储情况就是:
ff ff ff ff
也就是a=-1(因为a是有符号的,第一位是符号位,而按照补码存储的话,ffffffff就是-1)
明白了上述介绍之后,下面来讲述具体原因。
当执行语句printf("%d,%xn",i,d);的时候,我们通过反汇编,结果如下:
从上图中可以看出,这一行是这样执行:
1.把d的数据压栈,即图中的“mov eax,dword ptr[d]”和“push eax”这两句,压入d的值4294967295
2.再把i的数据压栈。由于i是long long类型,占64位,而寄存器只能存储32位,所以这里有两个压栈动作:
第一次把i的高32位压栈,即图中的“move ecx,dword ptr[ebp-8]”和“push ecx”这两句,压入i的高32位的数据,从i的地址空间存储情况中可以看出,高32位全是0,则压入的是数字0.
第二次把i的低32位压栈,即图中的“move edx,dword ptr[i]”和“push edx”这两句,压入i的低32位的数据,从i的地址空间存储情况中可以看出,低32位全是1,则压入的数字就是4294967295
3.再把输出格式字符串压栈,也就是printf的第一个参数,然后执行。
由于printf的输出格式中:
第一个需要显示的数字是%d格式的,而%d是显示整数类型,32位,所以系统只会从栈中弹出一个32位数据,也就是最后一个压栈的,就是i的低32位,即ffffffff,而%d输出的是有符号的整数,所以会显示-1。
然后系统继续往后读,第二个需要显示的数字是%x格式的,而%x是显示16进制的整数,32位,所以系统只会从栈中继续弹出一个32位的数据,也就是倒数第二次压栈的,就是i的高32位,即00000000,所以会显示0。
通过上面的分析,就可以知道为什么会显示0了。那么如何避免这种情况的发生呢?从上述分析可以看出,根本原因在于i的类型是long long类型,压栈的时候进行了2次压栈操作,而输出格式是%d,只进行了一次出栈操作。所以解决的办法就是改变i的输出格式。对于需要输出long long类型的数据,输出格式是%lld。
所以只需要把printf("%d,%xn",i,d);
改成printf("%lld,%xn",i,d);
让输出i的时候,进行两次出栈操作,就会保证结果的正确了。
这行代码有问题:
printf("%d,%xn",i,d);
改成:
printf("%lld, %xn",i,d);
输出就对了:
4294967295, ffffffff
ffffffff
65535
若要仔细分析其中缘由,还得从汇编代码说起。(以下分析基于VS2008,debug模式,win7 32位旗舰版)
先看看long long i的内存的布局
; 5 : long long i;
mov DWORD PTR _i$[ebp], -1
mov DWORD PTR _i$[ebp+4], 0
可以看到变量i是64位,用了8个bytes来存,其中低32位为0xFFFFFFFF,高32为全0(小端模式)。
继续看看printf的调用情况:
; 12 : printf("%d, %xn",i,d);
mov esi, esp
mov eax, DWORD PTR _d$[ebp]
push eax
mov ecx, DWORD PTR _i$[ebp+4]
push ecx
mov edx, DWORD PTR _i$[ebp]
push edx
push OFFSET ??_C@_07NCPMEAHF@?$CFd?0?5?$CFx?6?$AA@
call DWORD PTR __imp__printf
因为i是64位变量,但我们的机器是32位,所以变量i需要2次push操作。先push高32位,再push低32位。最后调用printf()函数,此时printf的函数栈情况如下:
++++++++++++++++++++++
+ 变量d +
++++++++++++++++++++++
+ i高32位:全0 +
++++++++++++++++++++++
+ i低32位:全1 +
++++++++++++++++++++++
+ 常量"%d, %xn"的地址 +
++++++++++++++++++++++
printf会根据常量"%d, %xn"的内容逐个解析,遇到%d时,其实打印的是i的低32位,为-1(虽然输出的值都是-1,但仅仅是个巧合),继续解析%x,此时打印的是i的高32位,所以这个d的值看起就是0了。
基于上面的分析,若将原来的代码改成:
printf("%d, %x, %xn",i,d,d);
输出如下:
-1, 0, ffffffff
ffffffff
65535
结果如我们所料,最后一个d的才是真正d的值。