Давайте попробуем со следующим демонстрационным кодом:
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 for A:
.quad 0
.quad typeinfo for A
.quad A::~A() [complete object destructor]
.quad A::~A() [deleting destructor]
Мы видим, что vptr указывает внутрь (не в начало vtable): .quad
означает 8 байт до vptr указывает на третий элемент: A::~A() [complete object destructor]
.
Этот способ вывода 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
- сначала vptr устанавливается как в конструкторе: динамический тип
*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));
.
RTTI: 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
(эти смещения почти всегда отрицательные).
person
curiousguy
schedule
01.07.2018