网站怎么申请支付宝,wordpress免费主机,全网营销推广系统,地铁建设网站文章目录 #x1f4dd;前言#x1f320; C支持函数重载的原理#xff1a;名字修饰(name Mangling)#x1f309;不同编译器不同函数名修饰规则 #x1f320;Windows下名字修饰规则#x1f6a9;总结 #x1f4dd;前言
函数重载概念 函数重载#xff1a;是函数的一种特殊… 文章目录 前言 C支持函数重载的原理名字修饰(name Mangling)不同编译器不同函数名修饰规则 Windows下名字修饰规则总结 前言
函数重载概念 函数重载是函数的一种特殊情况C允许在同一作用域中声明几个功能类似的同名函数这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同常用来处理实现功能类似数据类型不同的问题。
//参数类型不同
int Add(int left, int right)
{cout int Add(int left, int right) endl;return left right;
}double Add(double left, double right)
{cout double Add(double left, double right) endl;return left right;
}//参数个数不同
void f()
{cout f() endl;
}
void f(int a)
{cout f(int a) endl;
}//参数类型顺序不同
void f(int a, char b)
{cout f(int a,char b) endl;
}
void f(char b, int a)
{cout f(char b, int a) endl;
}C支持函数重载的原理名字修饰(name Mangling)
为什么C支持函数重载而C语言不支持函数重载呢
C通过名字查找、名字修饰、解析和链接这几个步骤,实现了函数重载的功能。名字修饰产生唯一内部名称,是支持重载的关键。但在程序运行时,仍然使用原来的外部函数名称调用,这是函数重载的一个重要特点。 什么是名字修饰: 名字修饰(Name Mangling)是C编译器为函数、类等名称添加额外信息的过程,目的是为了区分重载和重定义等名称。 名字修饰的原理 名称修饰是编译器在编译源代码时为函数、类等名称添加额外信息的过程生成内部链接名称。该内部链接名称包含原名称以及其他信息,如参数类型、返回类型等。 这样就可以区分函数重载、重定义等情况生成唯一的内部名称。链接器根据这些内部名称进行链接。但程序在调用时仍然使用原外部未修饰的名称。
例如一下C的函数重载
int func(int a);
int func(double b); 编译器可能为它们生成以下内部名称:
_Z4funci // func(int)
_Z4funcd // func(double)这里_Z4func是原名称,后面的i和d表示参数类型。因此即使两个函数的原名相同但在编译器进行编译处理后根据参数的类型进行标记获得了不同的名字标识符。所以当编译器根据内部名称的不同就可以将他们区分开来。
当然更细化的理解应该是这样的在C/C中一个程序要运行起来需要经历以下几个阶段预处理、编译、汇编、链接。
实际项目通常是由多个头文件和多个源文件构成而通过C语言阶段学习的编译链接我们可以知道编译和链接他们各自都干了不少事首先我们先吧一个项目分为3个文件Stack.hStack.cpp Test.cpp
Stack.h
#pragma once
#includeiostream
using namespace std;struct Stack
{
};void StackInit(struct Stack* ps, int n);Stack.cpp
#includeStack.hvoid StackInit(struct Stack* ps, int n)
{cout void StackInit(struct Stack* ps, int n) endl;
}Test.cpp
#includeStack.hint main()
{struct Stack st;StackInit(st, 10);return 0;
}代码运行 此时程序正常运行当我把Stack.cpp里的定义去掉后如图 再次编译运行时代码就会报错这个错误不是编译错误而是链接错误编译错误通常是语法错误。 再看此图我们来分析这个为什么是链接错误可知道当Test.cppStack.cppStack.h这三个文件运行起来是先进行预处理预处理****就是把相应的头文件展开然后宏替换然后条件编译等等紧接着Stack.cpp和Stack.h会生成Stack.i文件Stack.h和Test.cpp会生成Test.i文件也就是.c文件分别与.h文件进行生成.i文件生成了两个.i文件然后进行编译编译就检查语法生成汇编代码两个.i文件就会生成两个.s文件Stack.s和Test.s接下来就是汇编将汇编代码转成二进制机器码此时两.s文件将会生成对应的.o文件Stack.o和Test.o这时编译已然结束那接下来就是将这两个文件链接起来生成可执行程序xxx.exe。 了解了以上编译的大致过程接下来我们把Stack.cpp里的定义还原我们拿完整的代码来解析。 我们看以下反汇编代码图首先进去main()主函数时 可以看到函数有一堆要执行的指令函数地址第一句指令的地址 当我们继续按F11进入Call这个指令时他根据函数StackInit (0A113C5h)选择括号里的地址0A113C5h的跳转到00A113C5(注这个地址跟0A113C5h是一样的只是进制的表示不同)当再次运行时会继续根据函数括号的地址记性跳转 从这里看出有函数的定义才能生成函数一堆汇编指令第一句指定的地址才是函数的地址先Stack.cpp-Stack.o最后Test.o和Stack.o链接到一起合并到一起才有地址 结论:Test.cpp只有函数声明把Stack.cpp的定义去掉可以过因为语法检查是匹配的Test.cpp-Test.o过程中没有函数的地址链接时就要用StacklInit这个名字去Stack.o找他的地址 链接时: 1、直接用函数名字去查找是否支持重载不支持。C语言 2、直接用修饰后的函数名字去查找就可以支持重载。C
C如此例子运行 这就回到了我们最初的这个概念这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同常用来处理实现功能类似数据类型不同的问题 注意以上情况是分多个文件才会发生这样的情况如果你不分这么一个文件全部放在一个文件中就不会有这个情况但是实际项目通常是由多个头文件和多个源文件构成。 这是大致流程图
不同编译器不同函数名修饰规则
那么链接时面对Add函数链接接器会使用哪个名字去找呢这里每个编译器都有自己的函数名修饰规则。
int Add(int a, int b)
{return a b;
}
void func(int a, double b, int* p)
{ }
int main()
{Add(1,2);func(1,2,0);return 0;
}由于Windows下vs的修饰规则过于复杂而Linux下g的修饰规则简单易懂下面我们使用了g演示了这个修饰后的名字。
通过下面我们可以看出gcc的函数修饰后名字不变。而g的函数修饰后变成【_Z函数长度函数名类型首字母】。采用C语言编译器编译后结果 结论在linux下采用gcc编译完成后函数名字的修饰没有发生改变。采用C编译器编译后结果 结论在linux下采用g编译完成后函数名字的修饰发生改变编译器将函数参数类型信息添加到修改后的名字中。
Windows下名字修饰规则
函数签名修饰后名称int func(int)?funcYAHHZfloat func(float)?funcYAMMzint C :: func(int)?funccAAEHHzint C::C2::func(int)?funcC2cAAEHHzint N::func(int)?funcNYAHHzint N::C::func(int)?funccNAAEHHz
我们以int N::C:func(int)这个函数签名来猜测Visual C的名称修饰规则当然你只须大概了解这个修饰规则就可以了)。修饰后名字由“?”开头接着是函数名由“”符号结尾的函数名;后面跟着由“”结尾的类名“C”和名称空间“N再一个“”表示函数的名称空间结束:第一个“A”表示函数调用类型为“..cdecl”接着是函数的参数类型及返回值由“”结束最后由“Z”结尾。可以看到函数名、参数的类型和名称空间都被加入了修饰后名称这样编译器和链接器就可以区别同名但不同参数类型或名字空间的函数而不会导致link 的时候函数多重定义。
对比Linux会发现windows下vs编译器对函数名字修饰规则相对复杂难懂但道理都是类似的我们就不做细致的研究了。 扩展学习C/C函数调用约定和名字修饰规则–有兴趣好奇的同学可以看看里面 有对vs下函数名修饰规则讲解】 总结 1. 通过这里就理解了C语言没办法支持重载因为同名函数没办法区分。而C是通过函数修饰规则来区分只要参数不同修饰出来的名字就不一样就支持了重载。 2. 如果两个函数函数名和参数是一样的返回值不同是不构成重载的因为调用时编译器没办法区分 感谢你的收看如果文章有错误可以指出我不胜感激让我们一起学习交流如果文章可以给你一个小小帮助可以给博主点一个小小的赞