C++重载底层原理
Published on 2024-09-20 16:43 in Loading... with 红彡sakura

C++重载底层原理

C++重载原理

C++实现重载使用的是叫name mangling的技术,实际上是靠函数名称和传递的参数类型、数量生成一个新的函数名称,比如

void func(int);
void func(double);

(g++ xx.cpp -S后生成xx.s汇编文件)经过编译后的汇编符号变成

_Z4funci;
_Z4funcd;

通过这样的方法实现重载。

而C不能实现重载的原因是因为C不能通过函数名称和传递的参数来生成新的函数符号,所以c文件汇编后的函数名与源文件的函数基本一致。

tips:使用C++filt可以还原C++生成的函数符号

>C++filt _Z4funci
>fun(int)

再谈论一下函数调用约定

函数调用约定指的是函数参数的入栈顺序、位置以及由谁恢复堆栈的约定。

__cdecl

参数传递顺序由右到左,堆栈由调用方清理

int __cdecl func(int a1,int a2)
{
return a1+a2;
}
int main()
{
func(1,2);
return 0;
}

汇编代码如下:

5: return a1 + a2;
00561831 mov eax,dword ptr [a1]
00561834 add eax,dword ptr [a2]
6: }
00561837 pop edi
00561838 pop esi
00561839 pop ebx
0056183A add esp,0C0h
00561840 cmp ebp,esp
00561842 call __RTC_CheckEsp (056123Fh)
00561847 mov esp,ebp
00561849 pop ebp
0056184A ret
...
9: func(1, 2);
005618D1 push 2
005618D3 push 1
005618D5 call func (05613B6h)
005618DA add esp,8

可以看出先将2压入栈,再压入1,最后由main函数恢复栈。

__stdcall

__stdcall 调用约定用于调用 Win32 API 函数。 参数从右到左进行入栈,被调用方将清理堆栈,以便让编译器生成 vararg 函数 __cdecl。 使用此调用约定的函数需要一个函数原型。 __stdcall 修饰符是 Microsoft 专用的。

vararg函数指接收可变数量参数的函数。

int __stdcall func(int a1,int a2)
{
return a1+a2;
}
int main()
{
func(1,2);
return 0;
}

汇编代码如下:

5: return a1 + a2;
00181836 or al,5Fh
6: }
00181838 pop esi
00181839 pop ebx
0018183A add esp,0C0h
00181840 cmp ebp,esp
00181842 call __RTC_CheckEsp (018123Fh)
00181847 mov esp,ebp
00181849 pop ebp
0018184A ret 8
...
9: func(1, 2);
001818D1 push 2
001818D3 push 1
001818D5 call func (01813BBh)

__fastcall

这个约定仅适用于x86结构,尽可能多得使用寄存器传递函数自变量,前两个参数从左往右存入寄存器ECX、EDX,多余的参数从右往左压入栈内,如果有多余的参数在栈中则由被调用者恢复。

int __fastcall func(int a1,int a2)
{
return a1+a2;
}
int main()
{
func(1,2);
return 0;
}

汇编代码如下:

5: return a1 + a2;
00B9183D mov eax,dword ptr [a1]
00B91854 in eax,5Dh
00B91856 ret 4
...
9: func(1, 2, 3);
00B918D1 push 3
00B918D3 mov edx,2
00B918D8 mov ecx,1
00B918DD call func (0B913C0h)

可以看出是先压多余的参数入栈,再将前两个参数存入寄存器中,我也尝试了一下只传一个参数是存ECX还是EDX,结果是ECX。


__thiscall

特定于 Microsoft __thiscall 的调用约定用于 x86 体系结构上的 C++ 类成员函数。 它是成员函数使用的默认调用约定,该约定不使用变量参数(vararg 函数)。

__thiscall 下,被调用方清理堆栈,这对于 vararg 函数是不可能的。 自变量将从右到左推送到堆栈中。 指针 this 通过注册 ECX 传递,而不是在堆栈上传递。

__thiscall是C++类成员函数所独有的,在汇编代码中通常是将this作为第一个参数传递。

//TODO:能力尚浅,后续再来研究

__vectorcall

要看懂这个约定首先得知道x86与x64的区别。

//TODO:x86与x64的区别

__clrcall

//TODO:托管函数,也是不太懂

// clrcall3.cpp
// compile with: /clr
void Test() {
System::Console::WriteLine("in Test");
}
int main() {
void (*pTest)() = &Test;
(*pTest)();
void (__clrcall *pTest2)() = &Test;
(*pTest2)();
}

__nakedcall

一种比较少见的约定,常用于驱动开发。

参考文献

调用约定 | Microsoft Learn

posted @   Isakura红  阅读(20)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示