参数约定
传递方式 | 简要说明 | 编译约定 |
cdecl | C调用约定,参数从右到左求值并入栈,调用函数清理堆栈;实现可变参数的函数(如printf)只能使用该调用约定 | C: _resize C++: ?resize@@YA*@Z |
thiscall | C++ 成员函数调用约定,this指针存放于ECX寄存器中,参数从右到左求值入栈,被调函数在退出时清理堆栈 | |
stdcall | Windows API的缺省调用方式,参数以值传递方式从右到左求值入栈,被调函数在退出时清理堆栈 | C:_resize@4 C++: ?resize@@YG*@Z |
fastcall | 前两个参数是DWORD类型或更小的数据则传入ECX、EDX,其它参数以从右到左的方式求值入栈,被调函数退出时清理堆栈 | C:@resize@4 C++: ?resize@@YI*@Z |
clrcall | 从左到右加载参数到CLR expression stack | 与thiscall 合用 |
pascal | 不再使用 | 参见 stdcall |
syscall | 不再使用 | |
fortran | 不再使用 |
【注】VC++对函数的省缺声明是"__cedcl",将只能被C/C++调用,C++调用约定中的符号(*)需要根据参数填充;
参数传递顺序
参数传递顺序有从左到右传递、从右到左传递两种,由于参数也是一个表达式,关注参数的求值顺序对写出正确的程序非常关键,例如:calc (origin++, origin+inc),如果不清楚参数表达式求值顺序就无法正确理解程序;
概念名称 | 简要说明 | 备注(案例) |
从左到右传递 | 对函数的多个输入参数从左到右求值并压入栈 | 入栈为在堆栈中分配内存 出栈为释放参数所占内存 |
从右到左传递 | 对函数的多个输入参数从右到左求值并压入栈 |
参数传递方式:
参数传递方式有值传递、引用传递、指针传递三种,三种参数本质上都是【值传递】,基本类型由于地址所指即为真实数据,传递时会生成真实数据的拷贝,将会消耗更多的堆栈内存,而引用传递和指针传递乃间接指向对象,传递时只是生成地址的拷贝,堆栈内存消耗比较少;我们首先对各个概念做一个简要回顾:
概念名称 | 简要说明 | 备注(案例) |
值传递 | 对参数求值后把其所指数据生成一份拷贝再压入栈 | 堆栈内存消耗大户 |
引用传递 | 对参数求值后把它的引用地址压入栈 | 平台字节宽度,例如32位占4字节,64位占8字节。 |
指针传递 | 对参数求值后把它的引用地址压入栈 |
参数如何传递才最安全、最有效率?
Windows平台的调用栈空间是在链接时固定分配并写入二进制文件,UNIX类平台则可以通过环境变量设置,他们的默认栈初始空间情况如下:
平台 | 默认堆栈 | 最大堆栈 | 说明 | |
SunOS/Solaris | 8192KB | 无上限 | 可通过环境变量配置 | |
Unix/Linux | 8192KB | ??? | ||
Windows | x86/x64 | 1024KB | 32768K | 链接时修改 |
Itanium | 4096KB | 32768K | ||
cygwin | 2048KB | ??? | ||
指针传递、引用传递都是传递对象的地址,传给函数时都是把这个地址压入栈,32位平台为四个字节,64位平台为八个字节。标量类型、指针、引用等数据类型长度都小于等于机器字长,占用的空间小,入栈、出栈速度都是非常块的,一般情况下默认栈空间足够使用,不会出现堆栈溢出的问题;
但结构、类的值传递方式带来诸多问题,例如堆栈溢出、数据丢失、效率低下等,建议结构、类完全使用指针或者引用传递;如:
安全隐患 | 简要说明 | 备注 |
堆栈溢出 | 如果结构和类都是很大,创建其副本会消耗大量的空间和时间,最终产生溢出错误 | |
数据丢失 | 类对象创建副本时,会受到类实现的影响而无法完全复制,参见文档《Effective C++》第二章 | 效率降低 |