C/C++函数参数的入栈顺序,计算顺序和可变参数的实现

函数参数入栈顺序


[cpp]  view plain copy
  1. #include  
  2. void foo(int x, int y, int z)  
  3. {  
  4.         printf("x = %d at [%X]\n", x, &x);  
  5.         printf("y = %d at [%X]\n", y, &y);  
  6.         printf("z = %d at [%X]\n", z, &z);  
  7. }  
  8. int main(int argc, char *argv[])  
  9. {  
  10.         foo(100, 200, 300);  
  11.         return 0;  
  12. }  

运行结果是:

x = 100 at [...60]

y = 200 at [...64]

z = 300 at [...68]

这是由于,C程序栈的内存生长方式是往低地址内存生长,这也说明为什么局部变量无法申请太大内存,因为栈内容有限。此外,这个例子说明,函数参数的入栈的顺序是从右往左的!。参数入栈顺序具体的还与编译器相关,涉及到C语言中调用约定所采用的方式:

C调用约定在返回前,要作一次堆栈平衡,也就是参数入栈了多少字节,就要弹出来多少字节.这样很安全.

有一点需要注意:stdcall调用约定如果采用了不定参数,即VARARG的话,则和C调用约定一样,要由调用者来作堆栈平衡.

(1)_stdcall是 Pascal方式清理C方式压栈,通常用于Win32 Api中,函数采用从右到左的压栈方式,自己在退出时清空堆栈。VC将函数编译后会在函数名前面加上下划线前缀,在函数名后加上"@"和参数的字节数。 int f(void *p) -->> _f@4(在外部汇编语言里可以用这个名字引用这个函数)在WIN32 API中,只有少数几个函数,如wspintf函数是采用C调用约定,其他都是stdcall

(2)C调用约定(即用 __cdecl关键字说明)(The C default calling convention)按从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于传送参数的内存栈是由调用者来维护的(正因为如此,实现可变参数 vararg的函数(如printf)只能使用该调用约定)。另外,在函数名修饰约定方面也有所不同。 _cdecl是C和C++程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。函 数采用从右到左的压栈方式。VC将函数编译后会在函数名前面加上下划线前缀。

(3)__fastcall调用的主 要特点就是快,因为它是通过寄存器来传送参数的(实际上,它用ECX和EDX传送前两个双字(DWORD)或更小的参数,剩下的参数仍旧自右向左压栈传 送,被调用的函数在返回前清理传送参数的内存栈),在函数名修饰约定方面,它和前两者均不同。__fastcall方式的函数采用寄存器传递参数,VC将 函数编译后会在函数名前面加上"@"前缀,在函数名后加上"@"和参数的字节数。

(4)thiscall仅仅应用于"C++"成员函数。this指针存放于CX/ECX寄存器中,参数从右到左压。thiscall不是关键词,因此不能被程序员指定。

(5)naked call。 当采用1-4的调用约定时,如果必要的话,进入函数时编译器会产生代码来保存ESI,EDI,EBX,EBP寄存器,退出函数时则产生代码恢复这些寄存器的内容。

综上,其实只有PASCAL调用约定的从左到右入栈的.而且PASCAL不能使用不定参数个数,其参数个数是一定的。


支持可变参数的__cdecl调用其实可以理解的。C方式入栈顺序从右往左,那么在栈底的元素就是可变参数的最右边一个,我们只需要知道所有明确参数里的最左边一个参数在栈中的位置,剩下到栈底的都是可变参数了,反之如果从左往右入栈,则无法知道最右边的可变参数在栈中的位置。在具体实现中,也可观察到其中的原理,包括,需要调用者手动清栈。

[cpp]  view plain copy
  1. float averge(int n_values, ...)  
  2. {  
  3.     va_list var_arg;  
  4.     //准备访问可变参数  
  5.     va_start(var_arg, n_values);//第一个参数是va_list变量的名字,第2个参数是省略号前最后一个有名字的参数  
  6.     //取值  
  7.     for(::)  
  8.         sum += va_arg(var_arg, int);//第二个参数是参数的类型  
  9.     //完成处理可变参数,手动清栈  
  10.     va_end(var_arg);  
  11. }  

结论很简单:如果支持可变参数的函数,那么参数进栈的顺序几乎必然是自右向左 的。并且,参数出栈也不能由函数自己完成,而应该由调用者完成。


函数参数计算顺序

主要想说明的是,函数的参数压栈顺序和参数计算顺序不是一个概念。一个函数带有多个参数的时,C++语言没有规定函数调用时实参的求值顺序。这个是编译器自己规定的。

比方说int z = add(++x,x+y);不同编译器可能产生不同结果。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值