这是一个GCC(mingw)/ glibc bug - 带短裤的scanf?

时间:2012-11-03 08:14:48

标签: gcc mingw scanf short

查看以下简单的代码

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

我的问题分为两部分:

  • 我没有自己的个人Linux机箱。这是海湾合作委员会的问题,还是仅仅是一个问题?

但更重要的是,

  • 这是一个错误吗?编译器如何确定是否应将“短”置于2字节偏移或4字节偏移处?

3 个答案:

答案 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 ++)还是会产生实际后果?