2012-11-03 19 views
0

請看下面一段簡單的代碼這是GCC(mingw)/ glibc bug - scanf是否有短褲?

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的輸出。在窗口(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.

I嘗試顛倒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機器。這是海灣合作委員會的問題,還是隻是一個問題?

但是,更重要的是,

  • 這是一個錯誤呢?編譯器如何判斷是否應該將「short」置於2字節偏移量或4字節偏移量?
+1

「我沒有可用的個人Linux機器」 - 您可以通過在Windows機器上安裝VirtualBox,然後在VM中安裝Linux來解決此問題。 –

回答

3

要在short上使用scanf(),必須在格式字符串中指定%hd

由於您在騙取scanf(),您正在挑釁溢出。打開警告(至少-Wall)。你應該從GCC得到關於不匹配的投訴。 (當你學習C時,使用-Wall來捕捉你所犯的愚蠢錯誤。當你用C語言編程了超過25年的世界時,你會添加更多的標誌來確保你仍然不是' ... T在Mac OS X 10.7.5些荒謬的錯誤而你將永遠確保代碼編譯乾淨-Wall

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] 
+0

謝謝!採取點!爲什麼GCC和MSVC程序中變量的放置位置不同? – mayur

+0

變量的放置取決於編譯器。有人可能會建議MSVC比GCC使用更多的空間;據推測,有一個補償的好處(更快的訪問)。對我來說,略微沒有預料到在MSVC上'&x'和'&y'的地址相距4個字節,但這是我不擔心的C程序員(但會擔心我是否是C編譯器作家)。只要行爲正確,編譯器就可以以不同的方式實現;當準確的代碼被寫入時,它們將在這個代碼的(變體)上正確行爲。 –

1

喬納森·萊弗勒的回答解釋了問題與scanf。有人可能會想知道printf如何正常工作。

printf似乎工作的原因是它是一個可變參數函數,即接受可變數量參數的函數。在C標準中(因此在英特爾平臺上實現的ABI中),小於int(字符,短褲)的整型的所有值將作爲堆棧中的整數傳遞給可變參數函數,並且所有值將作爲double傳遞。但是,這個技巧不適用於scanf,它接收對象地址而不是實際值。即使在printf的上下文中被認爲是「良性」的錯誤也會使scanf超出它應該分配給的對象。

0

哈!所有關於彙編代碼的挖掘都是一場洗眼的!快速谷歌搜索格式標識符產生a rather hidden one(%hi)用於短整數。問題在於代碼中的格式說明符,而不是代碼本身。

因此,當scanf通過%d時,它會向傳遞的地址寫入一個4字節的數字,然後問題中說明的所有問題都開始顯示出來。

現在,只剩下一個問題了。爲什麼GCC和VC++在程序中的變量定位上有所不同?這只是一個迂腐的問題(GCC over VC++)還是這會產生實際後果?

+1

這是實現可以爲自己做出的選擇。 GCC更高效地使用堆棧空間,但MSVC可以保護您免受特定類型的超限(例如您遇到的那種超限)的影響。也可能在某些體系結構上對齊4字節邊界上的短褲允許更快訪問,並且MSVC安排仍然存在。 – user4815162342

+0

您可以使用'%hi'來接受0377,0xFF,255中的任何一個作爲有效輸入;你可以使用'%hd'作爲十進制輸入;你可以使用'%hx'作爲十六進制;你可以使用'%ho'作爲八進制輸入。在scanf()和printf()之間增加了輸出格式'%i'(相當於'%d')(使用C89標準)。 –

+0

MSVC和GCC可能選擇以不同方式佈置變量的原因有很多。其中一個原因可能是因爲在優化時,MSVC會在這些16位變量的時候使用更高效的32位訪問。另一個原因是「只是因爲」 - 你可能會問爲什麼你在'y'之前聲明'x'。但是,有關MSVC選擇基於變量*名稱*(不是類型或大小)更改的不同佈局的有趣示例,請參閱http://stackoverflow.com/a/4577565/12711 –