2

我有一个疑问:我可以声明一个指向类成员函数的指针

void (*MyClass::myFunc)(void);

我可以声明一个指向类成员变量的指针

int (MyClass::*var);

我的问题是:一个对象(由成员函数和成员变量组成)在内存(asm 级别)中是如何构造的?

我不确定,因为除了多态性和运行时虚函数外,即使没有对象,我也可以声明指向成员函数的指针,这意味着代码函数在多个类之间共享(尽管它们需要 *this 指针才能工作适当地)

但是变量呢?即使没有对象实例,我怎么能声明指向成员变量的指针?当然我需要一个来使用它,但是我可以在没有对象的情况下声明一个指针这一事实让我认为内存中的一个类对象用指向其他内存区域的指针来表示它的变量。

我不确定我是否正确解释了我的疑问,如果不是让我知道,我会尝试更好地解释它

4

1 回答 1

4

类非常简单地存储在内存中 - 几乎与结构相同。如果您检查存储类实例的位置的内存,您会注意到它的字段只是一个接一个地打包。

但是,如果您的类具有虚拟方法,则会有所不同。在这种情况下,存储在类实例中的第一件事是指向虚拟方法表的指针,它允许虚拟方法正常工作。您可以在 Internet 上阅读更多相关信息,这是一个更高级的主题。幸运的是,您不必担心,编译器会为您完成所有工作(我的意思是,处理 VMT,不用担心)。

让我们来看看方法。当你看到:

void MyClass::myFunc(int i, int j) { }

实际上编译器将它转换成类似的东西:

void myFunc(MyClass * this, int i, int j) { }

当你打电话时:

myClassInstance->myFunc(1, 2);

编译器生成以下代码:

myFunc(myClassInstance, 1, 2);

请记住,这是一种简化——有时它比这更复杂一些(尤其是当我们讨论虚拟方法调用时),但它或多或少地显示了编译器如何处理类。如果您使用诸如 WinDbg 之类的低级调试器,您可以检查方法调用的参数,您会看到,第一个参数通常是指向您调用该方法的类实例的指针。

现在,所有相同类型的类共享它们的方法的二进制文件(编译代码)。因此,为每个类实例复制它们是没有意义的,因此内存中只有一个副本,所有实例都使用它。现在应该清楚了,为什么即使没有类的实例,也可以获得指向方法的指针。

但是,如果要调用保存在变量中的方法,则始终必须提供一个类实例,该实例可以通过隐藏的“this”参数传递。


编辑:回应评论

您可以在另一个 SO question中阅读有关指针成员的更多信息。我猜,指向成员的指针存储了类实例的开头和指定字段之间的差异。当您尝试使用指向成员的指针检索字段的值时,编译器会定位类实例的开头并移动存储在指向成员的指针中的字节数以到达指定的字段。

每个类实例都有自己的非静态字段副本——否则它们对我们没有多大用处。

请注意,与指向方法的指针类似,您不能直接使用指向成员的指针,您必须再次提供类实例。

我所说的证明是有序的,所以这里是:

class C
{
public:
    int a;
    int b;
};

// Disassembly of fragment of code:

    int C::*pointerToA = &C::a;
00DB438C  mov         dword ptr [pointerToA],0  
    int C::*pointerToB = &C::b;
00DB4393  mov         dword ptr [pointerToB],4  

你能看到pointerToA和pointerToB中存储的值吗?字段a与类实例的开头相距 0 个字节,因此值 0 存储在 pointerToA 中。另一方面, fieldb存储在 field 之后a,它有 4 个字节长,因此 value 4 存储在 pointerToB 中。

于 2013-03-03T13:41:53.390 回答