2012-09-30 46 views
9

假設我們有一個類C++如何在內存中存儲函數和對象?

class A 
{ 
    int x; 
public: 
    void sayHi() 
    { 
     cout<<"Hi"; 
    } 
}; 

int main() 
{ 
    A *a=NULL; 
    a->sayHi(); 
} 

上面的代碼將編譯上的Turbo C(其中I測試)和打印Hi作爲輸出。

我期待崩潰,因爲aNULL。更過,如果我做sayHi()功能虛擬,它說

Abnormal temination(Segmentation fault in gcc) 

我知道有很多是依賴於實現的,但如果任何人都可以扔在任何實現一些輕或只是給出一個概述這將是非常好的。

+2

通過空指針調用方法是未定義的行爲。任何事情都可能發生 - 它不必崩潰,但標準允許它。 –

+0

不是C++的人,所以這是一個猜測,但是:你的代碼不需要訪問任何'A'的實例的內存。 'sayHi()'不使用字段'x',它不是虛擬的,因此它不需要訪問vtable來解析。 C++編譯器實際上必須插入一個檢查來查看'a'是否是導致錯誤的有效指針。 – millimoose

回答

6

在C++中,類的方法不存儲在該類的實例中。它們只是一些「特殊」功能,除了程序員指定的參數外,它們還透明地接受指針this

在你的情況下,sayHi()方法沒有引用任何類字段,因此,this指針(它是NULL)從未被遵循。

不要誤解,但這仍然是未定義的行爲。當您調用此程序時,您的程序可能會選擇將討厭的電子郵件發送到您的聯繫人列表。在這個特定的例子中,它做的最糟糕的事情,似乎工作。

virtual方法案例已被添加,因爲我回答了這個問題,但我不會優化我的答案,因爲它被其他人的答案所包含。

+1

+1,指出「工作」的未定義行爲是壞的。 –

7

顯然,代碼具有未定義的行爲,即無論您得到什麼是偶然的。也就是說,系統在調用非虛擬成員函數時不需要知道對象:它只能根據簽名調用。此外,如果一個成員函數不需要訪問成員,它根本不需要真的需要一個對象並且可以運行。這是代碼打印某些輸出時所觀察到的內容。不管這個系統是如何實現的,都沒有定義,但是,沒有什麼說它可以工作。

當調用一個虛擬函數時,系統開始查看與該對象關聯的類型信息記錄。在NULL指針上調用虛函數時,不存在這樣的信息並嘗試訪問它可能會導致某種崩潰。儘管如此,它並不是必須的,但它對大多數系統都是如此。

順便說一下,main()總是返回int

1

如果您調用類的非虛方法,對於編譯器來說,只需知道函數屬於哪個類並通過解引用(儘管是指向調用方法的類的NULL)即可,編譯器會獲取那個信息。方法幾乎只是一個將指向類實例的指針作爲隱藏參數的函數。這個指針是NULL,但如果你沒有在方法中引用任何屬性,那並不重要。

當您使此方法變爲虛擬時,情況會發生變化。編譯器不再知道編譯時與該方法關聯的代碼,並且必須在運行時解決這個問題。它的功能是查看一個基本上包含所有虛擬方法的函數指針的表;這個表與類實例相關聯,因此它查看相對於NULL指針的內存塊,因此在這種情況下會崩潰。

3

作爲概括,從一個類實例化的,沒有超類和虛擬函數的對象的佈局如下:

* - v_ptr ---> * pTypeInfo 
|    |- pVirtualFuncA 
|    |- pVirtualFuncB 
|- MemberVariableA 
|- MemberVariableB 

v_ptr是一個指向v表 - 其中包含的虛擬地址函數和RTTI數據。沒有虛函數的類沒有v表。

在上例中,class A沒有虛擬方法,因此沒有v-表。這意味着調用sayHi()的實現可以在編譯時確定並且是不變的。

編譯器生成的代碼將隱含的this指針設置爲a,然後跳轉到sayHi()的開頭。由於該實現不需要該對象的內容,因此當指針爲NULL時它的工作原理是一個令人高興的巧合。

如果您想讓sayHi()爲虛擬,編譯器無法確定在編譯器時調用的實現,所以會生成代碼來查找v表中函數的地址並調用它。在你的例子中,aNULL,編譯器讀取地址0的內容,導致中止。

+0

A類沒有虛擬方法的事實是不相關的。重要的是被調用的特定函數,比如Hi,不是一個虛函數,並且不使用類對象的成員。所以這個函數可以被調用,而不需要使用指向類對象的指針。 –

相關問題