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

函数参数的压栈顺序 对于一般的c++是从右边开始压栈,可以看两个例子: int arr[]={1,2,3,4 }; int *ptr=arr; PrintMN(*(++ptr),*ptr); ptr=arr; PrintMN(*ptr,*(++ptr)); void PrintMN(int m, int n) { qDebug()<<'m'<<m; qDebug()<<'n'<<n; } 上文在QT的输出结果是 m 2 阅读详情
昨天忽然想起来函数压栈顺序的问题,就跟舍友讨论了一下,结果发现在不同的编译器下面出现的结果竟然不一样!于是做了如下分析:

【源程序】

#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”。

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

【问题分析】
 1、Tc3下从压栈到函数调用阶段的汇编代码:(使用方法 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”的原因也就很清楚了。

    2、Vc.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.

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

C语言初探 之 printf压栈顺序 写在篇头: 在不同的编译器下结果不同,本例测试的环境为 devcpp5.3.0.1 。    ——————————————————————————————————————————————————————————————————————   先看一段代码,猜猜会是什么输出: x=1; printf("%d %d\n",x,x++); x=1; printf("%d %d\n",x 阅读详情

相关推荐

函数调用与栈

文章目录函数调用与栈内存栈调用函数前调用函数时关于寄存器%ebp栈帧函数调用将要结束时函数调用结束后注 函数调用与栈 内存栈   在C语言中函数的调用必须借助于栈。   关于栈是什么就不再做笔记了,但需要注意的是,这里的栈与数据结构中的栈虽然原理相同但并不是一个东西。在这里,栈就是一段计算机内存,只不过这段内存满足数据后进先出的规则。   另外还需要注意的是,内存栈是向下增长的,即栈顶在下、栈底在...

小腿跑乱刀的博客 3854

【C/C++】总结常用的函数调用约定

 一、函数调用的基本步骤 函数调用大致包括以下几个步骤。(1)参数入栈:将参数从右向左依次压入系统栈中。(2)返回地址入栈:将当前代码区调用指令的下一条指令地址压入栈中,供函数返回时继续执行。(3)代码区跳转:处理器从当前代码区跳转到被调用函数的入口处。(4)栈帧调整:具体包括:保存当前栈帧状态值,已备后面恢复本栈帧时使用(EBP入栈)。将当前栈帧切换到新栈帧(

guansir的专栏 1713

C语言函数调用及栈帧结构

一、地址空间与物理内存 (1)地址空间与物理内存是两个完全不同的概念,真正的代码及数据都存在物理内存中。 物理储存器是指实际存在的具体储存器芯片,CPU在操纵物理储存器的时候都把他们当做内存来对待,把他们看成由若干个储存单元组成的逻辑储存器,这个逻辑储存器就是我们所说的地址空间。 地址空间大小与逻辑储存器大小不一定相等。 (2)进程的地址空间分布 进程的地址空间包括:栈区(heap)、共享

qq_29403077的博客 1万+

函数调用过程中,函数参数的入栈顺序

函数调用过程中,第一个进栈的是(主函数中的)调用处的下一条指令(即函数调用语句的下一条可执行语句)的地址;然后是函数的各个参数,而在大多数C/C++编译器中,在函数调用的过程中,函数的参数是由右向左入栈的;然后是函数内部的局部变量(注意static变量是不入栈的);在函数调用结束(函数运行结束)后,局部变量最先出栈,然后是参数,最后栈顶指针指向最开始存的指令地址,程序由该点继续运行。

小爷我不嘚瑟 9472

关于C函数参数压栈顺序

下面列举了一些常见的编译器的调用约定 VC6:         调用约定        堆栈清除    参数传递         __cdecl                  调用者      从右到左,通过堆栈传递         __stdcall

G_Forever的专栏 1364

音频测试专题

麦克风与扬声器单体测试指标及整机性能测试概述 麦克风测试指标包括频率响应、灵敏度、指向性等10项关键参数,用于评估声音捕捉和信号转换性能。扬声器测试包含频率响应、灵敏度等12项指标,涉及声学特性和物理参数。整机性能测试覆盖安静、嘈杂和正常三种环境,重点检测麦克风底噪、清晰度、扬声器音量及通话质量等12项功能指标。两类单体测试与整机测试共同构成完整的音频设备质量评估体系,确保产品在不同场景下的性能表现。

weixin_52640906的博客 1317

函数参数压栈顺序

函数调用约定(Calling Convention) 函数调用约定不仅决定了发生函数调用时函数参数的入栈顺序,还决定了是由调用者函数还是被调用函数负责清除栈中的参数,还原堆栈。函数调用约定有很多方 式,除了常见的__cdecl,__fastcall和__stdcall之外,C++的编译器还支持thiscall方式,不少C/C++编译器还支持 naked call方式。这么多函数调用约定常常令许

u010278318的专栏 2533

ROS2实战:打造智能巡检机器人的关键技术与实现

本文详细介绍了基于ROS2和Navigation2构建智能巡检机器人的完整实战流程。通过设计主从协作的事件驱动架构,实现了机器人在已知地图中的自动导航、定点语音播报和环境图像采集功能。文章涵盖了从环境搭建、仿真配置到核心控制节点、语音服务与图像记录模块的代码实现,为开发者打造实用的自动巡检机器人提供了清晰的技术指南。

weixin_29031057的博客 223

C/C++ 函数压栈方式

C/C++ 函数压栈方式 一,不同关键字,系统压栈方式 1,如果函数func是__cdecl(VC下的默认调用方式),调用时情况如下   int   main()    {    //参数从右到左压栈    push   4    push   3    push   2    push   1    call   func    add   esp  

zuihoudebingwen的专栏 1778

IEDA激活码失效?教你5分钟快速续期的方法

本文针对IEDA激活码失效问题,提供了一套从快速应急到深度排查的完整解决方案。文章不仅详解了5分钟快速续期与清理缓存等应急方法,更深入剖析了授权机制、离线激活流程,并指导如何构建可持续的授权管理策略,帮助开发者彻底解决激活困扰,确保开发环境稳定。

a0b1c2d3的专栏 273

关于对鱼眼相机图片进行畸变校正的两种思路

鱼眼畸变主要表现为径向畸变(Radial Distortion),图像中心正常,边缘向外拉伸。畸变模型通常基于入射角θ\thetaθ(光线与光学轴夹角)和图像半径rrr的关系。等距投影(Equidistant Projection)rdfθrd​fθ,其中rdr_drd​为畸变图像半径,fff为焦距,θ\thetaθ为入射角。等立体角投影(Equisolid Angle Projection)rd2fsin⁡θ2rd​2fsinθ/2。

qq_45670407的博客 1546

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

函数调用时参数压栈顺序的问题(转) 收藏 昨天忽然想起来函数压栈顺序的问题,就跟舍友讨论了一下,结果发现在不同的编译器下面出现的结果竟然不一样!于是做了如下分析:【源程序】 #include void print(int a, int b, int c){    printf("%d/t%d/t%d/n", a, b, c);}int main(void){    int

天河水 3162

在虚拟机上搭建FTP服务器

chroot_list_file=/etc/vsftpd/chroot_list # 对chroot_list下用户限制。[root@localhost ~]# vim /etc/vsftpd/vsftpd.conf #打开配置文件。# 指定用户的shell: -s /usr/bin/nologin。# 在配置文件中找到以下几行代码, 去掉注释(去掉 "#"号)# 指定用户主目录:/data/ftp/ftpuser -M。(2)修改配置文件,路径/etc/vsftpd/ 下的文件。

weixin_70753380的博客 3209

C++函数调用的压栈过程

需要注意的是,栈的管理是由编译器和操作系统来完成的,具体的实现可能有所不同。C++函数调用的压栈过程是指在函数调用时,将函数的相关信息和局部变量等数据存储在栈(Stack)上的过程。上述示例展示了函数调用的压栈过程,包括参数的传递、局部变量的分配和返回地址的保存。在函数调用之前,将下一条指令的地址(函数调用后执行的下一条指令)压入栈中,以便函数执行完后能够返回到正确的位置。函数调用完成后,会按照相反的顺序将栈上的数据出栈,恢复到调用函数的状态,并将控制权返回给调用函数。函数,按照从右到左的顺序压入栈中。

weixin_42492218的博客 2176

PyTorch强化学习实战(3)——Gymnasium API扩展功能

我们已经学习了编写智能体必需的 Gymnasium 核心功能,其余 API 功能部分虽然非必需,但能显著提升开发效率和代码整洁度。在本节中,我们简要地介绍了 Gymnasium API 的扩展功能,学习了如何以模块化的方式扩展现有环境的功能,并熟悉了使用包装器可视化智能体活动的方法。

盼小辉丶的博客 1198

【SpringBoot 4.x 第70节】夯爆了,企业级 API 设计规范落地:状态码、错误码、接口文档与兼容性(超详解)!

🏆 本文收录于 《滚雪球学 Spring Boot 4.x》 专栏。本专栏面向 有一定 Java 基础,但尚未系统学习 Spring Boot 的读者,采用“滚雪球式学习法”:先跑通、再理解、再重构、再上线,带你从第一个 Hello API 开始,逐步完成一个可部署、可监控、可扩展的后端项目。

**My Coding Family** 131

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

要回答这个问题,就不得不谈一谈printf()函数,printf函数的原型是:printf(const char* format,…) 没错,它是一个不定参函数,那么我们在实际使用中是怎么样知道它的参数个数呢?这就要靠format了,编译器通过format中的%占位符的个数来确定参数的个数。

jiange_zh的博客 1万+

基于STM32单片机设计的智能坐垫_431

本文设计了一款基于STM32F103C8T6单片机的智能坐垫系统,该系统集成了压力传感器、SHT30温湿度传感器、薄膜加热垫、涡轮风机、语音播报模块、OLED显示屏及HC-05蓝牙模块,实现了对用户就坐状态与坐垫微环境的实时监测与智能调控。系统通过压力传感器检测用户就坐情况,自动启动久坐计时并触发语音提醒;温湿度数据用于控制通风与加热功能,以改善座椅舒适性;用户可通过按键或手机APP设置久坐时长、手动开关通风与加热,并通过OLED屏或APP实时查看环境参数与计时状态。本设计体现了嵌入式系统在健康关怀与舒适性

409

【C语言入门】函数参数压栈顺序的底层原理与实践

本文深入探讨了C语言中函数参数压栈的顺序,特别是“从右到左”的规则。文章首先介绍了栈的基本概念及其在函数调用中的作用,包括栈的物理结构和函数调用的栈帧。接着,详细解释了参数压栈顺序的定义和规则,以及为何采用“从右到左”的顺序,主要是为了支持可变参数函数。通过汇编代码的实验验证,文章展示了参数压栈顺序的实际表现,并区分了压栈顺序与参数求值顺序的不同。此外,文章还讨论了不同编译器在压栈顺序上的一致性与差异,以及压栈顺序对编程的实际影响,如调试栈溢出、编写可变参数函数和逆向工程。

Mr_-G,一名勤奋底层软件开发工程师的博客 1249

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

c语言函数参数压栈,函数调用压栈 浅谈C语言函数调用参数压栈的相关问题

想了解浅谈C语言函数调用参数压栈的相关问题的相关内容吗,在本文为您仔细讲解函数调用压栈的相关知识和一些Code实例,欢迎阅读和指正,我们先划重点:函数调用压栈,下面大家一起来学习吧。参数入栈的顺序以前在面试中被人问到这样的问题,函数调用的时候,参数入栈的顺序是从左向右,还是从右向左。参数的入栈顺序主要看调用方式,一般来说,__cdecl 和__stdcall 都是参数从右到左入栈。看下面的代码:#...

weixin_29070721的博客 692

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

参数压栈顺序

c/c++可变参数用法原理 最近看一些linux底层实现,随手记录点东西。 用法 没啥好说的 void foo(const char *name, …) { va_list args; char buff[1024]; va_start(args, name); vsnprintf(buff, 1024 name, args); va_end(args); } 原理 先复习下c/c++参数压栈,看...

swdszzh的博客 1072

c语言函数调用 压栈,局部变量与函数调用栈

不知道从什么时候开始,运行在OS内的程序文件,有了规范,Win系统下是PE,Linux系统下是ELF。符合规范的程序能够被OS调用并执行。不知道从什么时候开始,程序在OS的管控下执行,每个进程都有自己的堆(heap)和栈(stack)。堆的地址从小到大,存放需要程序代码显式申请的内存块,并且也需要程序自己管理和释放。栈的地址从大到小,存放函数调用过程中的指令地址和函数的局部变量。程序员需要关注堆的...

weixin_36155754的博客 1211

函数调用时参数是如何从右至左入栈的

在C++(以及C语言)中,函数调用时参数的入栈顺序通常是从右至左的。这一规则主要受到函数调用协议(Calling Convention)和编译器实现的影响。

2402_84885073的博客 1317

【C语言】printf函数参数压栈问题

现在我们假设参数的压栈顺序是从左到右的,这时,函数调用的时候,format最先进栈,之后是各个参数进栈,最后pc进栈,此时,由于format先进栈了,上面压着未知个数的参数,想要知道参数的个数,必须找到format,而要找到format,必须要知道参数的个数,这样就陷入了一个无法求解的死循环了。而如果把参数从右到左压栈,函数调用时,先把若干个参数都压入栈中,再压format,最后压pc,这样一来,栈顶指针加2便找到了format,通过format中的%占位符,取得后面参数的个数,从而正确取得所有参数。

瞌睡的博客 1796

函数调用函数的压栈

1 _cdecl    按从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于“C”函数或者变量,修饰名是在函数名前加下划线。对于“C++”函数,有所不同。如函数void test(void)的修饰名是_test;对于不属于一个类的“C++”全局函数,修饰名是?test@@ZAXXZ。           这是MFC缺省调用约定。由于是调用者负责把参数弹出栈,所以可以给函数定义个数不定的参数,如

LD的专栏 2212

【小记备忘】之C/C++函数调用约定,函数参数压栈顺序【2013.12.09】

【小记备忘】之C/C++函数调用约定,函数参数压栈顺序【2013.12.09】 欢迎加入我们的QQ群,无论你是否工作,学生,只要有c / vc / c++ 编程经验,就来吧!158427611  问题引申: int arr[]={6,7,8,9,10}; int *ptr=arr; *(ptr++)+=123; printf("%d,%d",*ptr,*(++ptr));

木心[ mx1024 ] 欢迎 1257
上一篇: C、C++中指针加 1 的问题
下一篇: 下面的程序运行时哪里会出现错误
evers
博客等级 码龄24年 7粉丝 4原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值