2012-06-27 189 views
2

所以這可能看起來像一個被廣泛回答的問題,但我更關心兩者之間發生什麼不同的內部事件。當使用常量長度時char []和new char []之間的區別

除此之外,第二個例子不僅創造了記憶,而是一個指向內存,什麼內存發生當發生以下情況的事實:

char a[5]; 
char b* = new char[5]; 

,更直接關係到爲什麼我問這個問題,爲什麼我能做到

const int len = 5; 
char* c = new char[len]; 

但不

const int len = 5; 
char d[len]; // Compiler error 

編輯應該提到我的VC獲得此編譯器錯誤++(去圖...)

1>.\input.cpp(138) : error C2057: expected constant expression 
1>.\input.cpp(138) : error C2466: cannot allocate an array of constant size 0 
1>.\input.cpp(138) : error C2133: 'd' : unknown size 

編輯2:應該已經公佈確切的代碼我正在同。如果使用運行時值計算動態分配數組的常量長度,則會產生此錯誤。

假設random(a,b)返回ab之間的int

const int len1 = random(1,5); 
char a[len1]; // Errors, since the value 
       // is not known at compile time (thanks to answers) 

const int len2 = 5; 
char b[len2]; // Compiles just fine 
+0

我沒有得到使用GCC 4.4.6的編譯器錯誤,並不期望一個。 – trojanfoe

+0

我的不好,應該提到我正在使用VC++。 – Qix

+1

我仍然不會指望有錯誤。 – trojanfoe

回答

14

不同之處在於數組的壽命。如果寫:

char a[5]; 

然後陣列具有它在限定的塊的壽命(如果它是在塊範圍限定 ),其中包含它(如果它是 類範圍所定義)或類對象的靜態生命週期(如果它在名稱空間 範圍內定義)。如果你寫:

char* b = new char[5]; 

,則數組有你關心的任何一輩子給它—必須 明確終止其壽命比:

delete [] b; 

並與問候你的最後一個問題:

int const len = 5; 
char d[len]; 

是完全合法的,應該編譯。那裏有一個區別:

int len = 5; // _not_ const 
char d[len]; // illegal 
char* e = new char[len]; // legal 

的原因不同的是大多編譯技術之一, 歷史:在很早以前,編譯器必須知道在 以創建數組的長度局部變量。

+5

@ Di-0xide:從某種意義上說,它的完美性是通過*而不是提及堆和堆棧來增強的。 C++標準也沒有提及它們,它們的存在(或不)是一個實現細節。但是你確實問過「內存中發生了什麼」,並且答案的這一部分必須假設標準不關心的實現。 –

+1

C++中缺少可變長度數組的原因不僅僅是編譯器技術和歷史的一個偶然事件。數組的大小是類型的一部分。即'int [5]'和'int [6]'是不同的類型。具有未知大小的數組是不完整的類型。 C++拒絕做可變長度數組是C++嚴格類型安全性的一部分,並且意味着C++不會使原始數組的情況比已經通過特殊外殼事物更糟糕:'template void foo(T &t); ... int b [random(1,5)]; foo(b); // T是什麼類型? – bames53

+0

@ bames53也有這方面的內容,儘管它只在C++中有用。一個編譯器技術問題C99增加了VLAs,如果C++沒有在C++ 11中採用它們,那麼鍵入問題可能是原因,或者至少是其中的一部分(我想知道當你實例化一個數組時,g ++會做什麼在'int a [n]'中,其中'n'不是常量。) –

0

第三線路對應該工作,不應該是一個編譯錯誤。那裏肯定還有別的事情要做。

前兩個示例之間的區別是char a[5];的內存將自動釋放,而char* b = new char[5];分配的內存不會被釋放,直到您明確釋放它爲止。一旦特定變量超出範圍,就不能使用第一種方式分配的數組,因爲它的析構函數會自動調用,並且內存可以自由被覆蓋。對於使用new創建的數組,您可以將指針傳遞並在原始變量範圍之外自由使用,甚至可以在創建該函數之外的函數之外使用它,直到您使用delete爲止。

的東西,你不能做的是:

int a = 5; 
int *b = new int[a]; 

動態內存分配,大小必須在編譯時是已知的。

0

你的數組在堆棧上分配;這意味着,一旦程序編譯完成,它就知道它必須保留5個字節來存儲a的字符。相反,b只是被聲明爲一個指針,並且它的內容將在運行時在堆上分配,並且如果內存太稀缺,它可能會失敗。最後,由於已經被新出現,它必須在某個時候被刪除,否則你將會泄漏內存。

1

what happens in memory when the following happens:

char a[5]; 
char b* = new char[5]; 

char a[5]在堆棧內存中分配5個字符。
new char[5]在堆內存上分配5個字符。


And more directly related to why I asked this question, how come I can do:

const int len = 5; 
char* c = new char[len]; 

but not

const int len = 5; 
char d[len]; // Compiler error 

兩者都爲我編譯成功。

0

當你使用new時,你從free-store/heap分配內存,你需要照顧自己釋放它。而且,查找可用內存實際上可能需要一些時間,同樣可以釋放它。

當你不使用new時,你的內存在堆棧上被保留,並被隱式分配和釋放。即當你輸入一個函數時,調用堆棧將只擴展所有局部變量的大小(至少在概念上 - 例如,一些變量可以完全存在於寄存器中),並且當你離開函數時它會減少。

如上例所示,當您在堆棧中爲動態大小分配變量時,這意味着在輸入函數作用域時需要一些附加信息。具體而言,需要保留的空間量取決於功能輸入。現在,如果可以在函數開始時確定上下文,那麼一切都很好 - 這大概是爲什麼在C99中允許這樣做 - 但是如果你有一個變量用於大小誰的價值,你只知道中間函數,你最終添加「假」函數調用。與C++的範圍規則一起,這可能會變得非常多毛,所以從概念上來說,讓C++範圍通過std :: vector來處理這個問題會容易得多。

1

在C++中,你不能在棧中有動態數組。 C99具有此功能,但不包含C++。

當您聲明char d[ len ]時,您正在堆棧上分配空間。 當你做char *c = new char[ len ]你分配空間

堆有它的管理器,可以分配可變數量的內存。 在C++中,堆棧必須通過的常量表達式值進行分配,因此編譯器有足夠空間進行大量優化。 編譯器知道這種方式會在給定的上下文中花費多少空間,並且能夠預測堆棧幀。 對於動態數組,這是不可能的,所以語言人員決定禁止它(至少在C++ 11之前)。

0

char a[5]分配5 sizeof(char)字節到堆棧內存,當new char[5]分配這些字節到堆內存。分配給堆棧內存的字節也保證在作用域結束時釋放,不同於堆內存,您應該明確釋放內存。

char d[len]應該被允許,因爲變量被聲明爲const,因此編譯器可以很容易地讓代碼將這些字節分配給棧內存。

5

what happens in memory when the following happens:

char a[5]; 
char *b = new char[5]; 

假設一個典型的但稍微簡化的C++實現,並且上面的代碼中出現的函數:

char a[5]; 

堆棧指針由5個字節移動,使5-字節空間。名稱a現在指的是5個字節的內存塊。

char *b = new char[5]; 

堆棧指針由sizeof(char*)移動時,使空間b。一個函數被調用,它會從一個名爲「free store」的東西中分配5個字節,基本上它會從操作系統獲得的大塊內存中分割出5個或更多的字節,然後做一些記錄以確保何時你可以用delete[]釋放這些字節,它們將可用於將來的分配以重新使用。它返回分配的5字節塊的地址,該地址存儲在堆棧空間b

第二個工作比第一個工作更多的原因是分配了new的對象可以按任意順序刪除。局部變量(又名「堆棧中的對象」)總是按照與創建相反的順序銷燬,因此需要更少的簿記。在trivially-destructible類型的情況下,實現可以在相反的方向上移動堆棧指針相同的距離。

要刪除我所做的一些簡化操作:對於每個變量,堆棧指針並沒有真正移動一次,可能它只會在函數的所有變量的函數入口處移動一次,在這種情況下,所需的空間至少是sizeof(char*) + 5。堆棧指針或單個變量可能會有對齊要求,這意味着它不會被移動所需的大小,而是被一些舍入量所移動。實現(通常是優化器)可以消除未使用的變量,或者爲它們使用寄存器而不是堆棧空間。可能還有其他一些我沒有想到的東西。

const int len1 = random(1,5);

語言規則相當簡單:數組的大小必須是一個常量表達式。如果一個const int變量在同一個TU中有一個初始值設定項,並且初始值設定項是一個常量表達式,則該變量名可用於常量表達式中。 random(1,5)不是一個常量表達式,因此len1不能用於常量表達式中。 5是一個常量表達式,所以len2沒問題。

語言規則是爲了確保在編譯時已知數組大小。因此,要移動堆棧,編譯器可以發出相當於stack_pointer -= 5(其中stack_pointer將爲espr13或其他)的指令。這樣做之後,它仍然「確切地」知道每個變量與堆棧指針的新值有什麼偏移 - 與舊的堆棧指針不同。變量堆棧分配給實現帶來了更大的負擔。

相關問題