"this" 在 parent class (c++) 中指向哪里?

Where does "this" point to in parent class (c++)?

我有一个关于 C++ 编程的问题。如果我有两个 class 分别称为 parent 和 child。 child 派生自 parent。当我在堆栈或堆上创建 object 时,它必须由两个 class 的构造函数初始化。我发现如果你在调试时进入构造函数,“this”指针指向不同的地址。 childclass的“this”指向object,但是parent的“this”指向什么?

这是我的代码供参考。仅供参考:我在 Ubuntu 20.04

上使用 g++ 10.3 和 gdb 9.2
#include <iostream>

class parent
{
public:
    int attribute0, attribute1, attribute2, attribute3;
    parent():attribute0(0), attribute1(1), attribute2(2), attribute3(3){}
};
class child : parent
{
public:
    int attribute4;
    child(int arg) : parent() {
        attribute4 = arg;
    }
};

int main(){
    auto obj0 = new child(100);
    delete obj0;

    child obj1(80);

    return 0;
}

Where does “this” point to in parent class (c++)?

在所有成员函数中,this总是指向被调用成员函数的对象。这与类型层次结构无关。在基的成员函数中,this 指向基的子对象。

this 总是指向它 (this) 是什么类型的实例的开头。

这听起来可能令人困惑,所以让我们试着想象一下。现在我要描述的不是 C++ 标准的一部分,每个实现都可以根据自己的意愿或需要自由更改它,但通常是这样的某种形式:

-----------------------------------------------------
| pre-parent | parent data | pre-child | child data |
-----------------------------------------------------

parent::parent() 中,this 属于 const parent * 类型,因此指向父数据的开头。但是在 child::child() 中,thisconst child * 类型,因此指向子数据的开头。您可以忽略前父部分和前子部分,这些是实现定义的内存块,用于描述分配、虚拟 table、调试哨兵等

所以现在应该很明显为什么你可以通过父对象的 this 访问整个对象(因为所有子数据都在它之后,因此可以通过常规成员访问访问),但你不能访问整个对象通过子对象 this 而不是先将其转换为完整对象。

为什么两个 this 指针不指向同一个地址也应该很明显(尽管如果父项没有字段并且编译器没有插入任何前子数据,它们可以,所以没有调试模式)。

#include <iostream>
class parent
{
public:
    int attribute0, attribute1, attribute2, attribute3;
    parent():attribute0(0), attribute1(1), attribute2(2), attribute3(3){}
};
class child : parent
{
public:
    int attribute4;
    child(int arg) : parent() {
        attribute4 = arg;
    }
};

int main(){
    std::cout << "attribute0: " << offsetof(parent, attribute0) << "\n";
    std::cout << "attribute1: " << offsetof(parent, attribute1) << "\n";
    std::cout << "attribute2: " << offsetof(parent, attribute2) << "\n";
    std::cout << "attribute3: " << offsetof(parent, attribute3) << "\n";
    std::cout << "attribute4: " << offsetof(child, attribute4) << "\n";
    return 0;
}

如果你真的想知道数据是如何打包的,你可以在成员变量上使用offsetof,看看你的编译器是如何做的(虽然它可能会给你一些警告,所以它不是一种使用 offsetof 的可靠方法,但出于教育目的,这很好)https://godbolt.org/z/6dGxT9e6b

attribute0: 0
attribute1: 4
attribute2: 8
attribute3: 12
attribute4: 16

'this'指向对象在内存中的开始(在本例中,与attribute0相同的位置).

如果您向父级添加虚方法 class,例如

class parent
{
public:
    int attribute0, attribute1, attribute2, attribute3;
    parent():attribute0(0), attribute1(1), attribute2(2), attribute3(3){}
    virtual void func() {}
};

然后你会注意到开头多了8个字节:

attribute0: 8
attribute1: 12
attribute2: 16
attribute3: 20
attribute4: 24

这8bytes是存放指向vtable的指针(对于32位代码,这可能是4byte的偏移量)

如果你真的想看到 'this' 对于父子 class 是相同的,只需打印出值并看一下:

#include <iostream>
class parent
{
public:
    int attribute0, attribute1, attribute2, attribute3;
    parent():attribute0(0), attribute1(1), attribute2(2), attribute3(3){}
    void printParent() {
        std::cout << "parent " << this << ' ' << &attribute0 << "\n";
    }
};
class child : public parent
{
public:
    int attribute4;
    child(int arg) : parent() {
        attribute4 = arg;
    }
    void printChild() {
        std::cout << "child  " << this << ' ' << &attribute4 << "\n";
    }
};

int main(){
    child* c = new child(4);
    c->printParent();
    c->printChild();
    return 0;
}

打印:

parent 0x1f98eb0 0x1f98eb0
child  0x1f98eb0 0x1f98ec0

所以,parent的'this',child的'this',attribute0,都指向同一个内存位置。 attribute4的地址恰好在'this'之后的16bytes,这是offsetof前面告诉我们的