C语言调用约定详解:cdecl、stdcall、fastcall

文章摘要

C语言中常见的函数调用约定包括cdecl、stdcall和fastcall,它们规定了参数传递、栈维护和寄存器使用规则。cdecl是C语言默认约定,支持可变参数,调用者清理栈;stdcall用于Windows API,被调用者清理栈;fastcall通过寄存器传递部分参数以提升性能。此外,thiscall用于C++成员函数,vectorcall优化向量运算。不同调用约定不可混用,64位平台通常采用统一约定。开发者需注意跨平台兼容性和调用约定的正确声明。

C语言中常见的调用约定(Calling Convention),比如 cdeclstdcallfastcall。这些约定主要规定了函数参数如何传递、返回值如何传递、栈的维护方式、寄存器的使用规则等。下面详细介绍:


1. cdecl(C Declaration)

  • 全称:C declaration
  • 参数传递:参数从右到左依次压栈(即最后一个参数先入栈)。
  • 栈清理调用者负责清理栈(Caller Clean-up)。
  • 返回值:通常通过EAX寄存器返回。
  • 可变参数:支持可变参数函数(如printf)。
  • 使用场景:C语言默认调用约定(在GCC、MSVC下C语言默认就是cdecl)。
  • 声明方式(MSVC):__cdecl
  • 示例
    int __cdecl add(int a, int b);
    

2. stdcall(Standard Call)

  • 全称:Standard Call
  • 参数传递:参数从右到左依次压栈。
  • 栈清理被调用者负责清理栈(Callee Clean-up)。
  • 返回值:通过EAX返回。
  • 可变参数不支持可变参数函数。
  • 使用场景:Windows API函数大多采用stdcall。
  • 声明方式(MSVC):__stdcall
  • 示例
    int __stdcall add(int a, int b);
    

3. fastcall(Fast Call)

  • 全称:Fast Call
  • 参数传递:前几个参数通过寄存器传递(如x86下ECX、EDX),剩余参数从右到左压栈。
  • 栈清理被调用者负责清理栈。
  • 返回值:通过EAX返回。
  • 可变参数不支持可变参数函数。
  • 使用场景:对性能要求高的场合。
  • 声明方式(MSVC):__fastcall
  • 示例
    int __fastcall add(int a, int b);
    

4. 其他常见调用约定

  • thiscall:C++成员函数的默认调用约定,this指针通过ECX传递。
  • vectorcall:用于向量运算,参数通过SIMD寄存器传递(MSVC支持)。

5. 调用约定的区别总结

调用约定参数传递方式栈清理者可变参数主要用途
cdecl右到左,全部压栈调用者支持C语言默认
stdcall右到左,全部压栈被调用者不支持Windows API
fastcall前2个寄存器,其余压栈被调用者不支持性能优化

6. 代码示例(MSVC)

int __cdecl    func1(int a, int b); // cdecl
int __stdcall  func2(int a, int b); // stdcall
int __fastcall func3(int a, int b); // fastcall

7. 注意事项

  • 不同调用约定不能混用,否则会导致栈混乱、程序崩溃。
  • 跨平台时要注意,不同编译器、不同平台的默认调用约定可能不同。
  • 64位下(如x86_64),Windows和Linux都有自己的统一调用约定,cdecl/stdcall/fastcall等在64位下意义不大。

好的,下面我将详细介绍cdecl、stdcall、fastcall三种调用约定在x86(32位)平台下的汇编实现细节,并简要说明它们在不同平台(如x86_64、ARM等)下的差异。


一、x86(32位)平台下的汇编实现

1. cdecl

特点

  • 参数从右到左依次压栈
  • 调用者清理栈

示例函数声明

int __cdecl add(int a, int b);

调用方汇编(伪代码):

push b        ; 先压入b
push a        ; 再压入a
call add      ; 调用函数
add esp, 8    ; 调用者清理参数(2个int,共8字节)

被调用方汇编(伪代码):

add:
    mov eax, [esp+4] ; 取a
    mov edx, [esp+8] ; 取b
    add eax, edx
    ret

2. stdcall

特点

  • 参数从右到左依次压栈
  • 被调用者清理栈

示例函数声明

int __stdcall add(int a, int b);

调用方汇编

push b
push a
call add
; 不需要add esp, 8

被调用方汇编

add:
    mov eax, [esp+4]
    mov edx, [esp+8]
    add eax, edx
    ret 8           ; 被调用者在ret时弹出参数

3. fastcall

特点(MSVC x86实现):

  • 前两个int参数分别通过ECXEDX寄存器传递,其余参数从右到左压栈
  • 被调用者清理栈

示例函数声明

int __fastcall add(int a, int b);

调用方汇编

mov ecx, a
mov edx, b
call add
; 没有参数压栈,也不需要清理栈

被调用方汇编

add:
    add ecx, edx
    mov eax, ecx
    ret

如果有第三个参数,第三个及以后的参数依然从右到左压栈,返回时ret弹出相应字节。


二、不同平台的调用约定差异

1. x86_64(64位)

  • Windows x64:前4个整数参数通过RCX, RDX, R8, R9传递,返回值用RAX,其余参数压栈。栈由调用者清理。没有cdecl/stdcall/fastcall之分,统一为“Microsoft x64 calling convention”。
  • Linux x86_64(System V ABI):前6个整数参数通过RDI, RSI, RDX, RCX, R8, R9传递,返回值用RAX,其余参数压栈。栈由调用者清理。

注意:64位下,cdecl/stdcall/fastcall等传统约定已被统一,编译器一般不再区分。


2. ARM

  • ARM EABI(嵌入式应用二进制接口):前4个参数通过R0-R3寄存器传递,返回值用R0,其余参数压栈。栈由调用者清理。

3. 其他平台

  • MIPS、PowerPC等:也有各自的ABI,参数传递和栈清理规则不同,但大体思路类似。

三、调用约定的实际影响

  • 函数调用的二进制兼容性:不同调用约定的函数不能直接互调,否则会导致栈错乱、程序崩溃。
  • 与汇编代码交互:如果C函数要被汇编调用,或汇编实现的函数要被C调用,必须保证调用约定一致。
  • 跨平台移植:移植时要注意目标平台的ABI,不能简单照搬x86的调用约定。

四、总结表格

平台参数传递方式栈清理者备注
x86 cdecl右到左压栈调用者支持可变参数
x86 stdcall右到左压栈被调用者Windows API常用
x86 fastcallECX/EDX寄存器+右到左压栈被调用者MSVC实现,GCC略有不同
x86_64 WinRCX, RDX, R8, R9+压栈调用者统一调用约定
x86_64 LinRDI, RSI, RDX, RCX, R8, R9+压栈调用者System V ABI
ARMR0-R3寄存器+压栈调用者EABI

五、参考资料


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你一身傲骨怎能输

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值