C語言函數調用時的傳參操作在32位x86機器上依賴棧進行.而在x86_64的機器上使用了部分寄存器作為輔助,但如果參數過多,寄存器不夠使用,此時也必須藉助於棧操作實現傳參.儘管C語言對函數傳遞參數的個數沒有明確限制(依編譯器實現而定:http://stackoverflow.com/questions/9034787/function-parameters-max-number),但過多的參數傳遞勢必影響代碼執行效率.
通常C語言函數傳參是非常明確的,如下面這個函數:
int test(int a,float b,int *pointer);
注:以下例子均使用32位x86,gcc編譯器說明.
但如果把數組或結構體作為參數傳遞呢?到底傳遞了多少參數,佔用了多少棧空間?
typedef struct list{ int a; int b; int c; int d; int e;}list;int test1(int array[5]);int test2(list listtest);
先看數組的例子:
/*passing arguments test:array*/#include<stdio.h>void array(int tmp[5]){ printf("%d\n",tmp[2]);}int main(void){ int test[]={1,2,3,4,5}; array(test);}編譯成彙編代碼,先截取main函數傳參部分:
movl $1, -32(%ebp) movl $2, -28(%ebp) movl $3, -24(%ebp) movl $4, -20(%ebp) movl $5, -16(%ebp) leal -32(%ebp), %eax pushl %eax call array
可以看到,在main函數中先將數組元素寫入數組空間內,然後將數組地址(即元素a[0]的地址)儲存在eax中,接著把eax壓棧,最後調用array函數.再看看array函數:
array:.LFB0: pushl %ebp movl %esp, %ebp subl $8, %esp movl 8(%ebp), %eax addl $8, %eax movl (%eax), %eax subl $8, %esp pushl %eax pushl $.LC0 call printf addl $16, %esp nop leave ret
首先是老套的操作:儲存ebp,同步ebp和esp,esp向下移動,以建立新函數棧環境.然後取8(%ebp).what is it?其實函數執行到這裡經過了3次壓棧:pushl %eax,call array,pushl %ebp.而每一次壓棧都是32位,也就是4個位元組.所以0(%ebp)是pushl %ebp的值,4(%ebp)是函數返回地址,8(%ebp)是pushl %eax的值,即數組地址.當然後面的addl $8,%eax就是tmp[2]的地址了,不再贅述.說了這麼多,總之一句話,array(test)僅僅是傳遞了一個數組地址而已,並沒有把整個數組元素一起作為參數傳給子函數.
再來看結構體的例子:
#include<stdio.h>typedef struct list{ int a; int b; int c; int d;}list;void test(list tmp){ printf("%d\n",tmp.b);}int main(void){ list tmp={.a=10,.b=20,.c=30,.d=40}; test(tmp);}
同樣截取main函數參數傳遞片段:
movl $10, -24(%ebp) movl $20, -20(%ebp) movl $30, -16(%ebp) movl $40, -12(%ebp) pushl -12(%ebp) pushl -16(%ebp) pushl -20(%ebp) pushl -24(%ebp) call test
可以看到,這下是實實在在把結構體中的成員全部壓棧了.因此在開源項目代碼中,常常不會看到以結構體作為參數的,而是傳遞一個指向該結構體的指標.這樣無論結構體有多少成員,壓棧也僅僅壓入一個值了.如下面的代碼:
#include<stdio.h>typedef struct list{ int a; int b; int c; int d;}list;void test(list *tmp){ printf("%d\n",tmp->b);}int main(void){ list tmp={.a=10,.b=20,.c=30,.d=40}; test(&tmp);}