(我的觀點:精通C++的未必精通C,反之亦然。我只是在C++的世界裏苦苦追尋,C的世界依然陌生。) html
注:如下內容拷貝自 http://news.cnblogs.com/n/176728/ ,根據這篇文章的內容, 能夠看到,在跨平臺的時候,某平臺編譯器ok,未必其它編譯器ok,須要事實說話。 程序員
1) 一個 Base*[]的指針數組中,存放了一堆派生類的指針,這樣,你 delete [] pBase; 只是把指針數組給刪除了,並無刪除指針所指向的對象。這個是最基礎的C的問題。你先得 for 這個指針數組,把數據裏的對象都 delete 掉,而後再刪除數組。很明顯,這和 C++ 沒有什麼關係。2)第二種多是:Base *pBase = new Derived[n] 這樣的狀況。這種狀況下,delete[] pBase 明顯不會調用虛析構函數(固然,這並不必定,我後面會說) ,這就是上面雲風回的微博。對此,我以爲若是是這個樣子,這個程序員徹底沒有搞懂C語言中的指針和數組是怎麼一回事,也沒有搞清楚, 什麼是對象,什麼是對象的指針和引用,這徹底就是C語言沒有學好。 shell
後來,在看到了 @GeniusVczh 的原文 《如何設計一門語言(一)——什麼是坑(a)》最後時,才知道了說的是第二種狀況。也就是下面的這個示例(我加了虛的析構函數這樣方便編譯): 編程
class Base { public: virtual ~B(){ cout <<"B::~B()"<<endl; } }; class Derived : public Base { public: virtual ~D() { cout <<"D::D~()"<<endl; } }; Base* pBase = new Derived[10]; delete[] pBase;
C語言補課 數組
我先不說這段 C++ 的程序在什麼狀況下能正確調用派生類的析構函數,我仍是先來講說C語言,這樣我在後面說這段代碼時你就明白了。 函數
對於上面的: 佈局
Base* pBase = new Derived[10];
這個語言和下面的有什麼不一樣嗎? 測試
Derived d[10]; Base* pBase = d;
一個是堆內存動態分配,一個是棧內存靜態分配。只是內存的位置和類型不同,在語法和使用上沒有什麼不同的。(若是你把 Base 和 Derived 想成 struct,把 new 想成 malloc () ,你還以爲這和 C++ 有什麼關係嗎?) spa
那麼,你以爲 pBase 這個指針是指向對象的,是對象的引用,仍是指向一個數組的,是數組的引用? .net
因而乎,你能夠想像一下下面的場景:
int *pInt; char* pChar; pInt = (int*) malloc (10*sizeof(int)); pChar = (char*) pInt;
對上面的 pInt 和 pChar 指針來講,pInt[3]和 pChar[3]所指向的內容是否同樣呢?固然不同,由於 int 是 4 個字節,char 是 1 個字節,步長不同,因此固然不同。
那麼再回到那個把 Derived[]數組的指針轉成 Base 類型的指針 pBase,那麼 pBase[3]是否會指向正確的 Derrived[3]呢?
咱們來看個純C語言的例程,下面有兩個結構體,就像繼承同樣,我還別有用心地加了一個 void *vptr,好像虛函數表同樣:
struct A { void *vptr; int i; }; struct B{ void *vptr; int i; char c; int j; }b[2] ={ {(void*)0x01, 100, 'a', -1}, {(void*)0x02, 200, 'A', -2} };
注意:我用的是G++編譯的,在 64bits 平臺上編譯的,其中的 sizeof (void*)的值是8。
咱們看一下棧上內存分配:
struct A *pa1 = (struct A*)(b);
用 gdb 咱們能夠看到下面的狀況:(pa1[1]的成員的值徹底亂掉了)
(gdb) p b $7 = {{vptr = 0x1, i = 100, c = 97 'a', j = -1}, {vptr = 0x2, i = 200, c = 65 'A', j = -2}} (gdb) p pa1[0] $8 = {vptr = 0x1, i = 100} (gdb) p pa1[1] $9 = {vptr = 0x7fffffffffff, i = 2}
咱們再來看一下堆上的狀況:(咱們動態了 struct B [2],而後轉成 struct A *,而後對其成員操做)
struct A *pa = (struct A*) malloc (2*sizeof(struct B)); struct B *pb = (struct B*) pa; pa[0].vptr = (void*) 0x01; pa[1].vptr = (void*) 0x02; pa[0].i = 100; pa[1].i = 200;
用 gdb 來查看一下變量,咱們能夠看到下面的狀況:(pa 沒問題,可是 pb[1]的內存亂掉了)
(gdb) p pa[0] $1 = {vptr = 0x1, i = 100} (gdb) p pa[1] $2 = {vptr = 0x2, i = 200} (gdb) p pb[0] $3 = {vptr = 0x1, i = 100, c = 0 '\000', j = 2} (gdb) p pb[1] $4 = {vptr = 0xc8, i = 0, c = 0 '\000', j = 0}
可見,這徹底就是C語言裏亂轉型形成了內存的混亂,這和 C++ 一點關係都沒有。並且,C++的任何一本書都說過,父類對象和子類對象的轉型會帶來嚴重的內存問題。
可是,若是在 64bits 平臺下,若是把咱們的 structB 改一下,改爲以下(把 struct B 中的 int j 給註釋掉):
struct A { void *vptr; int i; }; struct B{ void *vptr; int i; char c; //int j; <---註釋掉 int j }b[2] ={ {(void*)0x01, 100, 'a'}, {(void*)0x02, 200, 'A'} };
你就會發現,上面的內存混亂的問題都沒有了,由於 struct A 和 struct B 的 size 是同樣的:
(gdb) p sizeof(struct A) $6 = 16 (gdb) p sizeof(struct B) $7 = 16
注:若是不註釋 int j,那麼 sizeof (struct B)的值是 24。
這就是C語言中的內存對齊,內存對齊的緣由就是爲了更快的存取內存(詳見《深刻理解C語言》)
若是內存對齊了,並且 struct A 中的成員的順序在 struct B 中是同樣的並且在最前面話,那麼就沒有問題。
再來看 C++ 的程序
若是你看過我 5 年前寫的《C++虛函數表解析》以及《C++內存對象佈局 上篇、下篇》,你就知道 C++ 的標準會把虛函數表的指針放在類實例的最前面,你也就知道爲何我別有用心地在 struct A 和 struct B 前加了一個 void *vptr。C++之因此要加在最前面就是爲了轉型後,不會找不到虛表了。
好了,到這裏,咱們再來看C++,看下面的代碼:
#include using namespace std; class B { int b; public: virtual ~B(){ cout <<"B::~B()"<<endl; } }; class D: public B { int i; public: virtual ~D() { cout <<"D::~D()"<<endl; } }; int main (void) { cout << "sizeB:" << sizeof(B) << " sizeD:"<< sizeof(D) <<endl; B *pb = new D[2]; delete [] pb; return 0; }
上面的代碼能夠正確執行,包括調用子類的虛函數!由於內存對齊了。在個人 64bits 的 CentOS 上——sizeof (B):16 ,sizeof (D):16
可是,若是你在 class D 中再加一個 int 成員的問題,這個程序就 Segmentation fault 了。由於—— sizeof (B):16 ,sizeof (D):24。pb[1]的虛表找到了一個錯誤的內存上,內存亂掉了。
再注:我在 Visual Studio 2010 上作了一下測試,對於 struct 來講,其表現和 gcc 的是同樣的,但對於 class 的代碼來講,其能夠「正確調用到虛函數」不管父類和子類有沒有同樣的 size。
然而,在 C++ 的標準中,下面這樣的用法是 undefined! 你能夠看看 StackOverflow 上的相關問題討論:《Why is it undefined behavior to delete[] an array of derived objects via a base pointer?》(一樣,你也能夠看看《More Effective C++》中的條款三)
Base* pBase = new Derived[10]; delete[] pBase;
因此,微軟 C++ 編程譯器 define 這個事讓我很是不解,對微軟的 C++ 編譯器再度失望,看似默默地把其編譯對了很漂亮,實則誤導了好多人把這種 undefined 的東西當成 defined 來用,還讚賞作得好,真是使人無語。(就像微博上的這個貼同樣,說 VC 多麼牛,還說這是 OO 的特性。我勒個去!)