2010-12-22 91 views
4

POD表示沒有構造函數和析構函數的基本數據類型。靜態本地POD常量是如何初始化的?懶惰或不?

我很好奇,編譯器如何處理POD靜態局部變量的延遲初始化。如果函數意圖在多線程應用程序中的嚴格循環內運行,那麼惰性初始化的含義是什麼?這些是可能的選擇。哪一個更好?

void foo_1() { 
    static const int v[4] = {1, 2, 3, 4}; 
} 

void foo_2() { 
    const int v[4] = {1, 2, 3, 4}; 
} 

這個怎麼樣?沒有懶惰的初始化,但語法稍微笨拙?

struct Bar 
{ 
    static const int v[4]; 

    void foo_3() 
    { 
     // do something 
    } 
}; 

const int My::v[4] = {1, 2, 3, 4}; 

回答

4

當一個靜態變量用常量數據初始化時,我熟悉的所有編譯器都會在編譯時初始化這些值,這樣就沒有任何運行時間開銷。

如果該變量不是靜態的,則必須在每個函數調用時分配該變量,並且必須將值複製到該變量中。我想如果它是一個const變量,編譯器可能會將其優化爲靜態,除了可以拋棄常量。

+2

+1:注意:在成本對象上拋出const'ness然後修改它UB(因爲它可能存儲在只讀存儲器中)。 – 2010-12-22 20:49:39

+0

- **通常**只讀存儲器中存儲,只要流行的編譯器去。 – 2010-12-22 21:56:30

+0

@Adam:我相信只有在變量是靜態的情況下。堆棧上的常量const變量只存儲在常規堆棧中。 – Puppy 2010-12-22 22:04:33

2

foo_1()v在之前的某個時候開始main()初始化。在foo_2()中,每調用foo_2()就會創建並初始化v。使用foo_1()來消除額外的成本。

在第二個示例中,Bar::v也在main()之前的某個時間初始化。

2

表現比分配更爲複雜。例如,您可能會導致額外的緩存行必須與靜態變量緩存在一起,因爲它與您正在使用的其他本​​地內存不相鄰,並且會增加緩存壓力,緩存未命中等等。與這個代價相比,我會說每次重新分配堆棧上的數組的開銷非常小,這是非常微不足道的。不僅如此,任何編譯器在優化這類事情方面都很出色,但對靜態變量卻無能爲力。

在任何情況下,我都會建議兩者之間的性能差異很小 - 即使是在緊密環路中。

最後,你可以使用foo_2() - 編譯器完全在它的權限之內,就像創建一個類似於靜態的變量。由於它最初被定義爲const,所以const_casting的const是不確定的行爲,不管它是否是靜態的。但是,它不能選擇將靜態常量設置爲非靜態常量,例如,取決於返回地址的能力。

1

查找變量初始化方式的簡單方法是打印具有靜態變量和局部變量的函數的彙編語言列表。

並非所有的編譯器都使用相同的方法初始化變量。這是一種常見的做法: 在main()方法之前,通過將一部分值複製到變量中來初始化全局變量。許多編譯器會將常量放入一個區域,以便可以使用簡單的彙編移動或複製指令來分配數據。

本地變量(帶局部作用域的變量)可以在進入本地作用域時並在作用域中第一條語句執行之前初始化。這取決於很多因素,其中之一是變量的const

常量可以直接放入可執行代碼中,或者它們可能是指向ROM中的值的指針,或者被複制到內存或寄存器中。這取決於編譯器的設置,由編譯器決定最佳性能或代碼大小。

1

在技術方面,foo_1foo_3需要任何功能,包括類的構造函數,調用之前初始化其數組。這種保證基本上和沒有運行時一樣好。實際上,大多數實現不需要任何運行時來初始化它們。

此保證僅適用於具有靜態存儲持續時間的POD類型對象,這些對象使用「常量表達式」進行初始化。一些更多的對比例子:

void foo_4() { 
    static const int v[4] = { firstv(), 2, 3, 4 }; 
} 

namespace { // anonymous 
    const int foo_5_data[4] = { firstv(), 2, 3, 4 }; 
} 
void foo_5() { 
    const int (&v)[4] = foo_5_data; 
} 

foo_4的數據初始化首次foo_4被調用。 (請檢查您的編譯器文檔以確定它是否是線程安全的!)

foo_5的數據在main()之前的某個時間初始化,但可能在其他一些動態初始化之後。

但是這些都沒有回答關於性能的問題,我沒有資格評論這一點。 @ DeadMG的答案看起來很有幫助。

1

在所有這些情況下,您都有一個靜態初始化,所有的靜態變量將通過將數據段加載到內存中來初始化。如果編譯器發現它可能,foo_2中的const可以被初始化。 如果你有一個動態的初始化,那麼命名空間範圍內變量的初始化可以推遲到第一次使用。同樣,函數範圍內的局部靜態變量的動態初始化可以在首次通過函數或更早的函數期間執行。此外,編譯器可以靜態初始化這些變量,如果它能夠做到的話。我不記得標準的確切語言。