编程经验点滴(二)——《C、C++中函数调用时参数压栈的顺序问题》

原创 2004年10月24日 11:53:00


编程经验点滴(二)

函数调用时参数压栈顺序的问题
<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" />

2004-10-24

作  者:杨延庆
E-mail:blankmanATtomDOTcom
出  处:http://blog.csdn.net/blankman/archive/2004/10/24/programing_experience2.aspx



    
昨天忽然想起来函数压栈顺序的问题,就跟舍友讨论了一下,结果发现在不同的编译器下面出现的结果竟然不一样!于是做了如下分析:

【源程序】<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

#include <stdio.h>


void
print(int a, int b, int c)
{
    printf("%d/t%d/t%d/n", a, b, c);
}

int main(void)

{

    int p = 0;

    print(p++, ++p, p++);
     return0;

}

【调用分析】
    其实大家应该都知道这个顺序了,我简单粗略的提一下吧。
    C
C++ 编译器进行函数调用的时候参数压栈的顺序时从右向左的。拿上面的程序来说,首先压栈的参数是最后一个“p++”,压栈的值应该是0,此时 p=1;然后压栈参数的是中间的“++p”,压栈的值是2,此时 p=2;最后压栈的是第一个“p++”,压栈的值应该是 2,此时p=3
    
那么,输出结果就应该是“2 2 0”。

【存在问题】
    
TcTc3GCC 下编译通过,运行结果“2 2 0”;
    
VC.net 下编译通过,运行结果“2 3 0”;

【问题分析】
 1Tc3下从压栈到函数调用阶段的汇编代码:(使用方法 tcc -S filename.cpp

   ;    

   ;     {

   ;             int p = 0;

   ;    

       xor        si,si    ; si 清零,即 p=0

   ;    

   ;     print(p++, ++p, p++);

   ;    

       ;将最后一个参数压栈,先压栈,后加1
       mov        ax,si    ;ax=0

       inc        si

       push       ax

       ;将中间的参数压栈,先加1,后压栈
       inc        si

       mov        ax,si    ;ax=2

       push       ax

       ;将第一个参数压栈,先压栈,后加1
       mov        ax,si    ;ax=2

       inc        si

       push       ax

       ;调用 print 函数
       call       near ptr @print$qiii

 

    至此,在 Tc Tc3 下的分析工作完成,结果是“2 2 0”的原因也就很清楚了。

    2Vc.net下从压栈到函数调用阶段的汇编代码:
;
;int p = 0;
;
00411B3E     mov dword ptr [p],0               ;
赋初值 0,即 p=0
;
;print(p++, ++p,p++);
;
;这里涉及到了一些地址和寄存器,可能有的读者读不懂,把他们理解为变量就行了
;
;文中共出现 3 个地址 [p] [ebp-0D0h] [ebp-0D4h] , ebp 和 p 在段程序中的值
;没有改变,所以地址也可以理解为一个变量。下面对一个地址进行解释
;    mov dword ptr [p],0 就是把 p 地址处的内存赋值为 0,赋值单元数 dword
;
;文中还有一些寄存器,只是进行运算的时候使用,可以理解为临时变量
;
;操作第三个参数
00411B45     mov    eax,dword ptr [p]            ;eax = 0
eax = p
00411B48     mov    dword ptr [ebp-0D0h],eax     ;
存放最第三个参数值
00411B4E     mov    ecx,dword ptr[p]             ;ecx = 0 即 ecx = p
00411B51     add    ecx,1                        ;ecx = 1
00411B54     mov    dword ptr [p],ecx            ;p   = 1 即 p = ecx
;
操作第二个参数
00411B57     mov    edx,dword ptr [p]            ;edx = 1 即 edx = p
00411B5A     add    edx,1                        ;edx = 2
00411B5D     mov    dword ptr [p],edx            ;p   = 2 即 p = edx
00411B60     mov    eax,dword ptr[p]             ;eax = 2 即 p = eax
00411B63     mov    dword ptr [ebp-0D4h],eax     ;
存放第二个参数值
;
操作第一个参数
00411B69     mov    ecx,dword ptr[p]             ;ecx = 2 即 ecx = p
00411B6C     add    ecx,1                        ;ecx = 3
00411B6F     mov    dword ptr [p],ecx            ;p   = 3 即 p = ecx
;
开始压栈
00411B72     mov    edx,dword ptr [ebp-0D0h]     ;
第三个参数压栈,值为 0
00411B78     push   edx
00411B79     mov    eax,dword ptr [p]            ;
第二个参数压栈,值为3
00411B7C     push   eax
00411B7D     mov    ecx,dword ptr[ebp-0D4h]      ;
第一个参数压栈,值为 2
00411B83     push   ecx
;
调用 print 函数
00411B84     call   print(41100Fh)
00411B89     add    esp,0Ch
   
    
至此,在 Vc.net 下的分析工作完成,结果是“2 3 0”的原因是压栈的顺序不是标准 C 语言中约定的压栈的顺序,或者说根本就不是顺序,至于为什么这样我也不清楚,如果 .Net 的开发人员注意到了这个问题的话回个贴吧(痴人说梦^_^)。

    在C99中找到相应的建议了,是没有明确的规定压栈的顺序。

    The order of evaluation of the function designator, the actual arguments, and subexpressions within the actual arguments is unspecified, but there is a sequence point before the actual call.

    EXAMPLE In the function call (*pf[f1()]) (f2(), f3() + f4()) the functions f1, f2, f3, and f4 may be called in any order. All side effects have to be completed before the function pointed to by pf[f1()] is called.

【建议】
    
为了你的程序的通用性,不要写那些模棱两可的程序,尽管有很多考试都考这些,我个人不认为把程序写成这样是高手。



上一篇:编程经验点滴(一)——《定义字符串的问题(2004-10-13)》
下一篇:编程经验点滴(三)——《C、C++中指针加 1 的问题(2004-11-23)》

声明:原创,版权所有,如需转载请注明出处。
http://blog.csdn.net/blankman/archive/2004/10/24/programing_experience2.aspx
 

c/c++的函数参数压栈顺序

c/c++的函数参数压栈顺序. 曾看到一篇文章上面说:c/c++参数压栈顺序是从右到左,pascal参数压栈是从左到右.为了这句话丢了很多次人.无所谓了,反正咱脸皮厚.总结一下: 编译出来的c...
  • black2360
  • black2360
  • 2017年04月18日 16:00
  • 588

编程经验点滴(二)——《C、C++中函数调用时参数压栈的顺序问题》

编程经验点滴(二)函数调用时参数压栈顺序的问题2004-10-24作  者:杨延庆E-mail:blankmanATtomDOTcom出  处:http://blog.csdn.net/blankma...
  • blankman
  • blankman
  • 2004年10月24日 11:53
  • 1941

谈谈C语言中的序列点(sequence point)和副作用(side effects)

网上关于序列点的介绍很多,参考几篇,做个总结。在C99标准文件5.1.2.3讲到了序列点问题,序列点的定义是一个程序执行中的点,这个点的特殊性在于,在这个点之前语句产生的所有副作用都将生效,而后面语句...
  • tsroad
  • tsroad
  • 2015年11月14日 10:58
  • 2064

C语言函数参数压栈顺序为何是从右到左?

要回答这个问题,就不得不谈一谈printf()函数,printf函数的原型是:printf(const char* format,…) 没错,它是一个不定参函数,那么我们在实际使用中是怎么样...
  • jiange_zh
  • jiange_zh
  • 2015年08月09日 23:19
  • 2643

函数调用时参数压栈顺序的问题(转)

昨天忽然想起来函数压栈顺序的问题,就跟舍友讨论了一下,结果发现在不同的编译器下面出现的结果竟然不一样!于是做了如下分析:【源程序】 #include stdio.h>void print(int a,...
  • evers
  • evers
  • 2004年11月24日 11:29
  • 2897

sizeof对于a bit-field, a void expression, or a function designator无意义

看下C标准吧 C89 specified that sizeof’s operand can be any value except a bit-field, a void expression, ...
  • as_314159265
  • as_314159265
  • 2011年11月16日 20:18
  • 387

Lisp函数apply和funcall的比较

CommonLisp提供了两个函数来通过函数对象来调用函数:Apply和Funcall函数。 得到函数对象的方法是#‘,其实就是FUNCTION函数。 例如: (foo  1 2 3) == (fu...
  • xiaojianpitt
  • xiaojianpitt
  • 2012年08月06日 11:41
  • 10153

C语言中函数参数压栈方式为什么是从右到左的?

从论坛回答中摘出3个比较有说服力的观点: 一、 先通过一个小程序来看一看: #include void foo(int x, int y, int z) { printf("x = %...
  • own_ss
  • own_ss
  • 2016年06月01日 22:45
  • 2062

C语言函数调用时参数压栈的顺序以及函数指针的作用

1、函数参数压栈的顺序 很多人都知道压栈的顺序时从右向左进行压栈,具体的可观测的结果如下程序运行。我们都知道Pascal的参数入栈顺序时自左向右的,但是为什么C语言会选择自右向左呢?这也是C语言比pa...
  • geniusluzh
  • geniusluzh
  • 2012年06月19日 01:55
  • 5716

C语言入门(6)——C语言常用数学函数

在编码过程中会经遇到数学运算,幸运的是C语言提供了非常丰富的数学函数库。在数学中使用函数有时候书写可以省略括号,而C语言要求一定要加上括号,例如sin(pi/2)这种形式。在C语言的术语中,pi/2是...
  • yincheng01
  • yincheng01
  • 2014年06月24日 16:46
  • 1330
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:编程经验点滴(二)——《C、C++中函数调用时参数压栈的顺序问题》
举报原因:
原因补充:

(最多只允许输入30个字)