从汇编的角度揭开C++ this指针的神秘面纱(上)

C++中的this指针一直比较神秘。任何类的对象,都有一个this指针,无处不在。那么this指针的本质究竟是什么?this指针什么时候会被用到?今天通过几段简单的代码,来揭秘一下。

要先揭秘this指针,先来说一下函数调用时参数的传递过程。考虑以下代码:

复制代码
int sum(int i, int j, int k)
{
    return i + j + k;
}

int main()
{
    int a, b, c;
    a = 1;
    b = 2;
    c = 3; 
    sum(a,b,c);
    return 0;
}

这是一段非常简单的函数调用代码。我们生成其汇编代码(x86-64),如下所示:

复制代码
sum(int, int, int):
        pushq   %rbp
        movq    %rsp, %rbp
        movl    %edi, -4(%rbp)
        movl    %esi, -8(%rbp)
        movl    %edx, -12(%rbp)
        movl    -4(%rbp), %edx
        movl    -8(%rbp), %eax
        addl    %eax, %edx
        movl    -12(%rbp), %eax
        addl    %edx, %eax
        popq    %rbp
        ret
main:
        pushq   %rbp
        movq    %rsp, %rbp
        subq    $16, %rsp
        movl    $1, -4(%rbp)
        movl    $2, -8(%rbp)
        movl    $3, -12(%rbp)
        movl    -12(%rbp), %edx
        movl    -8(%rbp), %ecx
        movl    -4(%rbp), %eax
        movl    %ecx, %esi
        movl    %eax, %edi
        call    sum(int, int, int)
        movl    $0, %eax
        leave
        ret

我们重点来关注一下函数参数的传递过程。通过分析main函数的汇编函数, 我用类似于C语言的伪代码解释了一下每一行的意思,辅助理解,如下所示。

复制代码
main        
        pushq   %rbp
        movq    %rsp, %rbp      //rbp = rsp
        subq    $16, %rsp       //rsp -= 16
        movl    $1, -4(%rbp)    //*(rbp-4) = 1
        movl    $2, -8(%rbp)    //*(rbp-8) = 2 
        movl    $3, -12(%rbp)   //*(rbp-12) = 3
        movl    -12(%rbp), %edx //edx =*(rbp-12)
        movl    -8(%rbp), %ecx   //ecx =*(rbp-8) 
        movl    -4(%rbp), %eax   //eax = *(rbp-4)
        movl    %ecx, %esi    //esi = ecx 
        movl    %eax, %edi   //edi = eax
        call    sum(int, int, int)
        movl    $0, %eax
        leave
        ret

在执行这条指令(call sum(int, int, int))前,main函数的栈空间分布如下:

即main函数会存储三个变量: a, b, c. 同时会将其值分别赋值给edi, esi、edx寄存器。那么我们很好奇,将a, b, c三个变量的值赋值给edi, esi、edx寄存器会有什么用呢?我们先来看一下sum函数,我用类似于C语言的伪代码解释了一下每一行的意思,辅助理解,如下所示。

复制代码
sum(int, int, int):
        pushq   %rbp
        movq    %rsp, %rbp         //rbp = rsp
        movl    %edi, -4(%rbp)     //*(rbp-4) = edi
        movl    %esi, -8(%rbp)     //*(rbp-8) = esi
        movl    %edx, -12(%rbp)    //*(rbp-12) = edx
        movl    -4(%rbp), %edx     //edx = *(rbp-4)
        movl    -8(%rbp), %eax     //eax = *(rbp-8)
        addl    %eax, %edx         //edx += eax  
        movl    -12(%rbp), %eax    //eax = *(rbp-12) 
        addl    %edx, %eax         //eax += edx 
        popq    %rbp
        ret

sum函数的栈空间分布如下:

我们重点关注一下这几条指令:

movl %edi, -4(%rbp) //*(rbp-4) = edi

movl %esi, -8(%rbp) //*(rbp-8) = esi

movl %edx, -12(%rbp) //*(rbp-12) = edx

可以看到,在sum函数的栈空间中,其会分配三个存储单元,rbp-4, rbp-8, rbp-12存储1,2,3。而1,2,3这三个值分别又是从edi, esi、edx三个寄存器中拷贝过来的。而这三个寄存器的值又是来自main函数中a, b, c三个变量的赋值。也就是说,这里edi, esi、edx三个寄存器,在函数调用时,完成了参数的传递。 那么这种参数传递的现像是不是有什么约定呢?答案是有的!

在Linux/macOS 等 Unix-like系统中,函数的调用约定标准为**System V AMD64 ABI,**其参数传递机制:

参数位置 整数/指针寄存器 浮点寄存器
第 1 个 RDI XMM0
第 2 个 RSI XMM1
第 3 个 RDX XMM2
第 4 个 RCX XMM3
第 5 个 R8 XMM4
第 6 个 R9 XMM5
第 7+ 个 栈(右→左) XMM6-7

从这个约定中得知,在传递整数时,第一个参数用的是RDI寄存器,第二个参数用的是RSI寄存器,第三个参数用的是RDX寄存器。上面函数调用的例子中正好符合此调用约定(例子中用的是edi, esi、edx三个寄存器传递第1,第2,第3个参数,而edi, esi、edx正好是RDI、RSI、RDX三个寄存器的低32位)。

<this指针揭秘继续...>

相关推荐
奋发向前wcx1 小时前
y1,y2总复习笔记5 2026.7.19
数据结构·笔记·算法
王维同学4 小时前
[自学][Windows C++]RunOnceEx 注册表分组键的安全遍历
c++·windows·安全·开源
战族狼魂4 小时前
高频面试题精选:分治与AI Agent架构
人工智能·算法·大模型·大语言模型
李剑一5 小时前
你用过网易的将军令嘛?它底层实现账号保护的原理相当简单粗暴
算法
Scott9999HH5 小时前
告别流量波动玄学!从法拉第电磁定律到底层 C/C++ 流量累积算法,深度解密工业级电磁流量计选型与开发
c语言·c++·算法
中达瑞和-高光谱·多光谱5 小时前
1nm到8nm光谱分辨率,你的应用该选哪款光谱相机?
算法·高光谱·高光谱相机
脱胎换骨-军哥5 小时前
C++/Rust无缝互操作:混合系统新常态
开发语言·c++·rust
香辣牛肉饭6 小时前
【算法】动态规划 最长公共子序列(LCS)
经验分享·笔记·算法·动态规划
旖-旎6 小时前
LeetCode 279:完全平方数(完全背包)—— 题解
c++·算法·leetcode·动态规划·背包问题