2010-06-23 34 views
14

從C背景來看,我一直認爲POD類型(例如ints)從來不會在C++中自動初始化,但似乎這顯然是錯誤的!C++ POD類型何時獲得零初始化?

我的理解是,只有'裸'的非靜態POD值不會被零填充,如代碼片段所示。我說得沒錯,還有其他重要的案例我錯過了嗎?

static int a; 

struct Foo { int a;}; 

void test() 
{ 
    int b;  
    Foo f; 
    int *c = new(int); 
    std::vector<int> d(1); 

    // At this point... 
    // a is zero 
    // f.a is zero 
    // *c is zero 
    // d[0] is zero 
    // ... BUT ... b is undefined  
} 
+0

你確定它是C++而不是你的操作系統嗎?我會想象(但沒有檢查過),當一個操作系統爲你的進程分配內存時,至少當你觸及內存頁面時,它會將其歸零。 C和C++都不需要這種行爲,但是如果操作系統用最後一個進程放入其中的任何一個內存頁面,這將是一個很大的安全漏洞。如果ssh是使用該物理內存頁面的最後一個進程,那麼可能存在登錄信息或私鑰。 – 2010-06-23 13:45:27

+0

操作系統在分配內存時要保持內存清零似乎有點浪費。 – Alan 2010-06-23 13:46:41

+2

您可能還會閱讀Michael Burr在回覆[[類型名稱與新的區別之後的括號是否相同]]的優秀相關文章(http://stackoverflow.com/questions/620137/do-the-parentheses-在類型名稱make-a-difference-with-new/620402#620402) – 2010-06-23 13:46:43

回答

16

假設你沒有修改a在調用test()之前,a的值爲零,因爲具有靜態存儲持續時間的對象在程序啓動時被初始化爲零。

d[0]的值爲零,因爲由std::vector<int> d(1)調用的構造函數具有接受默認參數的第二個參數;第二個參數被複制到正在構建的向量的所有元素中。默認參數爲T(),所以你的代碼就相當於:

std::vector<int> d(1, int()); 

你是正確b有一個不確定的值。

f.a*c都具有不確定的值。要重視對它們進行初始化(這對於POD類型是一樣的零初始化),你可以使用:

Foo f = Foo();  // You could also use Foo f((Foo())) 
int* c = new int(); // Note the parentheses 
+0

大部分同意;只有我不確定爲什麼'Foo f'不調用合成的構造函數,如果是這樣,爲什麼這與'Foo f = Foo();'... – xtofl 2010-06-23 13:57:15

+0

謝謝。並感謝您的評論中的真棒鏈接! – Roddy 2010-06-23 13:57:56

+1

@xtofl:如果對於POD類型的對象沒有初始化方法(就像'Foo f;'的情況那樣),那麼該對象將保持未初始化狀態。 'Foo f = Foo();'創建一個初始化值爲'Foo'的值(這就是'Foo()'所做的),然後用它來初始化'f'。 – 2010-06-23 14:00:56

1

其實有些是零值的可能是因爲你想在應用程序的調試版本的代碼(如果是這樣的話)。

如果我沒有記錯的話,在你的代碼:

  • 一個應該初始化。
  • 乙方應未初始化
  • C必須指向新的(未初始化)詮釋
  • d應該被初始化爲[0](如你猜中)
+0

我覺得今天大多數類Unix操作系統中'a'會被清除爲'0'。從技術上講,'a'會出現在'.bss'段中,在'main()'被調用之前通常被設置爲全0。 – 2010-06-23 13:47:24

+0

是的,但我的觀點是,即使代碼中的值顯示爲零,他也不應該依賴該值。顯式初始化是這裏的方法。 – utnapistim 2010-06-23 13:51:33

+1

除非'a'在調用'test()'之前被修改,否則它的值爲零。具有靜態存儲持續時間的對象在程序啓動時被初始化爲零。 – 2010-06-23 13:55:55

0

對於我來說,POD類型取決於它們被放置在內存的一部分初始化。您的static int a分配給數據段,因此它在啓動時具有默認值。然而,我認爲f在你的例子中沒有inizialized ...

0

他們沒有。調試位版本可能會這樣做,但通常它只是放在內存中,並初始化爲內存中的任何值。

1

請注意,操作系統作爲安全功能執行的零初始​​化通常僅在首次分配內存時完成。我的意思是堆,堆棧和數據部分中的任何段。堆棧和數據部分通常是固定大小的,並且在應用程序加載到內存時被初始化。

數據段(包含靜態/全局數據和代碼)通常不會被「重用」,但如果您在運行時動態加載代碼,則可能不是這種情況。

堆棧段中的內存會一直重複使用。局部變量,函數堆棧幀等都被不斷地使用和重用,並且不會每次都初始化 - 就在應用程序第一次加載時。

但是,當應用程序發出對堆內存的請求時,內存管理器通常會在授予請求之前對內存段進行零初始化,但僅限於新段。如果您請求堆內存,並且已經初始化的段中有可用空間,則不會再次進行初始化。因此,不能保證如果你的應用程序重用了這段特定的內存,它將再次被初始化爲零。因此,例如,如果您在堆上分配Foo,爲其字段賦值,刪除Foo實例,然後在堆上創建新的Foo,則有可能分配新的Foo與舊的Foo具有相同的內存位置,所以它的字段最初將具有與舊的Foo字段相同的值。

如果你仔細想想,這是有道理的,因爲操作系統只是初始化數據,以防止一個應用程序訪問另一個應用程序的數據。允許應用程序訪問自己的數據的風​​險較小,所以出於性能方面的考慮,初始化並不是每次都進行 - 這是第一次讓特定的內存段可供應用程序使用(在任何段中)。

但是,有時在調試模式下運行應用程序時,某些調試模式運行時會在每次分配時初始化堆棧和堆數據(所以您的Foo字段將始終初始化)。但是,不同的調試運行時將數據初始化爲不同的值。一些零初始化,一些初始化爲「標記」值。

問題是 - 永遠不要在代碼的任何地方使用未初始化的值。絕對不能保證它們將被初始化爲零。此外,請務必閱讀之前關於parens和默認值初始值的鏈接文章,因爲這會影響「未初始化」值的定義。

+0

*絕對*是保證在OP的例子中'a'將被初始化爲零。 – 2017-12-21 18:12:06