很多 C++ 方面的書(shū)籍都說(shuō)明了虛析構(gòu)的作用:
10年專(zhuān)注成都網(wǎng)站制作,成都企業(yè)網(wǎng)站定制,個(gè)人網(wǎng)站制作服務(wù),為大家分享網(wǎng)站制作知識(shí)、方案,網(wǎng)站設(shè)計(jì)流程、步驟,成功服務(wù)上千家企業(yè)。為您提供網(wǎng)站建設(shè),網(wǎng)站制作,網(wǎng)頁(yè)設(shè)計(jì)及定制高端網(wǎng)站建設(shè)服務(wù),專(zhuān)注于成都企業(yè)網(wǎng)站定制,高端網(wǎng)頁(yè)制作,對(duì)成都木屋等多個(gè)方面,擁有多年的網(wǎng)站運(yùn)維經(jīng)驗(yàn)。
很久一段時(shí)間以來(lái),我一直認(rèn)為第 2 點(diǎn)僅僅指的是:當(dāng)派生類(lèi)使用 RAII 手法時(shí),如果派生類(lèi)的析構(gòu)沒(méi)有被調(diào)用,就會(huì)產(chǎn)生資源泄露。就像下面的代碼:
#include <iostream>
struct A
{
A() {
std::cout << "A::A" << std::endl;
}
~A() {
std::cout << "A::~A" << std::endl;
}
};
struct B : A
{
B() {
x = new int;
std::cout << "B::B" << std::endl;
}
~B() {
delete x;
std::cout << "B::~B" << std::endl;
}
int* x;
};
int main()
{
A* a = new B;
delete a;
}
這段代碼結(jié)果輸出:
A::A
B::B
A::~A
B 的析構(gòu)函數(shù)沒(méi)被調(diào)用,a->x 沒(méi)有被正確釋放,產(chǎn)生了內(nèi)存泄漏。
后來(lái)發(fā)現(xiàn)在多重繼承情況下,情況可能更加嚴(yán)重。例如以下代碼:
#include <iostream>
struct A1
{
A1() : a1(0) {}
~A1() {
std::cout << "A1::~A1" << std::endl;
}
int a1;
};
struct A2
{
A2() : a2(0) {}
~A2() {
std::cout << "A2::~A2" << std::endl;
}
int a2;
};
struct B : A1, A2
{
B() : b(0) {}
~B() {
std::cout << "B::~B" << std::endl;
}
int b;
};
int main()
{
B* b = new B;
A1* a1 = b;
A2* a2 = b;
printf("%p %p %p\n", b, a1, a2);
delete a2;
}
輸出:
0x5cbeb0 0x5cbeb0 0x5cbeb4
A2::~A2
free(): invalid pointer
已放棄 (核心已轉(zhuǎn)儲(chǔ))
B* 隱式轉(zhuǎn)型成 A2*,C++ 派生類(lèi)指針(引用)轉(zhuǎn)型為基類(lèi)指針(引用)被稱(chēng)為 upcast。upcast 在單一繼承的情況下,指針沒(méi)有進(jìn)行偏移,但是在多重繼承下,會(huì)進(jìn)行指針偏移??梢钥吹皆诙嘀乩^承下,第 2 個(gè)基類(lèi)指針與派生類(lèi)指針不同。再看 delete b
生成的匯編代碼:
movq -40(%rbp), %rbx ; %rbx = a2
testq %rbx, %rbx ; a2 == 0 ?
je .L8
movq %rbx, %rdi ; A2's this ptr = a2
call A2::~A2() [complete object destructor]
movl $4, %esi
movq %rbx, %rdi
call operator delete(void*, unsigned long) ; call operator delete(a2, 4)
可以看到先調(diào)用了 A2::~A2()
,再調(diào)用了 operator delete(a2, 12)
。 傳給底層 free()
函數(shù)的指針是 a2(0x5cbeb4)
,正確的指針應(yīng)該是 b(0x5cbeb0)
。而且第2個(gè)參數(shù)傳遞的是 4,是 A2 的大小,不是 B 的大小。free()
檢測(cè)到這個(gè)是非法的指針,直接終止進(jìn)程。給 A1
和 A2
的析構(gòu)函數(shù)都加上 virtual
,執(zhí)行結(jié)果為:
0x1eb2eb0 0x1eb2eb0 0x1eb2ec0
B::~B
A2::~A2
A1::~A1
執(zhí)行結(jié)果是正常的,再看此時(shí)生成的匯編代碼:
movq -40(%rbp), %rax ; %rax = a2
testq %rax, %rax ; a2 == 0 ?
je .L13
movq (%rax), %rdx ; %rdx = vptr
addq $8, %rdx ; %rdx = vptr + 8
movq (%rdx), %rdx ; %rdx = vptr[1] or %rdx = *(vptr + 8)
movq %rax, %rdi ; %rax = vptr[1]
call *%rdx ; call vptr[1]
這段代碼使用了虛函數(shù),找到 B 的虛表:
vtable for B:
.quad 0
.quad typeinfo for B
.quad B::~B() [complete object destructor] ; vptr B inherit A1
.quad B::~B() [deleting destructor]
.quad -16
.quad typeinfo for B
.quad non-virtual thunk to B::~B() [complete object destructor] ; vptr B inherit A2
.quad non-virtual thunk to B::~B() [deleting destructor]
a2 的虛指針指向 non-virtual thunk to B::~B() [complete object destructor]
,會(huì)執(zhí)行這個(gè)代碼段:
non-virtual thunk to B::~B() [deleting destructor]:
subq $16, %rdi ; this = a2 - 16 or this = b, a2 downcast to b
jmp .LTHUNK1
由于 a2 != b
,a2 要進(jìn)行 downcast 變成 b,于是使用 thunk 技術(shù)進(jìn)行指針偏移,再調(diào)用B::~B() [deleting destructor]
。B::~B() [deleting destructor]
再調(diào)用 B::~B(b)
,和 operator delete(b, 32)
.set .LTHUNK1,B::~B() [deleting destructor]
B::~B() [deleting destructor]:
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
movq %rdi, -8(%rbp) ; store this to stack
movq -8(%rbp), %rax ; %rax = this
movq %rax, %rdi
call B::~B() [complete object destructor] ; call B::~B(b)
movq -8(%rbp), %rax
movl $32, %esi
movq %rax, %rdi
call operator delete(void*, unsigned long) ; call operator delete(b, 32)
leave
ret
可以看到傳遞給 operator delete
的指針和大小是正確的。A2::~A2()
和 A1::~A1()
在 B::~B() [complete object destructor]
中被調(diào)用,不需要繼續(xù)深入觀察。
虛析構(gòu)完美解決了這兩個(gè)問(wèn)題:
free()
函數(shù)的指針是錯(cuò)誤的在 ISO/IEC :2011 5.3.3 也有對(duì)不使用虛析構(gòu)的描述
In the first alternative (delete object), if the static type of the object to be deleted is different from its
dynamic type, the static type shall be a base class of the dynamic type of the object to be deleted and the
static type shall have a virtual destructor or the behavior is undefined. In the second alternative (delete
array) if the dynamic type of the object to be deleted differs from its static type, the behavior is undefined.
文章標(biāo)題:C++ 不使用虛析構(gòu)的后果及分析
URL分享:http://chinadenli.net/article28/dsoipjp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供App設(shè)計(jì)、品牌網(wǎng)站設(shè)計(jì)、虛擬主機(jī)、電子商務(wù)、全網(wǎng)營(yíng)銷(xiāo)推廣、商城網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)