我写了这个非常简单的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?
编辑:It :: is ::直接指向vtable。我只是对我的指针感到困惑:-P
答案 0 :(得分:6)
movq -24(%rbp), %rax # load 'foo_p' into %rax
movq (%rax), %rax # fetch VTABLE
movq (%rax), %rdx # fetch function `bar` from VTABLE.
如果您将baz
(或kerflunk
)函数作为第二个函数添加到您的类中,您会看到更好的效果,您会看到第二个fetch位于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并内联函数)它没有做任何事情,因此彻底消除了呼叫)。
答案 1 :(得分:2)
第一个将 vtable 指针放入%rax
,第二个将函数指针放入%rdx
。