常用的几类函数
#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:
- Template functions (names or types) have return types encoded, with the exceptions listed below.
- 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.
- 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
- Constructors.
- Destructors.
- 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编译失败,例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 {
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 解答
|