问题 如何在内存级别实现继承?


假设我有

class A           { public: void print(){cout<<"A"; }};
class B: public A { public: void print(){cout<<"B"; }};
class C: public A {                                  };

如何在内存级别实现继承?

是否 C 复制 print() 代码本身或它有一个指向它的指针 A 部分代码?

当我们覆盖先前的定义时,例如在同一事物中,同样的事情是如何发生的 B (在内存级别)?


2353
2018-04-21 04:41


起源

你是不是要从C级继承“C级”? - David Smith
正确答案中有很多细节。如果您愿意购买/阅读有关该主题的书籍,您可以获得Stan Lippman的C ++对象模型。 - R Samuel Klatchko
难道你不是说“打印”是虚拟的吗? - squelart
@squelart:不,我没有。我想先了解正常的继承。 - Moeb


答案:


编译器允许他们选择实现它。但他们通常遵循CFront的旧实施。

对于没有继承的类/对象

考虑:

#include <iostream>

class A {
    void foo()
    {
        std::cout << "foo\n";
    }

    static int bar()
    {
        return 42;
    }
};

A a;
a.foo();
A::bar();

编译器将最后三行更改为类似于:

struct A a = <compiler-generated constructor>;
A_foo(a); // the "a" parameter is the "this" pointer, there are not objects as far as
          // assembly code is concerned, instead member functions (i.e., methods) are
          // simply functions that take a hidden this pointer

A_bar();  // since bar() is static, there is no need to pass the this pointer

曾几何时,我会猜到这是通过每个函数中的指针来处理的 A 对象已创建。但是,这种方法意味着每一个方面 A 对象将包含相同的信息(指向同一函数的指针),这会浪费大量空间。编译器很容易处理这些细节。

对于具有非虚拟继承的类/对象

当然,那不是你问的那个。但是我们可以将它扩展到继承,这是你所期望的:

class B : public A {
    void blarg()
    {
        // who knows, something goes here
    }

    int bar()
    {
        return 5;
    }
};

B b;
b.blarg();
b.foo();
b.bar();

编译器将最后四行转换为:

struct B b = <compiler-generated constructor>
B_blarg(b);
A_foo(b.A_portion_of_object);
B_bar(b);

有关虚拟方法的说明

当你谈论时,事情变得有点棘手 virtual 方法。在这种情况下,每个类都获得一个特定于类的指针到函数的数组,每个类都有一个这样的指针 virtual 功能。该数组称为vtable(“虚拟表”),每个创建的对象都有一个指向相关vtable的指针。打电话给 virtual 通过查找在vtable中调用的正确函数来解决函数问题。


7
2018-04-21 06:28



+1用于将名称修改和编译过程纳入讨论。除了使用虚拟方法变得更复杂(不一定通过vtable)之外,它还会使多重和虚拟继承变得更加混乱......可能超出了问题的范围。 - David Rodríguez - dribeas
是的,我不想深入了解多重继承或虚拟继承的细节。 - Max Lybbert
“创建的每个对象都有一个额外的隐藏表“不,每个 目的 创造得到 指向vtable的指针 (虚拟表),称为vptr。每个动态类都有一个或多个vtable。 “vtable,它是一个指向函数的指针数组“不,它更像是一个具有不同信息的结构(许多指向函数的指针(有时带有偏移量),指向char数组的指针,有时是许多偏移量,指向其他数据结构的指针......) - curiousguy
@curiousguy:有效点,我改进了措辞。 - Max Lybbert


查看 C ++ ABI 有关内存布局的任何问题。它被标记为“Itanium C ++ ABI”,但它已成为大多数编译器实现的C ++标准ABI。


3
2018-04-21 05:07





我不认为标准有任何保证。编译器可以选择制作多个函数副本,组合碰巧在完全不同类型上访问相同内存偏移的副本等。内联只是其中一个更明显的例子。

但是大多数编译器都不会生成A :: print代码的副本,以便在通过C实例调用时使用。在C的编译器内部符号表中可能有一个指向A的指针,但在运行时,您很可能会看到:

A a; C c; a.print(); c.print();

已经变成了以下几点:

A a;
C c;
ECX = &a; /* set up 'this' pointer */
call A::print; 
ECX = up_cast<A*>(&c); /* set up 'this' pointer */
call A::print;

两个调用指令都跳转到代码存储器中的完全相同的地址。

当然,因为你已经要求编译器内联 A::print,代码很可能被复制到每个调用站点(但因为它取代了 call A::print,它实际上并没有增加程序大小)。


3
2018-04-21 05:36



+1在原始示例中, B 和 C 不相关,但将其解释为 A 和 C 这个论点成立了。 - David Rodríguez - dribeas
是的...我只检查过继承是公共的而不是虚拟的,而不是继承了哪个类。哎呀。固定。 - Ben Voigt


对象中不存储任何信息来描述成员函数。

aobject.print();
bobject.print();
cobject.print();

编译器只会将上面的语句转换为直接调用函数print,基本上没有任何东西存储在对象中。

伪汇编指令将如下所示

00B5A2C3   call        print(006de180)

由于print是成员函数,因此您将拥有一个额外的参数;这个指针。这将作为函数的每个其他参数传递。


1
2018-04-21 04:55



@yesraaj:我指的是类代码。是否 C class 复制 print() 定义到自己或只是使用指针 print()在 A class? - Moeb
@cambr我不认为类会被存储,因为它在任何地方。任何方式让我们等待更多的答案 - yesraaj


在你的例子中,没有任何东西的复制。通常,对象不知道它在运行时所处的类 - 当程序出现时会发生什么 编译,编译器说“嘿,这个变量是C类型的,让我们看看是否有C :: print()。不,好吧,A :: print()怎么样?是的?好吧,打电话给那个!”

虚方法的工作方式不同,因为指向正确函数的指针存储在“vtable”中* 在对象中引用。如果你直接使用C,那仍然无关紧要,因为它仍然遵循上述步骤。但是对于指针,它可能会说“哦,C :: print()?地址是vtable中的第一个条目。”并且编译器插入指令以在运行时获取该地址并调用它。

*从技术上讲,这不是必须的。我很确定你不会在“vtable”的标准中找到任何提及;它的定义是特定于实现的。它恰好是第一个C ++编译器使用的方法,并且恰好比其他方法更好地工作,因此几乎每个C ++编译器都使用它。


1
2018-04-21 05:12



“通常,对象不知道它在运行时的类“动态类的一个实例在运行时肯定知道它的类型! - curiousguy
@curiousguy:在C ++中,类 甚至不存在 在运行时。它只是字节,指针和烟雾。无论如何,你说的这些“动态课程”是什么? - cHao
“类在运行时甚至不存在“不,不, typeid(*this).name() 当然确实存在于运行时“无论如何,你说的这些“动态课程”是什么?“带有vptr的类(用于虚函数或用于虚基类) - curiousguy
@curiousguy:是的,typeid可以在运行时存在。但是,类不会。第一个C ++编译器最初是作为一个将C ++变成C的翻译器 - 当然,它不包含任何内置的“类”概念。对象成为结构体,成员函数成为全局函数,名称略有损坏,虚函数成为vtable条目。在很大程度上,虽然他们现在直接生成机器代码,但他们 仍然 以这种方式工作 - CPU很少知道或关心“类”。就像我说的,它是所有字节和指针......加上一点点手和误导。 - cHao
至于当类不再存在时RTTI如何工作,我假设有一个指向vtable中预先存在的(已编译的)type_info的指针(可能是第一个条目)。似乎最简单的事情。 - cHao