从几个例子看函数重载

常用的几类函数

#include <iostream>
 
 void func(int a){
     std::cout << "global func: " << a << std::endl;
 }
 namespace test{
     void func(int a){ 
         std::cout << "namespace test func: " << a << std::endl;
     }   
 };
 
 class Demo {
     public:
     void func(int a){
         i = 1;
         std::cout << "class member func: " << a << std::endl;
     }   
     int i{0};
 };
 
 int main()
 {
     func(1);
     test::func(1);
     Demo d;
     d.func(1);
     return 0;
 }

函数的重载

对于程序的整个生命周期如下:编写程序(源代码)-> 预处理 -> 编译(扫描->词法分析->语法分析->语义分析->源代码优化->代码生成->目标代码优化) -> 汇编 -> 链接 -> 运行;
编译器将源代码编译产生目标文件时,符号名与相应的变量和函数进行对应,在比较早的时候,两者是一样的,比如汇编源代码中包含一个函数foo,编译成目标文件时,对应的符号名也是foo;C语言的规则比较类似,将全局变量和函数的符号名前加上下划线"_";
比如:

 extern "C" {
 void func(int a){ 
     std::cout << "global func: " << a << std::endl;
 }
 }

编译成的符号如下(readelf -s main | grep func):

Num: Value Size Type Bind Vis Ndx Name
60: 00000000000011e9 74 FUNC GLOBAL DEFAULT 16 func

由于C语言的符号生成规则如此,所以不支持函数的重载。也因此只要符号相同(相同的函数名、变量名会编译失败)

那么对于C++,它拥有类、继承、虚机制、重载、名称空间等这些特性,使符号管理(符号生成规则)更为复杂。为了支持这些特性,发明了Name Decoration 或 Name Mangling机制

看如下函数的符号是如何:

 int func(int){return 0;};
 float func(float){return 0;};
 class C{
     public:
     int func(int){return 0;};  
     class C2 {
         public:
         int func(int){return 0;};
     };  
 };
 namespace N{
 int func(int){return 0;};
 class C{
     public:
     int func(int){return 0;};  
 };
 };
 int main(){
     func(1);
     func(1.0f);
     C c;
     c.func(1);
     C::C2 c2;
     c2.func(1);
     
     N::func(1);
     N::C c1; 
     c1.func(1);
     
     return 0;
 }

命令:
编译: g++ main.cpp -o main
查看符号:readelf -s main | grep func

统计:

函数签名修饰后名称(符号名)
int func(int)_Z4funci
float func(float)_Z4funcf
int C::func(int)_ZN1C4funcEi
int C::C2::func(int)_ZN1C2C24funcEi
int N::func(int)_ZN1N4funcEi
int N::C::func(int)_ZN1N1C4funcE

基本规则如下:

所有符号以_Z开头,对于嵌套的名字(名称空间或类里面),后面紧跟着“N”, 然后是各个名称空间和类的名字,每个名字前是名字字符串长度,再以E结尾,对于函数来说,它的参数列表紧跟在“E”后面,对于int类型来说,就是字母i。

  <type> ::= <builtin-type>
         ::= <qualified-type>
         ::= <function-type>
         ::= <class-enum-type>
         ::= <array-type>
         ::= <pointer-to-member-type>
         ::= <template-param>
         ::= <template-template-param> <template-args>
         ::= <decltype>
         ::= P <type>        # pointer
         ::= R <type>        # l-value reference
         ::= O <type>        # r-value reference (C++11)
         ::= C <type>        # complex pair (C99)
         ::= G <type>        # imaginary (C99)
         ::= <substitution>  # See Compression below

<CV-qualifiers>      ::= [r] [V] [K] 	  # restrict (C99), volatile, const

<builtin-type>   ::= v	# void
				 ::= w	# wchar_t
				 ::= b	# bool
				 ::= c	# char
				 ::= a	# signed char
				 ::= h	# unsigned char
				 ::= s	# short
				 ::= t	# unsigned short
				 ::= i	# int
				 ::= j	# unsigned int
				 ::= l	# long
				 ::= m	# unsigned long
				 ::= x	# long long, __int64
				 ::= y	# unsigned long long, __int64
				 ::= n	# __int128
				 ::= o	# unsigned __int128
				 ::= f	# float
				 ::= d	# double
				 ::= e	# long double, __float80
				 ::= g	# __float128
				 ::= z	# ellipsis
                 ::= Dd # IEEE 754r decimal floating point (64 bits)
                 ::= De # IEEE 754r decimal floating point (128 bits)
                 ::= Df # IEEE 754r decimal floating point (32 bits)
                 ::= Dh # IEEE 754r half-precision floating point (16 bits)
                 ::= DF <number> _ # ISO/IEC TS 18661 binary floating point type _FloatN (N bits)
                 ::= DB <number> _        # C23 signed _BitInt(N)
                 ::= DB <instantiation-dependent expression> _ # C23 signed _BitInt(N)
                 ::= DU <number> _        # C23 unsigned _BitInt(N)
                 ::= DU <instantiation-dependent expression> _ # C23 unsigned _BitInt(N)
                 ::= Di # char32_t
                 ::= Ds # char16_t
                 ::= Du # char8_t
                 ::= Da # auto
                 ::= Dc # decltype(auto)
                 ::= Dn # std::nullptr_t (i.e., decltype(nullptr))
				 ::= u <source-name> [<template-args>] # vendor extended type

函数的名称修饰是否包含返回值,有如下解释:
Whether the mangling of a function type includes the return type depends on the context and the nature of the function. The rules for deciding whether the return type is included are:

  1. Template functions (names or types) have return types encoded, with the exceptions listed below.
  2. Function types not appearing as part of a function name mangling, e.g. parameters, pointer types, etc., have return type encoded, with the exceptions listed below.
  3. Non-template function names do not have return types encoded.

The exceptions mentioned in (1) and (2) above, for which the return type is never included, are

  1. Constructors.
  2. Destructors.
  3. Conversion operator functions, e.g. operator int.

const重载

先看下面几个例子:
1:

 void func(int a){ 
     std::cout << "global func: " << a << std::endl;
 }
 void func(const int a){
     std::cout << "global func: " << a << std::endl;
 }

和2:

 void func(int& a){ 
     std::cout << "global func: " << a << std::endl;
 }
 void func(const int& a){ 
     std::cout << "global func: " << a << std::endl;
 }

先猜测下上述两个例子表现如何?
结论:例1编译失败(error: redefinition of ‘void func1(int)’ 相同的函数符号相同),例2编译成功;为什么呢?
从编译出的符号进行倒推:

函数原型符号
void func(int)_Z4funci
void func(const int)_Z4funci
void func(int&)_Z4funcRi
void func(const int&)_Z4funcRKi

由于void func(int) 和void func(const int) 生成的符号相同,那么必然会编译失败;其思想在于,当函数参数是基本的value时,都是将实参拷贝一份然后传入函数,那么const与否是不会影响到调用方,所以无需区分这两种情况(编译器规则)。

再看一个例子:

 class Demo {
     public:
     void func(int a){ 
         i = 1;
         std::cout << "class member func: " << a << std::endl;
     }   
     void func(int a) const {
     	 // i = 1; // compile fail 
         std::cout << "class member const func: " << a << std::endl;
     }   
     int i{0};
 };

编译ok;

函数原型符号
void const Demo::func(int)_ZNK4Demo4funcEi
void Demo::func(int)_ZN4Demo4funcEi

第二个成员函数的const是修饰Demo object的。

const修饰返回值时,由于“Non-template function names do not have return types encoded”;对于这种情况,返回值使用和不使用const修饰生成的符号是相同的,若同时存在则会编译失败。StackOverflow 解答

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值