0

看下面一段简单的代码

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),所以现在,首先填充较小的偏移量,然后是后者(较大的)偏移量。

GCC 可能把这件事搞砸了,这似乎很荒谬。所以我尝试了 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 机器。这是 GCC 问题,还是仅仅是 mingw 问题?

但是,更重要的是,

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

3 回答 3

3

要使用scanf()on short,您必须%hd在格式字符串中指定。

你在挑衅溢出,因为你在撒谎scanf()。打开警告(-Wall至少)。您应该从 GCC 收到有关不匹配的投诉。(当你在学习 C 时,用它-Wall来捕捉你所犯的愚蠢错误。当你像我一样使用 C 编程超过 25 年时,你将添加更多标志以确保你仍然不是犯愚蠢的错误。并且您将始终确保代码编译干净-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]
于 2012-11-03T08:17:20.947 回答
1

Jonathan Leffler 的回答解释了scanf. 那么,人们可能想知道如何printf工作得很好。

原因printf似乎是它是一个可变参数函数,即接受可变数量参数的函数。在 C 标准中(因此在 Intel 平台上实现的 ABI 中),所有小于 int(chars、shorts)的整型值都作为 int 在堆栈上传递给可变参数函数,所有float值都作为double. 但是,此技巧不适用于scanf接收对象地址而不是实际值的 。printf即使是在上下文中被认为是“良性”的错误也会scanf超出它应该分配的对象。

于 2012-11-03T08:51:03.433 回答
0

哈!所有关于汇编代码的挖掘都是洗眼!快速谷歌搜索格式标识符产生了一个相当隐藏的(%hi)用于短整数。问题在于代码中的格式说明符,而不是代码本身。

因此,当 scanf 传递 %d 时,它会向传递的地址写入一个 4 字节的数字,然后问题中说明的所有问题都开始出现。

现在,只剩下一个问题了。为什么 GCC 和 VC++ 在程序中变量的定位不同?这只是迂腐的问题(GCC over VC++)还是有实际后果?

于 2012-11-03T08:58:30.313 回答