2011-03-15 191 views
4

可能重複:
C++: why is new needed?malloc()和虛函數有什麼問題?

爲什麼不能我使用malloc爲我的對象分配空間時,他們是包含虛擬函數的類的孩子嗎?這真令人沮喪。有沒有很好的理由?

下面的程序說明了這個問題。它出現segfaults第27行,在那裏我稱之爲AA-> F()

#include <iostream> 
#include <cstdlib> 

class A 
{ 
public: 
    virtual int f() {return 1;} 
}; 

class B 
{ 
public: 
    int f() {return 1;} 
}; 

class Aa : public A {}; 

class Bb : public B {}; 

int main() 
{ 
    Aa* aa = (Aa*)malloc(sizeof(Aa)); 
    Aa* aan = (Aa*)new Aa(); 
    Bb* bb = (Bb*)malloc(sizeof(Bb)); 
    std::cout << bb->f() << std::endl; 
    std::cout << aan->f() << std::endl; 
    std::cout << aa->f() << std::endl; 
    return 0; 
} 

版本信息:G ++(Ubuntu的/ Linaro的4.4.4-14ubuntu5)4.4.5

+1

解決方案在這裏:http://stackoverflow.com/questions/4904762/c-why-is-new-needed/4904873#4904873 – 2011-03-15 12:18:21

+0

@Tomalak:你應該投票結束,如果你可以重複(我相信與5k代表你被允許) – 2011-03-15 12:22:47

+0

答案很簡單。 'new'是在C++中創建類的新實例的正確方法。 'malloc'不是。 – 2011-03-15 13:15:15

回答

6

實現虛擬函數的常用方法是在與對象相反的偏移處有一個指向「虛擬表」或vtable的指針。這張表是需要弄清楚要調用什麼虛函數。這就是爲什麼malloc'ing空間不起作用。

+0

呃,是的。這就是我所說的,不是嗎?我明確地說過一個虛擬表的指針,而不是虛擬表本身...... – DavidK 2011-03-15 12:35:53

+0

我也誤讀了你的答案。我認爲英語是不明確的。它可以被讀取(就像你想的那樣)「...一個指向'虛擬表'的指針,該指針處於負偏移量」或者可能被誤讀「指向vtable的指針,該vtable位於負偏移量處」 。仍然值得+1。 – 2011-03-15 15:15:31

+0

我誤讀,對不起:) – 2011-03-15 20:16:55

2

不要使用malloc,使用new - malloc不調用構造函數。

當你做A * a = new A();編譯器會分配內存,爲A設置vtable指針並調用構造函數。當你調用一個虛擬函數時,vtable被用來實際查找函數。

當你做A * a = (A *) malloc(...);編譯器將分配內存,它將包含隨機數據。當你調用一個虛擬函數時,它會查看(垃圾)vtable並調用一些隨機位置。

虛擬函數的類看起來像這樣在內部:

struct Foo { 
    void * vtable; 
    int aClassMemberVar; 
}; 

調用虛函數着眼於「隱藏的」虛函數表的指針,它指向虛函數表,指針的鏈表功能。所以這個vtable指針必須被初始化,malloc不會這樣做。

+2

或創建vtable,這是這裏的相關項目。 – 2011-03-15 12:19:07

+0

我認爲從* vtable指針*(每個對象)區分*虛擬方法表*(每種類型唯一)非常重要。在我知道使用* vtable * s的所有實現中,無論該類型的任何對象是否被實例化,該表都存在。這裏的特定問題是這個特定對象中的* vtable指針是未初始化的,當試圖調用任何虛擬方法時,位於隨機地址的內存將被解釋爲* vtable *。 – 2011-03-15 12:25:35

+0

@Tomalak:新不會創建vtable。 – Erik 2011-03-15 12:27:09

6

malloc只分配內存,但不創建對象。因此,行

Aa* aa = (Aa*)malloc(sizeof(Aa)); 

分配的內存區域是大到足以容納一個A,但包含了垃圾。正如其他人指出的,這也意味着指向vtable的指針將不會被設置(我從@DavidRodríguez對另一個答案的評論中得到了一個指針),這是調用虛擬函數所需的。由於B不包含虛擬功能,因此不會出現此類問題。它將與B發生過,但是,如果B包含任何數據的會員,他們需要初始化,比如這個:

class B 
{ 
public: 
    B() : foo(new int()) {} 

    int f() {return *foo;} 
private: 
    int * foo; 
}; 

Aa* aan = (Aa*)new Aa(); 

不投可以這樣做:

Aa* aan = new Aa(); 
0

malloc不會調用該類的構造函數,因此您的對象沒有正確啓動,因此它會導致錯誤。使用C++時,使用new分配內存。順便說一句,沒有必要投下從new返回的指針。

1

當然,因爲虛擬功能表沒有得到正確創建?

+0

指向表的指針不會被設置,但表中將存在於系統中。 – 2011-03-15 12:20:24

+0

@大衛:這是說同樣的事情稍微好一點的方法! :) – Nick 2011-03-15 12:21:50

6

原因是malloc對C++構造函數一無所知,因此不會調用它們。您可以使用placement new撥打constuctors自己:

int main() 
{ 
    Aa* aa = (Aa*)malloc(sizeof(Aa)); 
    new(aa)Aa; 

    Aa* aan = (Aa*)new Aa(); 

    Bb* bb = (Bb*)malloc(sizeof(Bb)); 
    new(bb)Bb; 

    std::cout << bb->f() << std::endl; 
    std::cout << aan->f() << std::endl; 
    std::cout << aa->f() << std::endl; 

    aa->~Aa(); 
    free(aa); 

    delete aan; 

    bb->~Bb(); 
    free(bb); 

    return 0; 
} 

注意,你有這樣的釋放內存之前手動調用析構函數。

+0

+1有人給我+1我的小貼士新迴應...我沒有看到你的更完整,所以我複製我的+1給你:-)我會注意到你的代碼是真的不可讀:-) :-)這裏有一些空白行可以幫助。 – xanatos 2011-03-15 12:38:30

0

好理由叫做虛擬表。具有虛擬方法的類型的對象具有指向要調用的實際虛擬方法的地址的指針表。這些被稱爲虛擬表或V表。