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

时间:2010-04-21 04:41:56

标签: c++ memory inheritance implementation

假设我有

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

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

C是否将print()代码复制到自身,或者它是否指向指向A部分代码中某处的指针?

当我们覆盖之前的定义时会发生同样的事情,例如在B(在内存级别)?

5 个答案:

答案 0 :(得分:7)

允许编译器执行此操作但是他们选择。但他们通常遵循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的指针。通过查找要在vtable中调用的正确函数来解析对virtual函数的调用。

答案 1 :(得分:3)

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

答案 2 :(得分:3)

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

但是大多数编译器都不会生成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 :(得分:1)

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

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

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

伪汇编指令将如下所示

00B5A2C3   call        print(006de180)

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

答案 4 :(得分:1)

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

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

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

相关问题