C语言的变长参数

C语言的变长参数原理

 

引言: 在看《程序员的自我修养:第十一章运行库-11.2.2C语言标准库》时讲到C语言的变长参数以及cdecl,

所以搜集一些资料帮助理解。

 

X86调用约定- 维基百科,自由的百科全书

https://zh.wikipedia.org › zh-hans

 cdeclstdcall的区别- 简书

https://www.jianshu.com › ...

关于调用约定(cdeclfastcallstcallthiscall) 的一点知识- ...

https://www.laruence.com › 2008/.../...

 c语言如何判断一个声明到底是函数还是指针还是数组 ... - 知乎

https://www.zhihu.com › question

https://cdecl.org/

 

变长参数函数:

Int printf(const char* format, …);

cdecl:

一种X86的调用约定,和语言没有关系。变长参数实现的基础是cdecl

cdecl(C declaration,即C声明)是源起C语言的一种调用约定,也是C语言的事实上的标准。在x86架构上,其内容包括:

  1. 函数实参在线程栈上按照从右至左的顺序依次压栈。
  2. 函数结果保存在寄存器EAX/AX/AL中
  3. 浮点型结果存放在寄存器ST0中
  4. 编译后的函数名前缀以一个下划线字符
  5. 调用者负责从线程栈中弹出实参(即清栈)
  6. 8比特或者16比特长的整形实参提升为32比特长。
  7. 受到函数调用影响的寄存器(volatile registers):EAX, ECX, EDX, ST0 - ST7, ES, GS
  8. 不受函数调用影响的寄存器: EBX, EBP, ESP, EDI, ESI, CS, DS
  9. RET指令从函数被调用者返回到调用者(实质上是读取寄存器EBP所指的线程栈之处保存的函数返回地址并加载到IP寄存器)

事实上C++语言默认也是cdecl, 下面的汇编可以清晰的看到调用者完成了清理栈操作

 

 

补充stdcall

stdcall是由微软创建的调用约定,是Windows API的标准调用约定。非微软的编译器并不总是支持该调用协议。GCC编译器如下使用:

int __attribute__((__stdcall__ )) func()

stdcall是Pascal调用约定与cdecl调用约定的折衷:被调用者负责清理线程栈,参数从右往左入栈。其他各方面基本与cdecl相同。但是编译后的函数名后缀以符号"@",后跟传递的函数参数所占的栈空间的字节长度。寄存器EAX, ECX和EDX被指定在函数中使用,返回值放置在EAX中。stdcall对于微软Win32 API和Open Watcom C++是标准。

微软的编译工具规定:PASCAL, WINAPI, APIENTRY, FORTRAN, CALLBACK, STDCALL, __far __pascal, __fortran, __stdcall均是指此种调用约定。

也就是说调用windows api的代码的汇编,调用者不清理参数栈,由api函数的汇编代码清理栈。

 

变长参数部分待补充

 

有意思的技巧:

来源:https://www.zhihu.com/question/439224121

Int *foo[3]

看见[N] 读作 an array of N

看见()    读作 a function that returns

看见T*   读作 a point to T

然后从变量开始,先读右边的东西,再读左边的东西,然后被括号包裹着的话,就跳出去重复这个动作。

比如int *foo[3]

An array of 3 points to int

 

原文地址:https://www.cnblogs.com/water-bear/p/14675616.html