从汇编的角度揭秘C++函数重载,原来这么简单

函数重载是指在同一个作用域内,有多个同名函数,但是它们的形参列表不同。在调用时,根据不同的实参,调用相应的函数。函数重载是一种静态多态形式。我们先来看一个函数重载的例子,然后分析其背后的原理。请看下面这段代码:

cpp 复制代码
#include <stdio.h>

int sum(int a, int b) 
{
    int ret =  a + b; 
    printf("int type, sum = %d\r\n", ret); 
    return ret;
}

float sum(float a, float b)
{
    float ret =  a + b; 
    printf("float type, sum = %f\r\n", ret); 
    return ret;
}

int main()
{
    int i = 1, j = 2;
    float h = 1.0, k = 2.0; 

    printf("first call\r\n");
    sum(i, j);

    printf("second call\r\n");
    sum(h, k);

    return 0;
}

上面这段代码定义了两个同名函数:sum,但是形参不一样。在main函数中分别用两个int形,两个float型的变量去调用sum函数。期望的结果是:两个int型变量作为型参,会调用第一个sum函数。两个float型变量作为型参,会调用第二个sum函数。编译以及运行结果:

从输出结果来看,第一次调用第一个sum函数,第二次调用第二个sum函数。实现了静态多态的一个实现。其背后的原理是什么呢?为什么在同一个作用域内,有两个相同名的函数不会有编译错误呢?查看一下可执行文件的汇编代码:

cpp 复制代码
objdump -s -d function_loading > result

vi result

找到main函数汇编代码:

cpp 复制代码
00000000000011f7 <main>:
    11f7:       f3 0f 1e fa             endbr64
    11fb:       55                      push   %rbp
    11fc:       48 89 e5                mov    %rsp,%rbp
    11ff:       48 83 ec 10             sub    $0x10,%rsp
    1203:       c7 45 f0 01 00 00 00    movl   $0x1,-0x10(%rbp)
    120a:       c7 45 f4 02 00 00 00    movl   $0x2,-0xc(%rbp)
    1211:       f3 0f 10 05 33 0e 00    movss  0xe33(%rip),%xmm0        # 204c <_IO_stdin_used+0x4c>
    1218:       00
    1219:       f3 0f 11 45 f8          movss  %xmm0,-0x8(%rbp)
    121e:       f3 0f 10 05 2a 0e 00    movss  0xe2a(%rip),%xmm0        # 2050 <_IO_stdin_used+0x50>
    1225:       00
    1226:       f3 0f 11 45 fc          movss  %xmm0,-0x4(%rbp)
    122b:       48 8d 05 fe 0d 00 00    lea    0xdfe(%rip),%rax        # 2030 <_IO_stdin_used+0x30>
    1232:       48 89 c7                mov    %rax,%rdi
    1235:       e8 26 fe ff ff          callq  1060 <puts@plt>
    123a:       8b 55 f4                mov    -0xc(%rbp),%edx
    123d:       8b 45 f0                mov    -0x10(%rbp),%eax
    1240:       89 d6                   mov    %edx,%esi
    1242:       89 c7                   mov    %eax,%edi
    1244:       e8 20 ff ff ff          callq  1169 <_Z3sumii>
    1249:       48 8d 05 ec 0d 00 00    lea    0xdec(%rip),%rax        # 203c <_IO_stdin_used+0x3c>
    1250:       48 89 c7                mov    %rax,%rdi
    1253:       e8 08 fe ff ff          callq  1060 <puts@plt>
    1258:       f3 0f 10 45 fc          movss  -0x4(%rbp),%xmm0
    125d:       8b 45 f8                mov    -0x8(%rbp),%eax
    1260:       0f 28 c8                movaps %xmm0,%xmm1
    1263:       66 0f 6e c0             movd   %eax,%xmm0
    1267:       e8 38 ff ff ff          callq  11a4 <_Z3sumff>
    126c:       b8 00 00 00 00          mov    $0x0,%eax
    1271:       c9                      leaveq
    1272:       c3                      retq
    1273:       66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
    127a:       00 00 00
    127d:       0f 1f 00                nopl   (%rax)

这里大家如果看不懂或者不熟悉汇编,也没关系。重点关注这两条命令:

1244: e8 20 ff ff ff callq 1169 <_Z3sumii>

1267: e8 38 ff ff ff callq 11a4 <_Z3sumff>

call指令用于函数的调用,所以这两条指令分别是调用_Z3sumii以及_Z3sumff函数。对照main函数源码,可以看出,是这两条源码:

sum(i, j);

sum(h, k);

但是这里有点奇怪,源代码中是sum函数,在汇编里面怎么是_Z3sumii以及_Z3sumff呢。其实原理是,c++编译器会对函数名进行修饰。sum函数的后缀ii以及ff分别是代表形参分别是int和int,以及float和float。即一个sum函数经过修饰之后,变成了两个不同名的函数_Z3sumii以及_Z3sumff。为了验证这个结论,可用如下命令:

> nm function_loading | grep sum

输出如下:

00000000000011a4 T _Z3sumff

0000000000001169 T _Z3sumii

说明function_loading中定义了_Z3sumff以及_Z3sumii这两个符号。但这两个符号是修饰后的结果,那么修饰前的符号是怎么样的呢?通过以下命令:

>c++filt _Z3sumff

输出如下:

sum(float, float)

>c++filt _Z3sumii

输出如下:

sum(int, int)

跟源码正好匹配上。说明_Z3sumff是sum(float, float)修饰后的结果,_Z3sumii是sum(int, int)修饰后的结果。验证了前面的结论:sum函数的后缀ii以及ff分别是代表形参分别是int和int,以及float和float。即一个sum函数经过修饰之后,变成了两个不同名的函数_Z3sumii以及_Z3sumff。

从此,我们可以看出C++的函数重载的原理是C++编译器会对符号进行修饰。虽然函数名是一样的,但是经过修饰之后,函数名就不一样了。也就不会造成符号表冲突。

此文章的B站视频:用汇编深入剖析C++函数重载机制,原来这么简单_哔哩哔哩_bilibili

相关推荐
R-G-B6 分钟前
【25】MFC入门到精通——MFC静态文本框 中字符串 连续输出 不覆盖先前的文本 换行输出
c++·mfc·mfc静态文本框输出字符串·mfc静态文本框连续输出字符串·mfc静态文本框换行输出字符串
FFZero12 小时前
【C++/Lua联合开发】 (二) Lua调用C++函数
c++·junit·lua
CoderCodingNo3 小时前
【GESP】C++四级真题 luogu-B4068 [GESP202412 四级] Recamán
开发语言·c++·算法
一个不知名程序员www3 小时前
算法学习入门---双指针(C++)
c++·算法
Maple_land4 小时前
常见Linux环境变量深度解析
linux·运维·服务器·c++·centos
Larry_Yanan4 小时前
QML学习笔记(四十四)QML与C++交互:对QML对象设置objectName
开发语言·c++·笔记·qt·学习·ui·交互
Want5954 小时前
C/C++大雪纷飞①
c语言·开发语言·c++
Mr_WangAndy4 小时前
C++设计模式_行为型模式_策略模式Strategy
c++·设计模式·策略模式·依赖倒置原则
LoveXming5 小时前
Chapter11—适配器模式
c++·设计模式·适配器模式·开闭原则
Benny_Tang5 小时前
题解:P7989 [USACO21DEC] Bracelet Crossings G
c++·算法