关于

本文涉及到代码,演示环境为:win10 + VS2017 ,ubuntu+clang

clang版本:

参数入栈顺序

顺序

几种常见的函数参数入栈顺序,还有两种就不介绍了(__clrcall、__thiscall)

顺序 释义
__cdecl 函数参数按照从右到左的顺序入栈,并且由调用函数者把参数弹出栈以清理堆栈
__stdcall 函数参数按照从右到左的顺序入栈,被调用的函数在返回前清理传送参数的栈,函数参数个数固定
__fastcall 使用内部寄存器ECX,EDX传递前两个DWORD 或者size更小的参数
__fastcall用的很少。 通常情况下: c/c++默认入栈方式:__cdel。Windows api使用的是__stdcall方式。

自定义参数入栈形式

可以自定义函数的入栈顺序。 常用形式如下

函数返回值  入栈规则  函数名(参数类型 参数名);

一个例子

int __cdecl get_name_index(const std::string& str_name);

为什么要从右往左入栈?

  • 结论:为了确定函数参数个数。大胆猜想:统一定长参数函数和不定长参数函数处理函数入栈顺序。
  • 有的函数参数个数是确定的,比如上面的函数,就一个参数,有的函数参数是不定长,学过C的都知道printf和scanf,当然也可以自定义变长参数,c++11引入了可变长参数。
  • 定长参数的函数,当然,每个参数都有自己的地址,很容易就拿到了,but, 不定长参数呢,怎么知道每个参数的地址和压入多少个参数? 比如printf和scanf函数,第一个参数就很特别,一个格式化的字符串。printf是怎么知道有多少个参数呢? 通过格式格式字符串个数确定。有多少个格式字符串,就需要多少个参数

注意: 栈是先进后出,俗称后来居上。

  • 显然,定长函数参数就不需要分析了,每个参数都有自己的地址,能确定下来,从左往右 对比 从右往左没有区别。
  • 那不定长函数参数呢? 以 printf函数为例,
int a = 1;
int b = 2;
printf("%d, %d", a, b);

情况A: 从左往右。 那么上面的代码,先入栈的是"%d, %d", 再是a, 最后是b。printf需要知道压入了多少个参数,就需要检查格式化字符串"%d, %d", 但是,这个参数被压入了栈底。栈顶是b。肉眼当然知道压入了多少个参数,编译器需要通过判断条件才能知道,判断条件被压入栈底,这时,编译器就无法知道格式化字符串了,也就不知道参数个数了。

情况B:从右往左。 那么上面的代码,先入栈的是b,再是a,最后是"%d, %d". printf先检查第一个参数,栈顶是"%d, %d", 就可以知道压入了多少个参数了。

参数计算顺序

这个和编译器有关。不同编译器可能也相同。

一定要知道

Note: 期望输出 和 参数计算顺序 息息相关。

为什么? 代码参数的计算顺序决定了实际输出,这与期望输出是两码事。 而期望输出是主观预测输出结果。

我将使用下面的测试代码,观察VS和clang两种编译器的参数计算顺序。

int a = 2;
printf("%d, %d, %d", a, (a = (a + 2)), (a = (a + 3)));

win10 + VS2017

使用VS2017输出下面的代码,可知计算顺序。可自己先计算下输出结果是什么....

正确输出: 7, 7, 7

分析(个人理解): printf("%d, %d, %d", a, (a = (a + 2)), (a = (a + 3))); 可以拆解为下面的代码:

// printf("%d, %d, %d", a, (a = (a + 2)), (a = (a + 3)));
a = a + 3;
a = a + 2
a

printf函数先将表达式的结果计算出来,得到结算结果,再将计算结果压入栈。先计算a=a+3;, 此时, a的值从2->5, 同理,a=a+2;后,a的值从5->7。当执行函数printf时,变成了

printf("%d, %d, %d", 7, 7, 7);
  • 按照上面的分析方法,再来一个例子
int a = 2;
int b = 3; printf("%d, %d, %d", a, a = (a+b), b = (b-a));

输出结果

printf("%d, %d, %d", a, a = (a+b), b = (b-a));
// 等效下面的方式
1. b = b - a;// b = 3-2; b = 1
2. a = a +b; // a = 2 + 1; a = 3
3. printf("%d, %d, %d", 3, 3, 1);

ubuntu + clang

自己先计算结果,再看答案。

clang输出结果



可见,clang的计算顺序是从左至右,而VS的计算顺序是从右至左。

为什么clang的输出结果是这样的? 大胆猜想(个人意见)先计算a, 将a单独一个副本,再计算 a=a+2, 结果为4,再将a=a+2的结果4保存到另一个副本中,最后计算a=a+3=4+3 = 7,将(a=a+3)拷贝到下一个副本中。

结论

应该避免上述情况,即可实现同样的表达式,可以实现在不同的平台得到同样的结果。

踩坑

这点倒是深有体会,自己写公共服务模块时,就因为上面的情况出现了一些困扰。

c++参数入栈顺序和参数计算顺序的更多相关文章

  1. c语言中函数参数入栈的顺序是什么?为什么

    看到面试题C语言中函数参数的入栈顺序如何? 自己不知道,边上网找资料.下面是详细解释 #include <stdio.h> void foo(int x, int y, int z){   ...

  2. x86汇编寄存器,函数参数入栈说明

    https://en.wikipedia.org/wiki/X86_calling_conventions

  3. C/C++多参数函数参数的计算顺序与压栈顺序

    一.前言 今天在看Thinking in C++这本书时,书中的一个例子引起了我的注意,具体是使用了下面这句 单看这条语句的语义会发现仅仅是使用一个简单的string的substr函数将所得子串pus ...

  4. 函数调用过程中,函数参数的入栈顺序,why?

    C语言函数参数入栈顺序为从右至左.具体原因为:C方式参数入栈顺序(从右至左)的好处就是可以动态变化参数个数.通过栈堆分析可知,自左向右的入栈方式,最前面的参数被压在栈底.除非知道参数个数,否则是无法通 ...

  5. 有关C/C++中,表达式计算顺序的问题,以及表达式内部变量“副作用”问题(转)

    经常可以在一些讨论组里看到下面的提问:“谁知道下面C语句给n赋什么值?”m = 1; n = m+++m++;最近有位不相识的朋友发email给我,问为什么在某个C++系统里,下面表达式打印出两个4, ...

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

    整理日:2015年3月18日 为了这句话丢了很多次人.无所谓了,反正咱脸皮厚. 总结一下 编译出来的c/c++程序的参数压栈顺序只和编译器相关! 下面列举了一些常见的编译器的调用约定 VC6 调用约定 ...

  7. C语言函数参数压栈顺序为何是从右到左?(从左向右的话,碰到printf的会陷入死循环)

    上学期学习了汇编语言,并在操作系统实验中使用了汇编+C语言混合编程,中间也了解了一些C语言与汇编语言的对应关系. 由于汇编语言是底层的编程语言,各种函数参数都要直接控制栈进行存取,在混合编程中,要用汇 ...

  8. printf函数对参数的计算顺序

    没想到啊,没想到: printf函数对参数的计算顺序是从右往左的! 我不禁想问一句,这么坑爹的事情,书里居然没有写过.还是我看书不仔细,没有找到?(回头,在自己翻翻那本c语言编程) 于是下面的程序结果 ...

  9. c语言中printf()函数中的参数计算顺序

    今天看到了一个关于printf()函数计算顺序的问题,首先看一个例子: #include<stdio.h> int main() { printf("%d---%d---%d&q ...

随机推荐

  1. Python基础笔记4

    模块 模块是一组Python代码的集合,一个.py文件就称之为一个模块(Module),按目录来组织模块称为包(Package).优点:提高了代码的可维护性:避免函数名和变量名冲突. mycompan ...

  2. Can't connect to HTTPS URL because the SSL module is not available. - skipping

    今天用pip3安装第三方库的时候报了这样一个错: Can't connect to HTTPS URL because the SSL module is not available. - skipp ...

  3. C++你不知道的事

    class A { public: A() { cout<<"A's constructor"<<endl; } virtual ~A() { cout&l ...

  4. 出现NoClassDefFoundError,始终无法引入jar的解决

    在拉取代码后,项目的部分版本与本地存在的不一定一致,所以IDEA会自动下载并引入,但是在启动时可能存在java.lang.NoClassDefFoundError这个报错 比如引入marshallin ...

  5. Volatile的3大特性

    Volatile volatile是Java虚拟机提供的轻量级的同步机制 3大特性 1.保证可见性 当多个线程同时访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值 案例代码 ...

  6. CSS3实现字体描边

    CSS3实现字体描边的两种方法 -webkit-text-stroke: 1px #fff;:不建议,向内描边,字体颜色变细,效果不佳: 用box-shadow模拟描边,向外描边,保留字体粗细,赞! ...

  7. javaSE高级篇1 — 异常与多线程基础

    1.异常的体系结构  注:Throwable是一个类,不是一个接口,这个类里面是描述的一些Error和Exception的共性,如图所示: 异常 / 错误是什么意思? 定义:指的是程序运行过程中,可能 ...

  8. Elaticsearch(一)--基础原理及用法

    一.基础概念 1.Elasticsearch简介 Lucene是Java语言编写的全文(全部的文本内容进行分析,建立索引,使之可以被搜索)检索引擎工具包(全文检索引擎的架构),用于处理纯文本的数据,提 ...

  9. 【Git项目管理】分布式 Git - 分布式工作流程

    分布式 Git - 分布式工作流程 你现在拥有了一个远程 Git 版本库,能为所有开发者共享代码提供服务,在一个本地工作流程下,你也已经熟悉了基本 Git 命令.你现在可以学习如何利用 Git 提供的 ...

  10. SpringMVC responseBody注解分析

    @responsebody表示该方法的返回结果直接写入HTTP response body中一般在异步获取数据时使用,在使用@RequestMapping后,返回值通常解析为跳转路径,加上@respo ...