我的C++奇迹之旅相遇:支持函数重载的原理
时间:2024-04-01 08:20:39 来源:网络cs 作者:言安琪 栏目:卖家故事 阅读:
文章目录
📝前言🌠 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.h,Stack.cpp ,Test.cpp
:
Stack.h
#pragma once#include<iostream>using namespace std;struct Stack{};void StackInit(struct Stack* ps, int n);
Stack.cpp
#include"Stack.h"void StackInit(struct Stack* ps, int n){cout << "void StackInit(struct Stack* ps, int n)" << endl;}
Test.cpp
#include"Stack.h"int main(){struct Stack st;StackInit(&st, 10);return 0;}
代码运行:
此时程序正常运行,当我把Stack.cpp
里的定义去掉后,如图:
再次编译运行时,代码就会报错,这个错误不是编译错误,而是链接错误,编译错误通常是语法错误。
再看此图,我们来分析这个为什么是链接错误,可知道当Test.cpp,Stack.cpp,Stack.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) | ?func@@YAHH@Z |
float func(float) | ?func@@YAMM@z |
int C :: func(int) | ?func@c@@AAEHH@z |
int C::C2::func(int) | ?func@C2@c@@AAEHH@z |
int N::func(int) | ?func@N@@YAHH@z |
int N::C::func(int) | ?func@c@N@@AAEHH@z |
我们以int N::C:func(int)
这个函数签名来猜测Visual C++
的名称修饰规则(当然,你只须大概了解这个修饰规则就可以了)。修饰后名字由“?”
开头,接着是函数名由“@”
符号结尾的函数名;后面跟着由“@”
结尾的类名“C”
和名称空间“N"
,再一个“@”
表示函数的名称空间结束:第一个“A”
表示函数调用类型为“..cdecl”
,接着是函数的参数类型及返回值,由“@”
结束,最后由“Z”
结尾。可以看到函数名、参数的类型和名称空间都被加入了修饰后名称,这样编译器和链接器就可以区别同名但不同参数类型或名字空间的函数,而不会导致link
的时候函数多重定义。
对比Linux
会发现,windows
下vs
编译器对函数名字修饰规则相对复杂难懂,但道理都是类似的,我们就不做细致的研究了。
扩展学习:C/C++函数调用约定和名字修饰规则–有兴趣好奇的同学可以看看,里面
有对vs
下函数名修饰规则讲解】
🚩总结
1. 通过这里就理解了C语言没办法支持重载,因为同名函数没办法区分。而
C++
是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载。
2. 如果两个函数函数名和参数是一样的,返回值不同是不构成重载的,因为调用时编译器没办法区分
感谢你的收看,如果文章有错误,可以指出,我不胜感激,让我们一起学习交流,如果文章可以给你一个小小帮助,可以给博主点一个小小的赞😘
本文链接:https://www.kjpai.cn/gushi/2024-04-01/151822.html,文章来源:网络cs,作者:言安琪,版权归作者所有,如需转载请注明来源和作者,否则将追究法律责任!
下一篇:返回列表