2013-11-21 42 views
0

我們假設有一種函數,既可以是全局函數,也可以實現爲類成員const函數(不是靜態的,靜態的意味着全局的)。這取決於用戶的選擇,如果函數將被調用或根本不被調用。說 - 通常不叫或稱爲罕見 - 在非常特殊的情況下。 我對程序的邏輯組織不感興趣。我的問題是關於內存使用情況。我知道數據成員函數花費多一點(多一個指針),因爲它被稱爲槽對象。如果該函數不在運行時調用,那麼情況如何呢? 如果是全局函數,無論它是否被調用,它都將永久存儲在內存中。數據成員函數的情況如何,因爲它被分別稱爲through和被動態創建的對象 - 根本沒有創建,函數需要什麼內存,它將被放置在什麼位置以及如果對象不是創造了嗎?C++全局函數vs數據成員函數根據內存

謝謝。

+2

我認爲你誤解了,所有的函數,成員或不是,他們的代碼駐留在內存中。可以將成員函數作爲具有隱式參數的常規函數​​進行查看(有點不準確)。 – StoryTeller

+0

那麼數據成員函數究竟有什麼意義呢?只是爲了擁有更多可讀程序?我不是說範圍,虛擬,朋友等等。根據記憶的觀點,所有功能都是全球性的更有用嗎? –

+1

成員函數表示數據和代碼之間的強關係,並帶有編譯器支持。而且他們也表達了這種連接簡單易讀,是的。 – StoryTeller

回答

1

在典型的C++實現:

  • 的函數是一個函數,無論它是否是一些類的成員函數,它會駐留在內存中「永遠」(不考慮需求尋呼和所有的)。

  • virtual成員函數只不過是一個普通函數,它需要一個名爲this的隱藏參數。

  • A virtual成員函數是一個成員函數,它的地址記錄在它所屬類的調度表中。

所以成員函數非常像普通的功能,改變是呼叫完成的方式主要的事情:有一個額外的參數,並且可能通過一個隱藏的函數指針。

+0

它在內存中的位置?在數據部分? –

+0

@NickoletaDimitrova:通常在文本段中。爲什麼代碼將存在於數據段中?語言無法修改代碼。 –

1

正如在註釋中提到的那樣,C++中的類成員函數可以被認爲是一個普通的C風格的函數,它隱式地將一個指向它所調用類的實例的指針作爲參數。例如,請考慮下面的C++代碼:

class foo { 
public: 
    void set(int j); 
private: 
    int i; 
}; 

void foo::set(int j) { 
    i = j; 
} 

功能foo::set可以認爲C風格的功能這需要class foo *類型的(隱藏)的說法,當你這樣做foo a; a->set(3);,該class foo *是(含蓄地)通過是&a

綜上所述,無論您是否打電話給foo::set,它都會被加載到內存中。 (唯一可能的例外是,如果在代碼中根本沒有對函數的調用,在這種情況下優化器可能會在編譯期間將其刪除,但如果可以根據用戶輸入動態調用它,則會加載它。 )另一方面,無論您有多少個class foo實例,內存中都只需要存在foo::set的一個副本。

1

如果你的函數不是虛擬的,那麼作爲成員函數(又名方法)沒有任何開銷。實際上,在生成的對象代碼中,不存在「成員函數」的這種概念(除非它是虛擬的,稍後更多)。所有方法都是具有特殊變量this的函數。編譯器在編譯時知道在運行時要調用什麼方法,因此它可以生成特定於方法的目標代碼(有一些處理this)。順便說一句,這就是爲什麼的情況下下面的代碼不會崩潰:

#include <iostream> 

class Crashy { 
public: 
     void wouldItCrash() { 
       std::cout << "Hey, I'm still alive!" << std::endl; 
     } 
}; 

int main() { 
     Crashy* ptr = 0; 
     ptr->wouldItCrash(); 
     return 0; 
} 

儘管空指針的出現,計劃陸續完成,並打印"Hey, I'm still alive!"

#include <iostream> 

void Crashy__wouldItCrash(Crashy *this) { 
     std::cout << "Hey, I'm still alive!" << std::endl; 
} 

int main() { 
     Crashy* ptr = 0; 
     Crashy__wouldItCrash(ptr); 
     return 0; 
} 

我們不會取消引用函數指針,所以沒有問題發生:如果你想方法wouldItCrash作爲某種功能的特定參數this沒關係。

考慮下面的簡單程序:

#include <iostream> 

void FunctionName__() { 
     std::cout << "Hello from function!" << std::endl; 
} 

class ClassName__ { 
public: 
     void MethodName1__() { 
       std::cout << "Hello from method 1!" << std::endl; 
     } 
     void MethodName2__() { 
       std::cout << "Hello from method 2!" << std::endl; 
     } 
}; 

int main() { 
     FunctionName__(); 

     ClassName__ a; 
     a.MethodName1__(); 
     a.MethodName2__(); 
     return 0; 
} 

如果你編譯它沒有優化(只是g++ main.cpp),然後看一下符號表(nm a.out),你會看到

0804876d T _Z14FunctionName__v 
... 
0804882c W _ZN11ClassName__13MethodName1__Ev 
08048858 W _ZN11ClassName__13MethodName2__Ev 

那是,所有方法都變成了帶有特殊名稱的普通函數(所以不會發生衝突,參見name mangling

虛擬功能

正如我前面所說的,有一些特殊的規則適用於虛擬功能。虛擬函數在編譯時無法解析(編譯器不知道在運行時將處理哪個派生類實例),所以它延遲到運行時。因此,爲了提供虛擬功能,每個類(當然有這種功能)都有一個名爲virtual method table(aka vtable)的特殊查找表。在這種情況下,您顯然需要支付一些內存空間:您需要一個指向vtable中函數的指針和一個指向您類的每個實例的vtable的指針。