请教关于C语言形参和实参存储单元的问题? 您所在的位置:网站首页 形参和实参占用存储单元 请教关于C语言形参和实参存储单元的问题?

请教关于C语言形参和实参存储单元的问题?

2024-02-26 18:49| 来源: 网络整理| 查看: 265

作者:灵剑 链接:https://www.zhihu.com/question/47514375/answer/106347643 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

首先我们限定一下问题,只限于cdecl的调用约定,函数没有被编译器做inline的优化(C++才有inline,但是C编译器也可能自己把函数调用优化掉)。除了cdecl以外,C中其他常用的调用约定包括stdcall和fastcall,C++中还有一个thiscall(用于调用类的成员函数)。fastcall会使用寄存器来传递一部分参数。stdcall除了返回时自动清理堆栈以外,与cdecl在使用参数上区别不大。thiscall调用约定使用寄存器传递this指针参数。pascal调用约定跟stdcall类似,但是参数入栈的顺序是反的。 在cdecl的调用约定下,所有参数从右往左入栈,都要占用存储空间。如果返回值大小超过eax范围,还要额外压一个返回值预留空间到堆栈里,然后从堆栈返回,否则从eax返回。从这个角度来说,所有的实参都必须要占用独立的空间。而且C语言也不支持传递引用作为参数。 纠结的在于传递数组作为参数这种情况,对编译器来说,实际上的参数是个指针,但是从代码形式上来看形参好像是个数组……这个我也不知道该怎么算,这简直是个哲学问题。如果从定义的角度上看,这时候参数类型是数组,那姑且算是你同学对吧,但是是有争议的。 不过,有另一个很好的理由可以选D:

如果传递的实参是个常量,这个常量并不会占用额外的空间。 比如说:

int f(int a); int main(){ f(1); return 0; }

这个1,在实际运行的时候并不会有额外的空间来存储,而是直接从指令中压一个1到堆栈里然后直接调用。这个时候显然不是“实参和形参各占用独立的存储单元”。 传递一个固定的指针作为实参的时候也是一样的:

int f(int *a); int main(){ int r = 0; f(&r); return 0; }

这个&r也不会有专门的存储单元去保存,而是直接通过bp + 固定偏移量的方式压进堆栈。

=======================================================================

感谢评论指出的问题,cdecl、stdcall、fastcall是x86中的调用约定,x64中已经不同了,查了一下相关的资料,在Windows上和非Windows上,使用的调用约定是有差异的。另外x86上gcc for linux和windows也是有差别的。 x86 calling conventions 可以参考wikipedia

简单来说,x64下Windows只有一种调用约定: 前四个整数(指针)参数按顺序使用RCX, RDX, R8, R9,前四个浮点参数按顺序使用XMM0, XMM1, XMM2, XMM3,剩余的参数从右往左进堆栈。另外,调用方在栈上额外分配32个字节(但是不需要初始化),给RCX、RDX、R8、R9四个参数,这样被调用的函数在需要使用这四个寄存器的时候可以把这四个参数直接存到堆栈里对应的位置上,腾出寄存器的空间。

gcc x64在Linux下则按照SystemV的调用约定: 前六个整数或指针类型使用RDI, RSI, RDX, RCX (Linux内核中使用R10),R8,R9浮点数使用XMM0,XMM1,XMM2,XMM3,XMM4,XMM5,XMM6和XMM7,没有额外的分配空间,剩余参数仍然是从右往左进栈。

使用寄存器传参数的时候,按照传统的占用存储的说法就不合适了,不过由于这些寄存器都是易失的,用于传参之后寄存器里原来的值必须先保存到堆栈上,也可以相当于占用了相应的存储空间,前面的讨论仍然是适用的。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有