2013-05-07 75 views
2

我一直在閱讀如何在C++中分配內存。C++中的內存組織

一些資源提:

http://www.geeksforgeeks.org/memory-layout-of-c-program/

http://duartes.org/gustavo/blog/post/anatomy-of-a-program-in-memory

Object creation on the stack/heap?

Global memory management in C++ in stack or heap?

http://msdn.microsoft.com/en-us/library/vstudio/dd293645.aspx

Heap/Stack and multiple processes

Do different programs gets their memory from a common heap or from a separate heap?

http://computer.howstuffworks.com/c28.htm

enter image description here

我想根據我的閱讀澄清幾點:

根據http://www.geeksforgeeks.org/memory-layout-of-c-program/第4棧「堆棧,其中自動存儲變量以及每次保存的信息E中的功能被稱爲」

假設:

class myClass{ 
    int a; 
    char b; 
    public: 
    myClass(int a,char b) 
    { 
    this->a = a; 
    this->b = b; 
    } 
}; 

1)據我已閱讀,當我們編譯這段代碼二進制坐落在程序存儲器和目前尚無分配堆棧。正確?

現在,在我的主要:

int main() 
{ 
myClass Ob(1,'c'); 
return 0; 
} 

2)現在的大小5個字節的物體Ob(4字節(int)的,1個字節(字符) - 32位OS)是在堆疊產生的,因爲它是一個自動變量。正確嗎?

3)當構造函數myClass(int a,char b)被調用時,臨時變量(參數a,b)是在堆棧上爲構造函數創建的,然後在創建對象Ob後被銷燬?就像我們通過按值傳遞參數來調用函數一樣。

現在假設另一類

class pointerClass { 
int a; 
char* b; 
public: 
pointerClass(int size){ 
b= new char[size]; 
a=size; 
} 
}; 

現在,在主:

int main() 
{ 
pointerClass ptr(10) ; //Step 1 
} 

4)不執行的大小的8個字節這意味着PTR對象(INT一個(4個字節),字符* B(4字節,即只是持有指向堆的地址)在堆棧上創建?還有一個10字節的內存(對應於新的char [10]被分配在堆上),它正在被char * b的內容指向嗎?正確嗎?

5)當我們通過引用(如fn (int *a,char* b)fn(int& a,char& b))將參數傳遞給某個函數時,這是否意味着在函數棧上爲函數指定實際傳遞的對象並在函數返回時被銷燬時創建臨時指針/引用?或者更確切地說是傳遞實際的對象,而不是創建並銷燬函數棧上的臨時指針/引用?

這是我昨天問,但我不是滿意的答覆: Constructor, Copy Constructor and Stack Creation : C++

6)當我們重載FN如fn(int a,char b)fn(int& a,char& b)我們可以從主調用爲fn(A,B) 低於投 static_cast<void(*)(int, char)>(fn)(a, c); //Calls fn(int a,char b) static_cast<void(*)(int&, char&)>(fn)(a, c);//Calls fn(int& a.char& b) 到底發生了什麼?什麼是無效(*)。

由於

+7

我認爲你需要將這個問題分成幾個小問題。你希望我們寫C++書作爲答案嗎? – 2013-05-07 06:16:44

+0

我認爲所有的問題都是相關的,因此置於一個問題之下。答案是「是」或「否」。大多數情況下,輸入時不需要輸入太多的內容。 – 2013-05-07 06:18:09

+0

@Mat編輯我的問題 – 2013-05-07 06:19:48

回答

4
  1. 正確
  2. 正確(儘管它可能不是五個字節,大概爲八個)
  3. 正確
  4. 正確
  5. 暫時指針/參考在堆棧上創建的,而不是肯定你爲什麼不滿意之前給出的答案,它看起來對我來說是正確的
  6. void(*)(int,char)是一種類型,sp特別指向一個帶有兩個參數和int和一個char的void函數的指針。很顯然,這個演員強制編譯器選擇你想要的函數的版本,雖然這對我來說可能是新聞。

當然必須添加必要的警告,上面的任何內容都不需要C++,這只是C++的典型實現方式。

+0

關於第一個問題:我認爲這在很大程度上取決於你如何定義「堆棧」和其他一些術語。 「1)根據我所讀到的,當我們編譯這段代碼時,二進制文件位於程序存儲器中,而且還沒有任何文件被分配到堆棧中。」編譯!=加載程序(或執行)等 – dyp 2013-05-07 06:22:17

+0

約翰。關於5:我得到的答覆是沒有臨時引用或指針創建在堆棧上,而是直接傳遞對象...你能通過我的任何引用閱讀如果你爲此編了任何東西? – 2013-05-07 06:24:43

+2

@GauravK我認爲你誤解了答案'在任何一種情況下,對象的副本都是**而不是**創建的。' (我強調)。 – john 2013-05-07 06:26:29

6
  1. 正確 - 分配發生在運行時。
  2. 部分正確 - 標準不使用術語堆棧和堆,它僅僅需要來自對象的行爲。但是,堆棧是實現此功能的最常見和最常見的方式。此外,編譯器允許使用填充字節填充結構,因此不應推測該對象的大小。這被稱爲structure padding。只需使用sizeof即可獲得大小。
  3. 部分正確 - 按值傳遞和返回確實需要一個拷貝構造函數才能訪問調用,但這些調用在某些情況下可能會被忽略。該過程被稱爲copy elision
  4. 部分正確 - 指針僅指向具有動態存儲的對象。指針的大小可能會有所不同。
  5. 指針或引用是爲函數本地創建的,但它指向或引用其地址被傳遞的對象。這裏沒有需要的副本,也沒有發生。
  6. 每個變量在C和C++中都有一個數據類型。類型轉換允許您靈活地強制編譯器將指向一個數據類型的指針視爲完全不同的指針數據類型。由於函數有一個類型,指向函數的指針也有一個類型,並且類型轉換允許您強制編譯器將函數指針從一個函數類型處理爲另一個類型,因此本質上允許您調用所需的重載函數版本。
+0

「儘管指針的大小可能會有所不同。」可以根據C++實現(即編譯器,目標平臺等)而變化,但是在編譯的程序中是不變的。 – dyp 2013-05-07 06:34:04

+0

@juanchopanza:因此,*部分正確*在第3點。澄清它,以消除混淆的措辭。 – 2013-05-07 06:35:53

+0

那麼我喜歡這個答案,所以+1,但我有一些評論:「分配發生在運行時」有加載時間和庫所需的結構的初始化(例如一些有狀態的C字符串函數)。我問了關於「動態內存」,因爲我只知道Std的「動態存儲持續時間」( - > new,delete),但是指針也可以指向靜態或自動(或線程本地)存儲持續時間的對象。 6)重載分辨率需要轉換。 – dyp 2013-05-07 06:42:35

3

首先,我應該指出,您所顯示的圖非常依賴於系統 。例如,在Solaris下,操作 系統內存根本不映射到用戶地址空間。 至少在程序開始時,最頻繁的映射只有三個映射片段用於 用戶存儲器:至少在程序開始處:代碼 片段位於底部(但不是絕對底部,因爲 地址0通常不會被映射) ,它上面的一個數據段, 和頂部的一個堆棧段(長度減小),堆棧和數據之間有一個大的未映射內存空洞。

只要動態啓動 加載,所有這些都會完全更改。

  1. 號後您COMPLE代碼(在 編譯的更大的意義上),可執行文件是在一個文件中,而不是在內存中。在執行該程序之前, 程序不會加載到內存中。 (曾經有在早期的Unix一些例外,而在嵌入式 系統,即使在今天,但對於通用系統像 Windows和現代的Unix,這是真的。)

  2. 變量將在創建疊加。但由於考慮到對齊 的原因,幾乎 肯定會大於5個字節。 (8個字節對於大多數32位機器來說是最小的)。在C++中,對象創建是一個兩步過程: 內存分配和調用構造函數。在大多數實現中,函數中使用的所有對象所需的全部內存將立即分配在函數的頂部,即 ;在某些情況下,出於調試原因,還會在每個變量的兩側分配額外的 內存,並且內存將被初始化。 當程序流程 傳遞對象的定義時,將調用該對象的構造函數。

  3. 是的。調用構造函數就像調用一個 任何其他函數。再次,論證的創建是一個兩步過程;在這種情況下,策略會有所不同:有些實現將 分配函數頂部 處的所有參數所需的全部內存,其他人將在初始化它們之前根據需要爲其分配所需的所有內存 。並且在簡單的 變量(如int)的情況下,大多數機器具有單個指令 ,其將允許在相同的 指令中分配初始化。根據參數的類型以及它們如何初始化,編譯器甚至可以使用不同的策略。

  4. 正確,或多或少。對於內建類型,如int或 指針,唯一的「破壞」是釋放內存,並且取決於編譯器, 可能不會發生,直到調用函數的結尾 。 (另一方面,如果第二個函數調用 ,則該內存將被重用,因此程序 的行爲與「立即釋放」內存一樣。)

  5. 正確,或多或少。 (正式地,參考信息均不 「破壞」,因爲它們不是對象。實際上,至少 當它們被用作函數的參數,底層代碼 是完全一樣的指針。)

  6. 首先,只有事情你可以用 一個static_cast的結果做一個函數的指針,就是將它的原始類型轉換回 。其他任何東西都是未定義的行爲。如果 fn定義爲void fn(int a, char b),則使用 的結果static_cast<void (*) (int&, char&)>(fn)未定義 行爲,而將不起作用。這裏發生的事情幾乎可以是 ,但是很有可能會使 程序崩潰。而void (*)在這種情況下是部分的 聲明函數類型的指針; void (*)(int, char ),是類型名稱:指針(*)功能( (int, char) —括號是必要的,因爲 優先規則)返回void

編輯:

只是一個關於6點我已經錯過了 功能超載對於兩種類型的事實修正。 A static_cast可以使用 來解決函數重載,例如:在這樣的 的情況下,通常的規則不適用,因爲沒有類型 轉換。 (是的,這很混亂。)

+0

+1這個全面而準確的答案。 – dyp 2013-05-07 13:07:52

+0

@DyP錯字,是的。 – 2013-05-07 13:07:52