美文网首页
函数调用惯例(Calling Convention)

函数调用惯例(Calling Convention)

作者: andy_shx | 来源:发表于2020-06-11 20:34 被阅读0次

    转载,详见原文:https://www.cnblogs.com/zhangjinfu/articles/11277579.html
    我们知道,一个C程序由若干个函数组成,C程序的执行实际上就是函数之间的相互调用。请看下面的代码:

    [ 复制代码

    ](javascript:void(0); "复制代码")

    <pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;"> 1 #include <stdio.h>
    2 void funcA(int m, int n){ 3 printf("funcA被调用\n");
    4 }
    5 void funcB(float a, float b){ 6 funcA(100, 200);
    7 printf("funcB被调用\n");
    8 }
    9 int main(){ 10 funcB(19.9, 28.5); 11 printf("main被调用\n"); 12 return 0; 13 }</pre>

    [ 复制代码

    ](javascript:void(0); "复制代码")

    main() 调用了 funcB(),funcB() 又调用了 funcA()。对于main() 调用 funcB(),我们称 main() 是调用方,funcB() 是被调用方;同理,对于 funcB() 调用 funcA(),funcB() 是调用方,funcA() 是被调用方。

    函数的参数(实参)由调用方压入栈中供被调用方使用,它们之间要有一致的约定。例如,参数是从左到右入栈还是从右到左入栈,如果双方理解不一致,被调用方使用参数时就会出错。

    以 funcB() 为例,假设 main() 函数先将 19.9 入栈,后将 28.5 入栈,但是 funcB() 在使用这些实参时却认为 28.5 先入栈,19.9 后入栈,那么就一定会产生混乱,误以为19.9 是传递给 b、28.5 是传递给 a 的。

    所以,函数调用方和被调用方必须遵守同样的约定,理解要一致,这称为调用惯例(Calling Convention)。

    一个调用惯例一般规定以下两方面的内容:

    1. 函数参数的传递方式,是通过栈传递还是通过寄存器传递(这里我们只讲解通过栈传递的情况)。

    2. 函数参数的传递顺序,是从左到右入栈还是从右到左入栈。

    3. 参数弹出方式。函数调用结束后需要将压入栈中的参数全部弹出,以使得栈在函数调用前后保持一致。这个弹出的工作可以由调用方来完成,也可以由被调用方来完成。

    4. 函数名修饰方式。函数名在编译时会被修改,调用惯例可以决定如何修改函数名。

    在C语言中,存在多种调用惯例,可以在函数声明或函数定义时指定,例如:

    [ 复制代码

    ](javascript:void(0); "复制代码")

    <pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;"> 1 #include <stdio.h>
    2 int __cdecl max(int m, int n); 3 int main(){ 4 int a = max(10, 20);
    5 printf("a = %d\n", a);
    6 return 0;
    7 }
    8 int __cdecl max(int m, int n){ 9 int max = m>n ? m : n; 10 return max; 11 }</pre>

    [ 复制代码

    ](javascript:void(0); "复制代码")

    函数调用惯例在函数声明和函数定义时都可以指定,语法格式为:

    返回值类型 调用惯例 函数名(函数参数)

    在函数声明处是为调用方指定调用惯例,而在函数定义处是为被调用方(也就是函数本身)指定调用惯例。

    __cdecl是C语言默认的调用惯例,在平时编程中,我们其实很少去指定调用惯例,这个时候就使用默认的 __cdecl。

    注意:__cdecl 并不是标准关键字,上面的写法在 VC/VS 下有效,但是在 GCC 下,要使用 attribute((cdecl))。

    除了 cdecl,还有其他调用惯例,请看下表:

    | 调用惯例 | 参数传递方式 | 参数出栈方式 | 名字修饰 |
    | cdecl | 按照从右到左的顺序入栈 | 调用方 | 下划线+函数名,
    如函数 max() 的修饰名为 _max |
    | stdcall | 按照从右到左的顺序入栈 | 函数本身
    (被调用方) | 下划线+函数名+@+参数的字节数,
    如函数 int max(int m, int n) 的修饰名为 max@8 |
    | fastcall | 将部分参数放入寄存器,
    剩下的参数按照从右到左的顺序入栈 | 函数本身
    (被调用方) | @+函数名+@+参数的字节数 |
    | pascal | 按照从左到右的顺序入栈 | 函数本身
    (被调用方) | 较为复杂,这里不再展开讨论
    |

    相关文章

      网友评论

          本文标题:函数调用惯例(Calling Convention)

          本文链接:https://www.haomeiwen.com/subject/iueitktx.html