[原创]函数调用约定
发表于:
2024-8-5 18:49
3262
关于C语言的函数调用约定,主要关注四个问题:
参数的传递方向
参数的传递媒介
谁负责栈平衡?
返回值的存储
针对以上四个问题,在微软VC++平台上,提供了三种调用约定:__stdcall,__cdecl和__fastcall。
PS:以下内容仅适用于32位x86平台
__stdcall
这是VC++的约定,也就是微软的约定。
参数从右向左传递
使用栈传参
被调用方清理参数
返回值一般在处理器中
先准备一段C代码:
1
2
3
4
5
6
7
8
9
10
int
__stdcall func(
int
x,
int
y,
int
z,
int
m)
{
return
x + y + z + m;
}
int
main(
void
)
{
func(4, 5, 6, 7);
return
0;
}
断点调试到调用func处,打开反汇编窗口可见,从右向左依次向栈中压入7,6,5,4四个参数: 再断点执行到func返回处可见,结果保存在寄存器eax中作为返回值。 在执行ret指令时指定了一个操作数0x10,也就是过程返回后还要将栈指针寄存器esp加16字节(4个int型参数的长度)以恢复栈平衡,这里体现了被调用者清理参数空间。
__cdecl
这是C标准调用约定(C declaration)。
参数从右向左传递
使用栈传参
调用方清理参数
返回值一般在处理器中
将函数定义改为__cdecl
1
2
3
4
5
6
7
8
9
10
int
__cdecl func(
int
x,
int
y,
int
z,
int
m)
{
return
x + y + z + m;
}
int
main(
void
)
{
func(4, 5, 6, 7);
return
0;
}
断点调试到调用func处,打开反汇编窗口可见,从右向左依次向栈中压入7,6,5,4四个参数,并且在call指令之后,也就是过程调用返回后,将栈指针寄存器esp加0x10(16字节,4个int型参数的长度),恢复栈平衡,这里体现出由调用者清理参数空间。PS:如果函数是变参函数,则只能使用__cdecl,因为只有调用者知道传入了几个参数,也只能由调用者恢复栈平衡。 再断点执行到func返回之前,可见结果是保存到寄存器eax中作为返回值的。
__fastcall
一个小众的调用约定
左数前两个参数使用处理器传参,其它参数从右向左使用栈传参
被调用方清理参数空间
返回值一般在处理器中
因为前两个参数使用寄存器传参,相对快些,故名曰fast。 将func定义改为__fastcall
1
2
3
4
5
6
7
8
9
10
int
__fastcall func(
int
x,
int
y,
int
z,
int
m)
{
return
x + y + z + m;
}
int
main(
void
)
{
func(4, 5, 6, 7);
return
0;
}
断点执行到调用func处,打开反汇编窗口可见,先从右向左向栈中压入7,6,再使用寄存器edx,ecx分别传递参数5,4: 断点执行到func返回处可见,最终结果保存在寄存器eax中作为返回值,并且在执行ret指令时指定了操作数8(m,z两个参数的长度)以恢复栈平衡。
x64架构的传参
上述所有传参方式均是针对32位x86平台的,后续的x64架构新增了R8~R15共8个寄存器,缓解了32位平台寄存器紧缺的问题,所以在x64架构中,无论是哪种调用约定,都优先使用寄存器传参。 将Visual Studio修改为x64: 再看反汇编代码,4个参数均使用寄存器传参:
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2024-8-7 06:31
被米龙·0xFFFE编辑
,原因: 修改一些错误