引子
#include <stdio.h>
void main() {
int i = 5;
printf("%d %d %d %d %d %d\n", i, --i, i--,i, i--,i);
}
* 求运行结果
在64位Ubuntu上的输出结果是 :2 2 4 2 5 2
在嵌入式平台ssc323上输出结果是 :2 2 4 2 3 2
原因
- 这是由于函数调用规则不同导致的,函数调用对于程序员只是一条命令,但是语言实现却有一些需要考虑的,因此出现了多种调用规则。
- 需要考虑的:
- 参数压栈顺序
- 栈维护
- 返回值存放
- 函数名称修饰
函数调用规则
- 常见规则:stdcall、cdecl、fastcall、thiscall、naked call等。
调用规则 | __cdecl | __stdcall | __fastcall | __pascal | __thiscall |
---|
参数压栈顺序 | 从右到左 | 从右到左 | 用ECX和EDX传送前两个双字(DWORD)或更小的参数,剩下的参数仍旧自右向左压栈传送 | 从左到右 | 从右到左 |
栈维护 | 函数调用者 | 函数本身 | 函数本身 | 函数本身 | 函数本身 |
返回值存放 | EAX | EAX | EAX | EAX | EAX |
C编译器名称修饰 | 函数名前加上一个下划线前缀 | 函数名前加上一个下划线前缀 | 函数名前加上一个"@“符号,后面也是一个”@"符号和其参数的字节数 | | |
C++编译器名称修饰 | 规则与_stdcall调用约定相同,只是参数表的开始标识由上面的"@@YG"变为"@@YA"。 | 见表下面 | 规则与_stdcall调用约定相同,只是参数表的开始标识由上面的"@@YG"变为"@@YI" | | |
获知当前编译工具使用的函数调用规则
设置函数调用规则
void fatal () __attribute__ ((stdcall__));
* 在头文件中可以
void fatal () __attribute__ ((__stdcall__));
个人总结
- 以上函数调用规则的介绍在大多数博客中都能找到,规则种类较多,差别不大,看完好像并没有什么实际作用,在实际编程中,测试发现不同环境下的,语言并不是完全按照这些规则行事,例如:linux下,参数的压栈并不是完全按照从右到左或者从左到右,个人测试发现如果参数数据类型不同,编译器有时会自动调整顺序,个人更喜欢去测试和验证编译器的规则。
引子中例子的输出结果原因
- 函数调用参数压栈前,先会对所有参数进行运算,运算顺序:64位Ubuntu上是从右往左,ssc323平台是从左往右,可能是由压栈顺序决定的。
- 对于i–,会生成临时变量保存运算的结果,压栈也是使用的生成的临时变量。
- 对于–i,不会生成临时变量,经过了所有的参数运算后,最后压栈时使用的是变量i。
1. i, //所有参数运算完后,i值为2
2. i--, //从右往左运算,生成的临时变量值为5
3. i, //所有参数运算完后,i值为2
4. i--, //从右往左运算,生成的临时变量值为4
5. --i, //所有参数运算完后,i值为2
6. i //所有参数运算完后,i值为2
* 所以输出结果为:2 2 4 2 5 2
* 结果的显示顺序还是从左到右,按代码需求来的,不会由于底层实现不同而发生变化。
1. i, //所有参数运算完后,i值为2
2. --i, //所有参数运算完后,i值为2
3. i--, //从左往右运算,生成的临时变量值为4
4. i, //所有参数运算完后,i值为2
5. i--, //从左往右运算,生成的临时变量值为3
6. i //所有参数运算完后,i值为2
* 所以输出结果为:2 2 4 2 3 2
* 结果的显示顺序还是从左到右,按代码需求来的,不会由于底层实现不同而发生变化。