以下课程,
class A
{
public:
char VarA;
int VarB;
virtual ~A(){}
};
g ++ fdump-class-hierarchy为我提供了Vtable,
Vtable for A
A::_ZTV1A: 4u entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI1A)
16 (int (*)(...))A::~A
24 (int (*)(...))A::~A
但我不明白: 1.两个第一指针是什么? 2.为什么虚拟析构函数有两个指针?
谢谢!
答案 0 :(得分:0)
让我们尝试以下演示代码:
class A
{
public:
char VarA;
int VarB;
virtual ~A();
};
#include <cstdio>
A::~A() {
std::printf("A::~A()\n");
}
#include <typeinfo>
void *complete_object_addr(A &ref) {
return dynamic_cast<void*> (&ref);
}
const std::type_info& get_typeinfo(A &ref) {
return typeid(ref);
}
void explicit_destructor_call(A *p) {
p->~A();
}
void delete_object(A *p) {
delete p;
}
#include <memory>
void create_object (A *p) {
new (p) A;
}
在Godbolt中。
让我们从构造函数调用开始
void create_object (A *p) {
new (p) A;
}
查看vptr和vtable在哪里:
create_object(A*):
movq $vtable for A+16, (%rdi)
ret
A*
参数位于rdi
中,vptr在对象中的偏移量为0,因此*(void*)p
给出了vptr。该vtable生成为:
vtable for A:
.quad 0
.quad typeinfo for A
.quad A::~A() [complete object destructor]
.quad A::~A() [deleting destructor]
我们看到vptr指向内部(而不是vtable的开头):.quad
意味着vptr指向第三个元素A::~A() [complete object destructor]
的8个字节。
这种输出vtable的方法比您所问的要清晰得多:有两个可以虚拟地调用的析构函数:
实际上,explicit_destructor_call(A*)
的代码定义为
void explicit_destructor_call(A *p) {
p->~A();
}
显示对函数指针vptr[0]
的调用。
explicit_destructor_call(A*):
movq (%rdi), %rax
jmp *(%rax)
对虚拟函数的调用为(p->vptr)(p)
;请注意,传递this
参数在生成的代码中是隐含的,就像在寄存器中一样。。
这里有一个窍门,您需要关闭汇编指令过滤器才能看到它:
.text
.size A::~A() [base object destructor], .-A::~A() [base object destructor]
.globl A::~A() [complete object destructor]
.set A::~A() [complete object destructor],A::~A() [base object destructor]
我不习惯使用这些指令,但这肯定意味着A::~A() [complete object destructor]
实际上是A::~A() [base object destructor]
,即:
.LC0:
.string "A::~A()"
A::~A() [base object destructor]:
movq $vtable for A+16, (%rdi)
movl $.LC0, %edi
jmp puts
*this
的动态类型重置为A
,这仅对销毁基类子对象有用,而对销毁完整对象无用puts("A::~A()");
(这表明printf
规范字符串在可能的编译时进行解释)。函数delete_object(A*)
定义为
void delete_object(A *p) {
delete p;
}
编译为
delete_object(A*):
testq %rdi, %rdi
je .L8
movq (%rdi), %rax
jmp *8(%rax)
这稍微复杂一点:需要测试p!=0
,因为当delete p;
为空时,p
是合法的。如果p
不为零,则代码跳转到(char*)vptr + 8
,这是vtable中的下一项:vptr[1]
。
该析构函数编译为:
A::~A() [deleting destructor]:
pushq %rbx
movq %rdi, %rbx
movq $vtable for A+16, (%rdi)
movl $.LC0, %edi
call puts
movq %rbx, %rdi
movl $16, %esi
popq %rbx
jmp operator delete(void*, unsigned long)
首先,我们将动态类型重置为A
。 (我认为实际上并不需要。)然后,像在“销毁对象”析构函数中那样输出文本,然后调用operator delete(this,sizeof(A));
。
typeid
,dynamic_cast
获取typeid(lvalue of A)
的值非常简单;
const std::type_info& get_typeinfo(A &ref) {
return typeid(ref);
}
编译为
get_typeinfo(A&):
movq (%rdi), %rax
movq -8(%rax), %rax
ret
我们看到vptr[-1]
被读取并返回。
最后,通过dynamic_cast<void*>
void *complete_object_addr(A &ref) {
return dynamic_cast<void*> (&ref);
}
非常简单:
complete_object_addr(A&):
movq (%rdi), %rax
addq -16(%rax), %rdi
movq %rdi, %rax
ret
在vptr[-2]
之后偏移this
的最派生对象(这些偏移几乎总是负数)。