关于c/c++的各种编译器的调用约定(更新中)

23 篇文章 0 订阅
23 篇文章 0 订阅

调用约定

简述:

–通过在不同的IDE里编译程序,观察各个IDE的调用约定的细节与差别。

一、压栈规则/调用约定:

首先,在c/c++中,printf的本质是一个在<stdio.h>中所具有的函数,所以实际上里面的一些参数的传递,实际上也是遵守编译器所自使用的调用约定的。

在这里,我们举一个例子:
ps:这里的博客实际上参考了https://blog.csdn.net/qq1184810369/article/details/14323555?locationNum=15这篇博客,在此基础上有了一些拓展的思考和研究

int i=3;
printf("%d,%d,%d,%d,%d,%d,%d",i++,++i,i--,--i,i--,i--,i+5);

按照我们常规的思路,根据自增运算符的规则,这句printf应该会在输出其中的每个自增表达式之前或者之后将对应的变量i进行改变,所以结果应该是(3,5,5,3,3,2,6)

但实际上,我们在如devc(codeblocks)中运行时:

在这里插入图片描述

在vs2019中运行:

在这里插入图片描述

我们看到输出的数据是以一种很诡异的如“0,1,0,1,2,3,6”,和“0,1,0,1,2,3,8”的形式输出的。如果是正常的思路的话怎么想都不对。

那么这里我们提到一个词–函数的调用约定即函数的参数传递

函数的参数传递大体有一、传给寄存器,二、传入栈中。这些参数的传递根据编译器的不同有不同的传递方式,根据实际经验对于c/c++来说,将参数入栈是比较常见的作法(还有如指针和c++的引用的地址入栈)。并且入栈顺序也有不同,如c/c++的许多ide是按参数从右向左入栈,也有从左向右入栈,还有的编译器或者解释器是按更为复杂的入栈方式。

那么这里联想到前面所说的,printf()的本质其实就是一个函数,那么我们所给它的那些信息也就会被入栈。而这里也不难看出,因为栈的传递规则是FILO(先入后出),所以对于我们的表达式,若要从左到右输出数据,那么我们就要从右向左将数据入栈才可以。

而在这里,我们虽然知道了这些参数是从右向左入栈的,但是还是不能解释为什么输出的是那些东西。那么这里就提到一个问题:

我们所传入的是什么?

具体问题具体分析,我们这里的代码如果按照参数传递的规则来的话,传递的应该是:“%d,”,i++,++i,i–,--i,i+5,(这里先不看前面的字符串),很容易看到的一点是,我们所传入的不单单可能只是一个值,这里传入的都是如i++(i=i+1),i+5这样的 运算表达式

而这里涉及到一个知识点,既然传入的参数是一些表达式,那么表达式肯定是要经过计算的,但这里的计算分为两种情况,一种是再传入之前,因为编译器有相关的调用约定,所以是将表达式计算完之后直接将值入栈。另一种是直接将表达式入栈,而后函数提取出来再进行运算。

并且,对于codeblocks,vs,devc(devc和codeblocks的调用约定未发现差别)来说,有如下规则:

1、printf()操作分两步完成:

第一步:参数入栈:

在入栈时,各种变量运算进行执行。

第二步:参数出栈:

在出栈时,输出栈中的结果,如果栈中压入的是变量,则输出变量本身的值,如果压入的计算公式,则需要重新计算(对VS的"i+常量"而言),而如果压入的是数值,则将该数值输出。

2、printf()压栈规则:

后入栈,也就是参数从右往左入栈。

3、前置加加与后置加加的区别:

前置操作压栈时,压入的是变量;后置操作压栈时,压入的是常量(即运算结果)。

前置操作在压栈时,已经进行了前置运算。也就是说,对于++i,压栈时,i已经完成了自加,并且,压入栈的是i本身,而不是i的值。

后置操作在压栈后,相关变量才完成后置操作运算。也就是说,对于i++,压栈时,i并没有完成自加,并且,压入栈的是i的值,而非i变量本身。

4、VS与CodeBlock/devc中,i+常量操作的处理:

在VS中“i+常量”操作在压栈时,压入的是“i+常量”运算,此处的i是变量。

在CodeBlocks/devc中,“i+常量”操作在压栈时,压入的是“i+常量”的运算结果,压入的是数值。

因此,我们对于之前输出结果的疑惑可以有了解答:

在codeblocks/devc中,因为所有表达式都是在完成运算之后再压栈,对于这个情况也就是说,printf里所要输出的值,从右向左进行计算,i+5=>3+5=8,入栈, i–=>3入栈,3=3-1…输出…

而vs因为其调用约定对于i+5不同于前面的自增和自减,压入的是i+5这个表达式,也就是我们说的,在函数调用到这个值的时候再进行表达式的运算,那么此时printf里面的i经过前面的运算已经如输出所见,i=3这个变量先经过实际编译器的处理,i–,i–,--i,i–,++i,i++等操作,那么就是=>3入栈,3-1=2,2入栈,2-1=1,1-1入栈,1-1=0…0入栈,0+1=1。而最后我们的输出也就是将这些数据一个一个出栈,并且最后在i=1的时候,进行i+5的操作,输出6。

ps:这里提到的那篇博客真的很厉害,思路十分清晰,讲得十分明白,这里的一些论述也引用了那篇博客,推荐大家关注一波。

---------------------------------------更新
这里我加了ida对代码的分析,发现实际情况跟想象中的不太一样。
devc:

在这里插入图片描述vs:
在这里插入图片描述cc的编译方法在处理printf里的表达式时,从右往左先对表达式进行了处理,将结果计算好了放在内存单元里,之后由printf函数进行调用。

但vs的编译方法是将变量i存储起来后,之后的每一步运算都直接对i进行操作,再放入对应的内存单元(后置++操作是先放入内存单元再对i操作),当所有运算完成之后,再同样按从右到左的顺序入栈。

因此,用dev(cb)跑出来的结果,i+5=8,实际上就是3+5=8,而vs输出的i+5=6则某种意义上就是"i+5=6".

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值