首页
社区
课程
招聘
[原创]函数调用约定
发表于: 2024-8-5 18:49 3263

[原创]函数调用约定

2024-8-5 18:49
3263

关于C语言的函数调用约定,主要关注四个问题:

  1. 参数的传递方向
  2. 参数的传递媒介
  3. 谁负责栈平衡?
  4. 返回值的存储

针对以上四个问题,在微软VC++平台上,提供了三种调用约定:__stdcall,__cdecl和__fastcall。

PS:以下内容仅适用于32位x86平台

__stdcall

这是VC++的约定,也就是微软的约定。

  1. 参数从右向左传递
  2. 使用栈传参
  3. 被调用方清理参数
  4. 返回值一般在处理器中

先准备一段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)。

  1. 参数从右向左传递
  2. 使用栈传参
  3. 调用方清理参数
  4. 返回值一般在处理器中

将函数定义改为__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

一个小众的调用约定

  1. 左数前两个参数使用处理器传参,其它参数从右向左使用栈传参
  2. 被调用方清理参数空间
  3. 返回值一般在处理器中

因为前两个参数使用寄存器传参,相对快些,故名曰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编辑 ,原因: 修改一些错误
收藏
免费 1
支持
分享
最新回复 (7)
雪    币: 437
活跃值: (272)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
很好的文章,对汇编初学者很有帮助!

__cdecl
这是C标准调用约定,de即default的缩写(纠正一下,这里的cdecl是指C declaration)
The cdecl (which stands for C declaration) is a calling convention for the programming language C and is used by many C compilers for the x86 architecture.
来自wiki关于cdecl的描述:https://en.wikipedia.org/wiki/X86_calling_conventions#cdecl
2024-8-6 17:40
0
雪    币: 5510
活跃值: (870)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
3
lovepd 很好的文章,对汇编初学者很有帮助! __cdecl 这是C标准调用约定,de即default的缩写(纠正一下,这里的cdecl是指C declaration) The cdecl (whic ...
感谢指正
2024-8-7 06:29
0
雪    币: 15969
活跃值: (3376)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4

>后续的x64架构新增了R8~R15共8个寄存器

我倒是 好奇,
若 传入 20-30 个 参数 是如何 ?
先把 R8-R15 先用光,
再来 寄存器 E?X 

寄存器 都用完了 用啥 ?


最后于 2024-8-7 07:49 被plusv编辑 ,原因:
2024-8-7 07:27
0
雪    币: 5510
活跃值: (870)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
5
plusv >后续的x64架构新增了R8~R15共8个寄存器我倒是 好奇,若 传入 20-30 个 参数 是 ...
没试过这么多参数,你可以上机调试一下看看
2024-8-7 09:40
0
雪    币: 0
活跃值: (35)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
plusv >后续的x64架构新增了R8~R15共8个寄存器我倒是 好奇,若 传入 20-30 个 参数 是 ...

2024-8-7 18:51
1
雪    币: 5510
活跃值: (870)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
7
LLL亮
你还真试了
2024-8-7 19:54
0
雪    币: 15969
活跃值: (3376)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
LLL亮

感谢你的测试.


由结果看


mov dword ptr [rsp+?h],?h  --> 共 22 个

mov r?d,?  --> 共 2 个

mov e?x,?  --> 共 2 个


1.

与楼主所说 不同,

并不是 "在x64架构中,无论是哪种调用约定,都优先使用寄存器传参"


2.

与我 预测 不同,

我 预测:

先把 R8-R15 先用光,

再来 寄存器 E?X 

寄存器 都用完了 用 其它.

结果 是:

用 其它.

只用了 2 个 R8-R15,

只用了 2 个 E?X.


3. 

奇怪的是 32 位 及 64 位 寄存器 都只用了 2 个,似乎用的贼少,

那"x64架构新增了R8~R15共8个寄存器,缓解了32位平台寄存器紧缺的问题"

也没有缓解了 32 位寄存器紧缺的问题.


4.

我觉的 32 位 用 Push 容易一眼看出 参数,

但 64 位 用 mov 不容易看.


最后于 2024-8-8 02:55 被plusv编辑 ,原因:
2024-8-8 02:46
0
游客
登录 | 注册 方可回帖
返回
//