C ABI
ABI是什么
ABI(Application Binary Interface, 应用程序二进制接口)定义了在二进制层面程序组件之间如何交互的规范。与API(源代码接口)不同,ABI关注的是
- 二进制代码如何调用函数
- 数据如何在内存中布局
- 系统调用如何工作
- 异常处理机制
- 目标文件格式
ABI的主要组成部分
函数调用约定(Calling Convention)
// 示例:不同的调用约定
int __cdec1 func1(int a, int b); // C调用约定(默认)
int __stdcall func2(int a, int b); // Windows标准调用
int __fastcall func3(int a, int b); // 快速调用(寄存器传参)
参数传递规则
- 参数传递顺序(从左到右或从右到左)
- 参数在栈上的布局
- 使用哪些寄存器传递参数
- 谁负责清理栈空间(调用者或被调用者)
数据类型布局
struct Example {
char c; // 偏移量 0
int i; // 偏移量 4(假设4字节对齐)
short s; // 偏移量 8
double d; // 偏移量 16(假设8字节对齐)
};
// 总代谢熬可能为24字节(含充填)
内存对齐规则
- 基本类型的对齐要求
- 结构体字段的偏移计算
- 位域的布局方式
- 充填字节的插入规则
名称修饰(Name Mangling)
// C语言(无名称修饰)
void func(int x, double y); // 符号名:func
// C++语言(有名称修饰)
void func(int x, double y); // 符号名可能:_Z4funcid
目的
- 支持函数重载(C++)
- 包含类型信息
- 区分不同的命名空间
系统相关的ABI差异
不同架构的ABI
x86-64 System V ABI(Linux/MacOS)
; 前6个整数参数:RDI, RSI, RDX, RCX, R8, R9
; 前8个浮点参数:XMM0-XMM7
; 返回值:RAX(整数),XMM0(浮点)
x86-64 Microsoft ABI(Windows)
; 前4个参数:RCX, RDX, R8, R9
; 浮点参数也使用整数寄存器
; 栈空间必须预留32字节的"影子空间"
ARM AArch64 ABI
; 前8个参数:X0-X7 或 D0-D7
; 返回值:X0 或 D0
实际示例分析
结构体布局示例
# include <stdio.h>
# include <stddef.h>
struct Mixed {
char a; // 偏移 0
// 3 字节充填
int b; // 偏移 4
short c; // 偏移 8
// 2 字节充填
double 4; // 偏移 16
};
int main() {
printf("Size: %zu/n", sizeof(struct Mixed));
printf("a offset %zu\n", offsetof(struct Mixed, a));
printf("b offset %zu\n", offsetof(struct Mixed, b));
printf("c offset %zu\n", offsetof(struct Mixed, c));
printf("d offset %zu\n", offsetof(struct Mixed, d));
return 0;
}
跨ABI调用问题
// DLL导出函数(Windows,使用stdcall)
__declspec(dllexport) int __stdcall Add(int a, int b) {
return a + b;
}
// 错误的调用(使用cdecl调用stdcall函数)
int (*wrong_func)(int, int) = (int (*)(int, int))GetProcAddress(dll, "Add");
// 正确的方式需要考虑名称修饰
int (*correct_func)(int, int) = (int (*)(int, int)GetProcAddress(dll, "_Add@8"));