從反彙編理解堆棧及printf

#include
int main()
{
    long long a = 1, b = 2, c = 3;
    printf("%d %d %d\n", a,b,c);
    return 0;
}

//Tencent某年實習生筆試題目


結果是:

1 0 2

Process returned 0 (0x0)   execution time : 0.136 s
Press any key to continue.


該段轉自:

http://blog.csdn.net/yang_yulei/article/details/8086934


sprintf/fprintf/printf/sscanf/fscanf/scanf等這一類的函數,它們的調用規則(calling conventions)是cdecl,cdecl調用規則的函數,所有參數從右到左依次入棧,這些參數由調用者清除,稱爲手動清棧。被調用函數不會要求調用者傳遞多少參數,調用者傳遞過多或者過少的參數,甚至完全不同的參數都不會產生編譯階段的錯誤。函數參數的傳遞都是放在棧裏面的,而且是從右邊的參數開始壓棧,printf()是不會對傳遞的參數進行類型檢查的,它只有一個format specification fields的字符串,而參數是不定長的,所以也沒辦法對傳遞的參數做類型檢查,也沒辦法對參數的個數進行檢查。所以了,壓棧的時候,參數列表裏的所有參數都壓入棧中了,它不知道有多少個參數,所以它都壓棧。
所以對於問題1中的代碼,壓入棧之後應該是這樣的:
棧裏面壓進去了三個數,a,b和c因爲a佔2*4個bytes,b佔1*4個byte,所以現在棧裏面有3*4個bytes。c、b先後壓入棧,a最後壓入棧。因爲這是little endian,即每個數字的高字節在高地址,低字節在低地址。而棧的內存生長方向是從大到小的,也就是棧底是高地址,棧頂是低地址,所以a的低字節在低地址(低地址值爲0x00000001,高地址值爲0x00000000)。(有條件的同學可以在big endian的機器上驗證一下)
那麼輸出的時候,format specification fields字符串其匹配棧裏面的內容,首先一個%d取出4個bytes出來輸出,然後後面又有一個%d再取出4個bytes出來打印。所以結果就是這樣了。也就是說剛開始壓入棧的b的值在輸出的時候根本都沒有用到,c更沒用到。


printf在壓棧時,對於長度小於32位的參數,自動擴展成32位(由CPU的位數決定的)。
故在根據格式串解釋時,對於%c %hd這樣的小於32位數據的格式串,系統也會自動提取32位數據解釋,而不會提取8位或16位來解釋。(因爲你把人家壓入的時候就規定了擴展成32位嘛),但輸出結果仍是8位或者16位值
至於浮點參數壓棧的規則:float(4 字節)類型擴展成double(8 字節)入棧。所以在輸入時,需要區分float(%f)與double(%lf),而在輸出時,用%f即可。printf函數將按照double型的規則對壓入堆棧的float(已擴展成double)和double型數據進行輸出。

另附一段程序的反彙編作爲理解;

0x00401334 push   %ebp    //ebp入棧作爲保護
0x00401335 mov    %esp,%ebp //棧頂地址給ebp,ebp是讀取棧內容的寄存器
0x00401337 and    $0xfffffff0,%esp
0x0040133A sub    $0x40,%esp
0x0040133D call   0x401970 <__main>
0x00401342 movl   $0x1,0x38(%esp) //棧底是0x1,+4空間是0x0
0x0040134A movl   $0x0,0x3c(%esp)
0x00401352 movl   $0x2,0x30(%esp)
0x0040135A movl   $0x0,0x34(%esp)
0x00401362 movl   $0x3,0x28(%esp)
0x0040136A movl   $0x0,0x2c(%esp) //上面到main是按定義順序入棧,這裏是小頭先入棧,後面會做調整
0x00401372 mov    0x28(%esp),%eax //以下爲調整
0x00401376 mov    0x2c(%esp),%edx
0x0040137A mov    %eax,0x14(%esp)
0x0040137E mov    %edx,0x18(%esp) //按照大地址先入棧的順序入棧,先入c
0x00401382 mov    0x30(%esp),%eax
0x00401386 mov    0x34(%esp),%edx
0x0040138A mov    %eax,0xc(%esp)
0x0040138E mov    %edx,0x10(%esp) //b
0x00401392 mov    0x38(%esp),%eax
0x00401396 mov    0x3c(%esp),%edx
0x0040139A mov    %eax,0x4(%esp)
0x0040139E mov    %edx,0x8(%esp) //最後入棧a,調整後的順序是c b a,大地址在棧底,a最先定義可以先出棧
0x004013A2 movl   $0x403024,(%esp)
0x004013A9 call   0x401be0 <printf> //下面是printf,就按之前的說明執行
0x004013AE mov    $0x0,%eax
0x004013B3 leave
0x004013B4 ret


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章