【问题标题】:Double indirection in C++ vtablesC ++ vtables中的双重间接
【发布时间】:2013-04-17 08:05:26
【问题描述】:

我编写了这个非常简单的 C++ 程序,我想知道为什么编译器会跨两个指针取消引用来布置 vtable。这是 C++ 程序:

class Foo {
 public:
  virtual void bar() {
  }
};

int main(int argc, char *arv[]) {
  Foo foo;
  Foo *foo_p(&foo);
  foo_p->bar();
}

现在,我可以查看编译器生成的程序集了:

$ g++ -ggdb -Wall -O0 -S test.cpp

以下是相关部分:

    .loc 1 9 0 
    leaq    -16(%rbp), %rax  # put the address of 'foo' in %rax
    movq    %rax, %rdi       # use it as the first argument of the following function
    call    _ZN3FooC1Ev      # call the Foo constructor
    .loc 1 10 0
    leaq    -16(%rbp), %rax  # put the address of 'foo' in %rax
    movq    %rax, -24(%rbp)  # create 'foo_p' on the stack
    .loc 1 11 0
    movq    -24(%rbp), %rax  # load 'foo_p' into %rax
    movq    (%rax), %rax     # dereference the pointer, put it in %rax
                             # %rax now holds the hidden pointer in 'foo', which is the vtable pointer
    movq    (%rax), %rdx     # dereference the pointer ::again:: (with an offset of 0), put it in %rdx
                             # %rdx now holds a function pointer from the vtable
    movq    -24(%rbp), %rax  # create the 'this' pointer (== foo_p) and put it in %rax
    movq    %rax, %rdi       # use the 'this' pointer as the first argument to the following function
    call    *%rdx            # call Foo::bar (via the vtable)

为什么第二个指针解引用是必要的?为什么对象中“隐藏”的 vtable 指针不直接指向 vtable?

edit:它 ::is:: 直接指向 vtable。我只是对我的指针感到困惑:-P

【问题讨论】:

  • +1 这样准备就没有愚蠢的问题

标签: c++ pointers assembly function-pointers vtable


【解决方案1】:
movq    -24(%rbp), %rax  # load 'foo_p' into %rax
movq    (%rax), %rax     # fetch VTABLE
movq    (%rax), %rdx     # fetch function `bar` from VTABLE. 

如果您将baz(或kerflunk)函数作为第二个函数添加到您的类中,您会看到更好,您会看到第二个提取位于8 到VTABLE 中。

你可以看到类内部的结构是这样的(注意这是“出于说明目的,并非旨在成为现实”)

struct VTABLE
{
    void (*bar)();
};

struct Foo
{
    VTABLE *vtable;
};

在 Foo 的构造函数 [即使你没有声明它也存在],有一段代码可以做到:

this->vtable = &VTABLE_Foo; 

以及编译器在某处所做的(再次,为了说明目的,名称肯定不同):

VTABLE VTABLE_Foo = { foo::bar };

所以打电话给bar,我们会这样做:

foo_p->vtable->bar(foo_p);

编译器显示的是:

void (*temp)() = foo_p->vtable->bar;
temp(foo_p);

这几乎可以肯定是使用-O0 的结果,如果您使用优化进行编译,编译器会更直接地执行此操作(包括可能意识到在这种情况下它不需要 vtable 并内联不需要的函数'不做任何事情,从而完全消除呼叫)。

【讨论】:

  • 很高兴了解它是如何工作的,只要你不认为这是理所当然的,这就是它总是如何工作的,因为编译器被允许这样做“任何实际给出的方式正确的行为”。
【解决方案2】:

第一个将vtable指针放入%rax,第二个将函数指针放入%rdx

【讨论】:

    猜你喜欢
    • 2020-02-15
    • 1970-01-01
    • 2011-04-07
    • 2011-09-05
    • 2012-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-06
    相关资源
    最近更新 更多