查看以下简单的代码
int main()
{
short x = 0, y = 0;
scanf("%d", &x);
scanf("%d", &y);
printf("%d %d\n", x, y);
return 0;
}
如果你输入4和5到这个程序,你希望输出中得到4和5。在Windows(mingw)上使用GCC 4.6.2,它产生0和5作为输出。所以我挖了一下。这是生成的汇编代码
movw $0, 30(%esp)
movw $0, 28(%esp)
leal 30(%esp), %eax
movl %eax, 4(%esp)
movl $LC0, (%esp)
call _scanf
leal 28(%esp), %eax
movl %eax, 4(%esp)
movl $LC0, (%esp)
call _scanf
虽然我没有做太多的汇编编码,但上面的代码看起来并不正确。似乎建议将x放置在esp的30个字节的偏移处,并且将y放置在esp的28个字节的偏移处,然后将它们的地址传递给scanf。因此,当x和y的地址作为长整数(4字节地址)处理时,应发生以下情况: 第一次调用将字节[30,34]设置为值0x00000004,第二次调用将字节[28,32]设置为值0x00000005。但是,由于这是一个小端机器,我们将从30开始[0x04 0x00 0x00 0x00],然后从28开始[0x05 0x00 0x00 0x00]。这将导致字节数30重置为0.
我尝试颠倒scanfs的顺序,并且它起作用(输出确实为4和5),所以现在,首先填充较小的偏移,然后填充后者(较大的)偏移。
海湾合作委员会可能搞砸了这似乎是荒谬的。所以我尝试了MSVC,它生成的程序集有一个明显的区别。变量放在偏移-4和-8处(即它们被认为是4个字节长,尽管注释表示2个字节)。这是代码的一部分:
_TEXT SEGMENT
_x$ = -8 ; size = 2
_y$ = -4 ; size = 2
_main PROC
push ebp
mov ebp, esp
sub esp, 8
xor eax, eax
mov WORD PTR _x$[ebp], ax
xor ecx, ecx
mov WORD PTR _y$[ebp], cx
lea edx, DWORD PTR _x$[ebp]
push edx
push OFFSET $SG2470
call _scanf
add esp, 8
lea eax, DWORD PTR _y$[ebp]
push eax
push OFFSET $SG2471
call _scanf
add esp, 8
我的问题分为两部分:
但更重要的是,
答案 0 :(得分:3)
要在scanf()
上使用short
,您必须在格式字符串中指定%hd
。
你引发了溢出,因为你在骗scanf()
。打开警告(至少-Wall
)。您应该收到GCC关于不匹配的投诉。 (当你学习C语言时,使用-Wall
来捕捉你犯下的愚蠢错误。当你在C语言编程超过四分之一世纪时,你会添加一些标志来确保你仍然没有犯下愚蠢的错误。而且你总是要确保代码用-Wall
编译干净。)
Mac OS X 10.7.5上的GCC 4.7.1说:
ss.c:6:4: warning: format ‘%d’ expects argument of type ‘int *’, but argument 2 has type ‘short int *’ [-Wformat]
ss.c:7:4: warning: format ‘%d’ expects argument of type ‘int *’, but argument 2 has type ‘short int *’ [-Wformat]
答案 1 :(得分:1)
Jonathan Leffler的回答解释了scanf
的问题。人们可能想知道printf
如何正常工作。
printf
似乎有效的原因是它是一个可变函数,即一个接受可变数量参数的函数。在C标准中(因此在英特尔平台上实现的ABI中),小于int(字符,短路)的整数类型的所有值都作为堆栈中的int传递给可变函数,并且所有float
值都作为传递double
。但是,此技巧不适用于scanf
,它接收对象地址而不是实际值。即使在printf
的上下文中被视为“良性”的错误也会使scanf
超出它应该分配的对象。
答案 2 :(得分:0)
哈!关于汇编代码的所有挖掘都是一个洗眼!快速谷歌搜索格式标识符产生a rather hidden one(%hi)用于短整数。问题在于代码中的格式说明符,而不是代码本身。
因此,当scanf传递了%d时,它为传递的地址写了一个4字节的数字,然后问题中说明的所有问题都开始出现了。
现在,只剩下一个问题了。为什么GCC和VC ++在程序中的变量定位上有所不同?这只是一个迂腐的问题(GCC over VC ++)还是会产生实际后果?