C++ vtables中的双重间接

Lit*_*rum 4 c++ assembly pointers function-pointers vtable

我写了这个非常简单的C++程序,我想知道为什么编译器会在两个指针引用之间列出vtable.这是C++程序:

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

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

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

$ g++ -ggdb -Wall -O0 -S test.cpp
Run Code Online (Sandbox Code Playgroud)

以下是相关部分:

    .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)
Run Code Online (Sandbox Code Playgroud)

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

编辑:It :: is ::直接指向vtable.我只是对我的指针感到困惑:-P

Mat*_*son 6

movq    -24(%rbp), %rax  # load 'foo_p' into %rax
movq    (%rax), %rax     # fetch VTABLE
movq    (%rax), %rdx     # fetch function `bar` from VTABLE. 
Run Code Online (Sandbox Code Playgroud)

如果你将一个baz(或kerflunk)函数作为第二个函数添加到你的类中,你会看到它更好,你会看到第二个fetch 8进入VTABLE.

您可以在类中看到类似这样的结构(注意这是"出于说明目的,不是为了实现")

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

struct Foo
{
    VTABLE *vtable;
};
Run Code Online (Sandbox Code Playgroud)

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

this->vtable = &VTABLE_Foo; 
Run Code Online (Sandbox Code Playgroud)

编译器已经完成的某个地方(再次,为了说明的目的,名称肯定是不同的):

VTABLE VTABLE_Foo = { foo::bar };
Run Code Online (Sandbox Code Playgroud)

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

foo_p->vtable->bar(foo_p);
Run Code Online (Sandbox Code Playgroud)

编译器显示的是:

void (*temp)() = foo_p->vtable->bar;
temp(foo_p);
Run Code Online (Sandbox Code Playgroud)

这几乎肯定是使用的结果-O0,如果你使用优化进行编译,编译器会更直接地进行编译(包括可能意识到它在这种情况下不需要vtable并且内联函数不做任何事情,因此彻底消除呼叫).