程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C >> 關於C >> C語言函數參數入棧的匯編理解

C語言函數參數入棧的匯編理解

編輯:關於C

先來看這樣一段程序:

 

#include 
#include 
#include 

void print1(int a,int b,int c)
{
    printf(%p
,&a);
    printf(%p
,&b);
    printf(%p
,&c);
}

int main(void)
{
   print1(1,2,3);
   exit(0);
}

 

它的輸出是:

 

0022FF40
0022FF44
0022FF48
發現a,b,c的地址是逐漸增大的,差值是4個字節。這和我所知道的:C函數參數入棧的順序是從右到左 是相匹配的,而且地址的增大值也

 

與變量所占的字節數相匹配。

不過當把程序稍微做一下修改,如下:

#include 
#include 
#include 

void print2(char a,char b,char c)
{
   printf(%p
,&a);
   printf(%p
,&b);
   printf(%p
,&c);
}

int main(void)
{
   print2(1,2,3);
   exit(0);
}
再觀察一下它的輸出:

 

 

0022FF2C
0022FF28
0022FF24
怎麼和上面的效果是相反的!雖然我知道這肯定編譯器的一個技巧,不過 參數入棧的順序是從右到左的 概念卻動搖了。

 

為了弄清楚其中的道理,必須觀察程序生成的中間.s文件,為此,我執行了以下一條命令:

 

gcc -S test.c(當前C文件中保存的程序是文章一開始的那個) 在當前目錄下生成test.s文件
使用vim打開test.s文件(只截取主要內容了):

 

esp是指向棧頂的指針,ebp是用來備份這個指針的。棧的形狀如下:

esp

ebp

|____________________________________________________

棧的最大值 棧的最小值

每壓入一個參數入棧,就執行 esp = esp - sizoeof(參數)。不過在esp值變之前,先備份一下 ebp = esp,這樣不管最後esp指到哪裡去了,函數結束時就用這個ebp就能順利回到調用者了。

 

print1:
   pushl %ebp//6.先把ebp壓棧,保存這個指針
   movl  %esp, %ebp//7.使ebp這個指針保存著esp這個指針指向的地址值
   subl  $8, %esp//8.使esp - 8,也就是說空下8個字節以便實現某個功能
   leal   8(%ebp), %eax//9.把(ebp + 8)的地址給eax 這個地方為什麼要+8 因為這個函數在經歷第5,6步的時候存在著壓了兩個4字節入棧的操作。此時+8就指向了實參1
   movl  %eax, 4(%esp)//10.這個時候就用到第8步空下來的8個字節中的4個了,原來是保存值,原理就是用C語言寫兩個數交換值時的那個第三個變量,即緩沖區
   movl  $.LC0, (%esp)	//11.把字符串“%p
”壓棧     從第10,11步來看,兩個參數的入棧順序,其實不管順序了,兩個參數,最右邊的在高地址,最左邊的在低地址
   call  printf//12.調用函數printf,又是壓棧出棧的操作了	到此可以得到8個字節的緩沖區全部用完了
   leal  12(%ebp), %eax//13.同第9步,此時獲取的是實參2的地址
   movl  %eax, 4(%esp)//14.我想說同上
   movl  $.LC0, (%esp)	//15.我想說同上
   call  printf//16.我想說同上
   leal  16(%ebp), %eax//17.同第9步,此時獲取的是實參3的地址
   movl  %eax, 4(%esp)//18.我想說同上
   movl  $.LC0, (%esp)	//19.我想說同上
   call  printf//20.我想說同上。到了此處我們就知道,printf打印參數的地址,這個地址是在main函數中壓棧時分配的,是什麼就是什麼,符合參數入棧的順序是從右到左這個說法。
   leave
   ret 
   .size print1, .-print1
.globl main
   .type main, @function
main:
   leal  4(%esp), %ecx
   andl  $-16, %esp
   pushl -4(%ecx)
   pushl %ebp
   movl  %esp, %ebp
   pushl %ecx
   subl  $20, %esp//1.先把棧預留20個字節,這其中的原因(為什麼是20)估計與什麼算法有關
   movl  $3, 8(%esp)//2.看!,先把3放入esp + 8
   movl  $2, 4(%esp)//3.再看!,把2放入esp + 4
   movl  $1, (%esp)//4.最後把1放入esp
   call  print1//5.調用函數print1。至此可以看到參數1,2,3是從右往左壓入棧的,3在最高最地址(相對於1的保存地址來說),而1就在最低地址了(相對於3的保存地址來說)
   movl  $0, (%esp)
   call  exit
   .size main, .-main

好的,這個程序分析完了,再來看有疑問的程序吧:

 

 

print2:
   pushl %ebp//5.我想說同上
   movl  %esp, %ebp//6.我想說同上
   subl  $24, %esp//7.這個就不同上了,比上面那個esp - 8大很多嗎,不過要記住,這24個字節是個緩沖區
   movl  8(%ebp), %eax//8.把實參1放入eax
   movl  12(%ebp), %edx//9.把實參2放入edx
   movl  16(%ebp), %ecx//10.把實參3放入ecx
   movb  %al, -4(%ebp)//11.把eax的低字節放入ebp - 4
   movb  %dl, -8(%ebp)//12.把edx的低字節放入ebp - 8
   movb  %cl, -12(%ebp)//13.把ecx的低字節放入ebp -12。從這個地方就可以發現問題的出現原因了,此時的實參1所存放的地址高於存放實參3的地址。到此,24字節的緩沖區已經使用了12
   leal  -4(%ebp), %eax//14.把實參1存放的地址放入eax
   movl  %eax, 4(%esp)//15.把實參1放入esp + 4
   movl  $.LC0, (%esp)	//16.把字符串“%p
”放入esp
   call  printf//17.調用函數printf。從此依然可以看出函數參數的入棧地址是最右邊的在高地址,最左邊的在低地址。到此24字節的緩沖區使用了20個,還余下4個沒有用
   leal  -8(%ebp), %eax//18.我想說同上
   movl  %eax, 4(%esp)//19.我想說同上
   movl  $.LC0, (%esp)	//20.我想說同上
   call  printf//21.我想說同上
   leal  -12(%ebp), %eax//22.我想說同上
   movl  %eax, 4(%esp)//23.我想說同上
   movl  $.LC0, (%esp)	//24.我想說同上
   call  printf//25.我想說同上
   leave
   ret
   .size print2, .-print2
.globl main
   .type main, @function
main:
   leal  4(%esp), %ecx
   andl  $-16, %esp
   pushl -4(%ecx)
   pushl %ebp
   movl  %esp, %ebp
   pushl %ecx
   subl  $20, %esp
   movl $3, 8(%esp)//1.和上面那個程序一樣的壓棧操作
   movl $2, 4(%esp)//2.我想說同上
   movl $1, (%esp)//3.我想說同上
   call  print2//4.我想說同上
   movl  $0, (%esp)
   call  exit
   .size main, .-main

 

 

 


  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved