β

C语言的三种函数调用方式

炒饭的小站 90 阅读

C语言提供了三种调用函数的方式:cdecl,stdcall和fastcall。cdecl是标准的C语言调用函数的方式(C declared);stdcall是大多数链接库中使用的调用方式,比如Windows API,JNI API等等;fastcall顾名思义,就是说它调用起来比较快。这三种方式在C语言编写中几乎没有任何差别,但对应了不同的底层实现。

要使用这三种调用函数的方法,需要在函数声明和定义时写明__cdecl(可不写),__stdcall,__fastcall。注意每个词前面是两个下划线符号。

下面来一个简单的例子:

#include <stdio.h>
int cdecl_func(int a, int b, int c)
{
    return a + b + c;
}
int __stdcall stdcall_func(int a, int b, int c)
{
    return a + b + c;
}
int __fastcall fastcall_func(int a, int b, int c)
{
    return a + b + c;
}
int main()
{
    printf("%d\n", cdecl_func(1, 2, 3));
    printf("%d\n", stdcall_func(1, 2, 3));
    printf("%d\n", fastcall_func(1, 2, 3));
}

cdecl的调用方式是由调用方控制参数栈,被调用的函数在返回时不需要对自己使用的栈进行清理,所以cdecl_func编译出来的汇编代码如下:

movl    8(%esp), %eax
    addl    4(%esp), %eax
    addl    12(%esp), %eax
    ret

调用方的代码如下:

movl    $3, 8(%esp)
    movl    $2, 4(%esp)
    movl    $1, (%esp)
    call    _cdecl_func

可以看出其采用了放置参数到栈上,然后调用的方式,可以避免清理栈的问题。

stdcall和cdecl不同,是由被调用函数来清理参数栈的,所以编译出来的函数最后一句不同,表示返回的同时清理12字节的栈空间:

movl    8(%esp), %eax
    addl    4(%esp), %eax
    addl    12(%esp), %eax
    ret     $12

同时,调用时采用压栈的方式:

pushl    $3
    pushl    $2
    pushl    $1
    call    _stdcall_func@12

这里有一点,stdcall的函数在gcc编译器下生成的标识符名会加“@参数字节数”,有时候会在(动态)链接时引起“找不到标识符”错误。通过加入 -Wl,--kill-at 可以移除“@”,从而修复错误,在此不作详述。

fastcall通过使用寄存器传参,达到快速调用的目的,使用的寄存器为ecx和edx,如果有多于两个的参数,则使用stdcall的方式进行栈传参:

addl    %edx, %ecx
    movl    %ecx, %eax
    addl    4(%esp), %eax
    ret     $4
pushl   $3
    movl    $2, %edx
    movl    $1, %ecx
    call    @fastcall_func@12

gcc下fastcall函数的命名方式更加不同,生成的标识符是以“@”开头,而不是下划线“_”。

至此,就介绍完毕C语言中三种不同的调用方式。在汇编编程的时候,最常用的往往是stdcall,而且大部分API库也都用了这种方式。但是cdecl有其独特的优势,就是可以支持不定长的参数,比如printf。在gcc中,如果给一个stdcall函数设置不定长参数,那么编译后的结果就会失去stdcall的属性,实际上成为一个cdecl函数。至于fastcall,可以在一些需要快速传参的地方使用,但是很多时候还是使用内联函数更好一些。

作者:炒饭的小站
随意而为,不拘一格
原文地址:C语言的三种函数调用方式, 感谢原作者分享。

发表评论