看下面一段简单的代码
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 字节偏移处?