代码仓库shanchuann/CPP-Learninng

名字粉碎(Name Mangling,也称为名字修饰) 是编译器为解决命名冲突(如函数重载、类成员、命名空间、模板等场景)而对标识符(函数名、变量名等)进行的特殊编码处理。其核心目的是生成唯一的符号名,确保链接阶段能准确识别不同的实体。

C 语言不支持函数重载、类、命名空间等特性,函数名本身即可唯一标识一个函数,因此无需修饰。但 C++ 支持以下特性,会导致同名实体存在,必须通过名字粉碎区分:

  • 函数重载:允许同名函数存在,只要参数列表不同(参数类型、数量、顺序);
  • 类成员函数:不同类中可以有同名成员函数;
  • 命名空间:不同命名空间中可以有同名实体;
  • 模板:模板实例化会生成不同的具体函数 / 类,需区分模板参数。

修饰名由函数名、类名、调用约定、返回类型、参数等共同决定。

调用约定

__stdcall(回调调用约定)是Pascal程序的缺省调用方式,通常用于Win32 API中,函数采用从右到左的压栈方式,自己在退出时清空堆栈

1
2
3
4
5
6
7
8
9
10
11
12
13
void __stdcall funa(int a, int b) {} //回调调用约定

funa(1, 2);
push 2
push 1
call funa (0A168Bh)
nop

void __stdcall funa(int a, int b) {} //回调调用约定
push ebp
......
pop ebp
ret 8
image-20251015120349693

栈平衡:funa首先压2,1入栈,ESP上抬8,当函数退出后ESP需要回退到起点,由被调用函数维持

__cdecl(C调用约定)即用关键字说明)按从右到左的顺序压参数入栈,由调用者把参数弹出.对于传送参数的内存栈是由调用者来维护的,正因如此,实现可变参数的函数只能使用该调用约定

1
2
3
4
5
6
7
void __cdecl funb(int a, int b) {} //C调用约定

funb(3, 4);
push 4
push 3
call funb (0A1096h)
add esp,8 //由调用者(主函数)维持

__fastcall(快速调用约定)主要特点就是快,因为其是通过寄存器来传参的(实际上他使用ECX和EDX传送前两个双字(DWORD:在 x86 架构(32 位)中,“双字” 指 32 位(4 字节)的数据类型,如intunsigned intlong(32 位环境下)、指针(32 位地址)等)或更小的参数(指尺寸小于 32 位的数据类型,如 8 位的char、16 位的short等),剩下的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈),在函数名修饰约定方面,他与前两者均不同

1
2
3
4
5
6
7
void __fastcall func(int a, int b) {} //快速调用约定

func(5, 6);
mov edx,6 //传入寄存器
mov ecx,5
call func (0A1848h)
nop

在 x86 架构的通用寄存器(EAXEBXECXEDXEBPESP等)中,ECXEDX被选中主要是因为它们的 “通用性”—— 没有被赋予必须固定承担的特殊职责:

  • EAX:常用于返回值存储(函数返回时默认用EAX传递返回值),若占用可能冲突;
  • EBX:在某些编译模式下被用作 “基址寄存器”(如全局数据访问),需保持稳定;
  • EBP:栈基址指针,ESP:栈顶指针,两者用于维护栈帧结构,绝对不能用于传参;
  • ECXEDX:无强制固定用途,且在 x86 指令集中常用于通用计算(如ECX可作为循环计数器),用作参数传递寄存器不会与其他核心功能冲突。
1
2
3
4
5
int main() {
funa(1, 2);
funb(3, 4);
func(5, 6);
}
调用约定 参数传递顺序 栈清理者 支持可变参数
__cdecl 右→左 调用者
__stdcall 右→左 被调用者
__fastcall 前 2 个用寄存器,剩余右→左 被调用者

thiscall仅用于C++类成员函数,this指针存放于ECX寄存器,参数从右到左压入,thiscall不是关键字,因此不能被程序员指定

Name Mangling是一种在编译过程中将函数名,变量名的名字重新命名的机制

C语言编译时的函数名修饰约定规则

C语言的名字修饰规则十分简单,__cdecl是C/C++的缺省调用方式,调用约定函数名字前添加了下划线前缀:_functionname

image-20251015121829587

__stdcall(标准调用约定)在函数名前加下划线_,后加@和参数总字节数(十进制),例如void foo(int a, int b)(参数共 8 字节)修饰后为_foo@8

C++编译时的函数名修饰约定规则

__cdecl调用约定

  1. ?标识函数名的开始,后跟函数名
  2. 函数名后以@@YA标识参数表的开始,后跟参数表
  3. 参数表以代号表示

基础数据类型

类型 代号 说明
void X 无类型(用于无参数或返回 void)
char D 8 位字符型
signed char C 有符号 8 位字符型(与char可能不同,取决于编译器是否默认signed
unsigned char E 无符号 8 位字符型
shortshort int F 16 位有符号整数
unsigned short G 16 位无符号整数
int H 32 位有符号整数(最常用,如int a的参数代号为H
unsigned int I 32 位无符号整数
longlong int J 32 位有符号长整数(32 位环境下与int长度相同,64 位可能为 64 位)
unsigned long K 32 位无符号长整数
long long Q 64 位有符号长整数(__int64
unsigned long long R 64 位无符号长整数
float M 32 位单精度浮点数
double N 64 位双精度浮点数(如double b的参数代号为N
long double O 扩展精度浮点数(通常 80 位或 128 位,取决于编译器)
bool _N 布尔类型(1 字节或 4 字节,MSVC 中通常为 4 字节)

复合类型(指针、引用、数组等)

类型 代号规则 示例
指针(T* 前缀P + 基础类型代号 char*PADP+A+DA为指针修饰);int*PAH
引用(T& 前缀A + 基础类型代号 int&AHdouble&AN
数组(T[] 前缀$ + 元素类型代号 + 数组长度(可选) int[3]$H3char[]$D(未指定长度)
函数指针 前缀P6 + 返回类型 + 参数表 + @Z int(*)(char)P6HAD@ZP6为函数指针标记,H返回 int,D参数 char)

类 / 结构体 / 命名空间类型

类型 代号规则 示例
类 / 结构体T 前缀V + 类名 + @@ AVA@@;结构体SVS@@
类指针(T* P + 类类型代号 A*PVA@@P+VA@@
命名空间中的类型 命名空间名 + @ + 类型代号 命名空间ns中的类BVns@B@@
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
"bool __cdecl Namefuna(int,int)" (?Namefuna@@YA_NHH@Z)
"void __cdecl Namefuna(int)" (?Namefuna@@YAXH@Z)
"void __cdecl Namefuna(double,int)"
"void __cdecl Namefuna(int *)" (?Namefuna@@YAXPAH@Z)

bool Namefuna(int a, int b);
void Namefuna(int a);
void Namefuna(double a, int b);
void Namefuna(int* a);
int main() {
int a = 10;
Namefuna(a);
Namefuna(a, 20);
Namefuna(3.14, 30);
Namefuna(&a);
return 0;
}