欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

从CPU的视角看C++的构造函数和this指针

最编程 2024-07-09 07:06:26
...

从汇编角度,清晰的去看构造函数和this指针到底是个什么东西呢?也许可以解决你的一点小疑问

首先写一个很简单的代码demo:

class A{
public:
    int a;
    A(){
        ;
    }
    void seta(int _a){
        a=_a;
    }
    A* getA(){
        return this;
    }
};

int fun1(int px){
    return px;
}

int main(){
    A aa;
    aa.seta(8); 
    aa.getA();
}

mov rbp, rsp:设置新的基址指针,指向当前栈帧。

mov QWORD PTR [rbp-8], rdi:将 this 指针(存储在 rdi 中)保存到栈上。

在seta中的部分汇编:

mov rax, QWORD PTR [rbp-8]:将 this 指针的值加载到 rax 中。

mov DWORD PTR [rax], edx:将 edx(即 _a 的值)存储到 this->a 中。

我们从类成员函数和构造函数中都能看到 两句关于参数的汇编,同理普通函数中px参数也有着相同的汇编,那么很显然,对于cpu而言,this指针仅仅是一个参数而已。C++语法糖对于this做了隐藏处理,因此我们在使用的时候才会无感。

而这个有这个this往往可以认为函数的完整使用是这样:

    A aa;
    aa.seta(8); 


====
    A::seta(&aa,8) 当然这么调用是错误的,看着很像类静态函数

this 指针的创建

  • 函数调用前创建:当成员函数被调用时,this 指针在调用成员函数之前被设置为当前对象的地址。也就是说,在成员函数执行之前,this 指针已经被创建并指向调用该函数的对象。

this 指针的销毁

  • 函数调用结束后销毁:在成员函数执行完毕并返回后,this 指针的生命周期就结束了。因为 this 只是一个指向对象的指针,当函数返回时,不再需要这个指针,函数调用上下文会自动清理这个隐式参数。

 

学习C++的友友大概率会知道这么一句话:静态成员函数不属于类的某个具体对象,而是属于整个类。这意味着静态成员函数不能访问非静态成员变量和成员函数,因为它们没有 this 指针。针对这句话我们也可以通过cpu视角进行观察

而this 指针的存在条件

  • 对象实例调用:只有当对象实例调用非静态成员函数时,this 指针才会被传递给该函数。
  • 隐式参数:在非静态成员函数内部,编译器会自动添加一个隐式的 this 指针参数,用于引用调用该函数的对象

观察上图,你就会看到 静态成员函数中没有this指针,即也就没法去调用相关的成员函数和数据,如果我们想要通过静态成员函数去调用对象相关的数据,可以通过黄色框框的这种写法。你会发现这种写法的汇编同成员函数的汇编相同,也就是说this存的就是传递进去的这个对象的地址。

也就有了成员函数的完整版应该是:void setp(A* this,int p);是不是看着参数和示例第二个静态成员函数一样~

cpu眼中构造函数与普通函数没有任何区别:

通过上面的汇编也能看明白,构造含有this指针,且对于cpu来说他与普通函数是相同的

派生类构造函数总会调用基类的构造函数

  • mov QWORD PTR [rbp-8], rdi:将 this 指针(在 rdi 中)保存到局部变量中。
  • mov rax, QWORD PTR [rbp-8]:将 this 指针从局部变量中加载到 rax 中。
  • mov rdi, rax:将 this 指针(现在在 rax 中)移动到 rdi 中,以便调用基类构造函数时使用。
  • call A::A() [base object constructor]:调用基类 A 的构造函数。此时,this 指针指向 B 对象,但调用的是 A 的构造函数,因此它会正确初始化 A 的部分。
  • mov rax, QWORD PTR [rbp-8]:再次将 this 指针从局部变量中加载到 rax 中。
  • mov DWORD PTR [rax+4], 1:将 1 赋值给 this->a。这里的 [rax+4] 指的是b 位于对象内偏移量 4 的位置。 (因为B继承A,B所以内存结构上,前四个字节是A.a ,然后才是B.a ,所以是移动到偏移4位置之后在赋值)