c++ - 局部變量初始化是否強制?



performance optimization (12)

作為一個簡單的例子,你能確定這將被初始化為(C / C ++)嗎?

bool myVar;

我們在一個產品中遇到了問題,有時會在屏幕上繪製圖像,有時甚至不是,通常取決於使用的​​機器。 事實證明,在我的機器上,它被初始化為假,在同事機器上,它被初始化為真。

未初始化的本地人所引起的維護問題(尤其是指針)對於那些已經做了一些c / c ++維護或增強的人來說是顯而易見的,但是我仍然可以看到他們,偶爾會聽到性能問題。

在c中很容易證明冗餘初始化被優化了:

$ less test.c
#include <stdio.h>
main()
{
#ifdef INIT_LOC
    int a = 33;
    int b;
    memset(&b,66,sizeof(b));
#else
    int a;
    int b;
#endif
    a = 0;
    b = 0;
    printf ("a = %i, b = %i\n", a, b);
}

$ gcc --version
gcc (GCC) 3.4.4 (cygming special, gdc 0.12, using dmd 0.125)

[未優化:]

$ gcc test.c -S -o no_init.s; gcc test.c -S -D INIT_LOC=1 -o init.s; diff no_in
it.s init.s
22a23,28
>       movl    $33, -4(%ebp)
>       movl    $4, 8(%esp)
>       movl    $66, 4(%esp)
>       leal    -8(%ebp), %eax
>       movl    %eax, (%esp)
>       call    _memset
33a40
>       .def    _memset;        .scl    3;      .type   32;     .endef

[優化:]

$ gcc test.c -O -S -o no_init.s; gcc test.c -O -S -D INIT_LOC=1 -o init.s; diff
 no_init.s init.s
$

所以WRT在什麼情況下的性能是強制變量初始化不是一個好主意?

如果適用的話,不需要限制對c / c ++的回答,但是要清楚語言/環境(和可重複的證據比推測更受歡迎!)。


在C / C ++中,我完全同意你的觀點。

在Perl中,當我創建一個變量時,它會自動置於默認值。

my ($val1, $val2, $val3, $val4);
print $val1, "\n";
print $val1 + 1, "\n";
print $val2 + 2, "\n";
print $val3 = $val3 . 'Hello, SO!', "\n";
print ++$val4 +4, "\n";

他們都被設置為undef最初。 Undef是一個虛假的價值和一個佔有者。 由於動態類型,如果我給它添加一個數字,它假定我的變量是一個數字,並用eqivilent的假值0替換undef。如果我做字符串操作一個字符串的假版本是一個空字符串,自動替換。

[[email protected] Code]$ ./undef.pl

1
2
Hello, SO!
5

所以Perl至少要宣布早點,不要擔心。 特別是大多數程序有很多變量。 您使用較少的行,看起來更乾淨,沒有明確的初始化。

 my($x, $y, $z);

:-)

 my $x = 0;
 my $y = 0;
 my $z = 0;

性能? 如今? 也許回來時,CPU在10MHz運行是有道理的,但今天它幾乎沒有問題。 總是初始化它們。


我不確定是否有必要“使其成為強制性”,但我個人認為初始化變量總是更好。 如果應用程序的目的是盡可能的緊湊,那麼C / C ++就是為此目的而打開的。 不過,我認為我們中的很多人已經被燒了一兩次了,沒有初始化一個變量,並假設它包含一個有效的值(例如指針),但實際上並沒有。 地址為零的指針比在該特定位置上的最後一個存儲器內容具有隨機垃圾更容易檢查。 我認為在大多數情況下,這不再是一個表現問題,而是一個清晰和安全的問題。


有時一個變量被用來“收集”嵌套ifs / elses的更長塊的結果...在這種情況下,我有時會保持變量未初始化,因為它應該稍後被其中一個條件分支初始化。

訣竅是:如果我一開始就沒有初始化它,然後long if / else塊中有一個錯誤,所以變量永遠不會被分配,我可以在Valgrind中看到這個錯誤:-)這當然需要經常運行代碼理想的定期測試)通過Valgrind。


有時你需要一個變量作為佔位符(例如使用ftime函數),所以在調用初始化函數之前初始化它們是沒有意義的。

不過,在我看來,並不是壞事,詮釋你意識到陷阱的事實,

uninitialized time_t t;
time( &t );

簡短的答案:聲明變量盡可能接近第一次使用,並初始化為“零”,如果你仍然需要。

長答案:如果你在一個函數開始時聲明了一個變量,並且稍後才使用它,那麼你應該重新考慮將你的變量放置到本地作為範圍。 然後通常可以立即為其分配所需的值。

如果你必須聲明它是未初始化的,因為它被賦值在一個條件中,或者被引用傳遞並賦值給它,將它初始化為一個與null等價的值是個不錯的主意。 如果你在-Wall下編譯,編譯器有時可以保存你的內容,因為如果你在初始化之前從變量讀取數據,它會發出警告。 但是,如果您將其傳遞給函數,則不會警告您。

如果您安全地使用它並將其設置為null,那麼如果您將其傳遞給它的函數覆蓋它,則不會造成任何負面影響。 但是,如果傳遞給它的函數使用該值,則幾乎可以保證斷言失敗(如果有的話),或者至少使用一個空對象進行斷開。 隨機初始化可以做各種不好的事情,包括“工作”。


總是初始化局部變量至少為零。 如你所見,沒有真正的表現。

int i = 0;
struct myStruct m = {0};

如果是這樣的話,基本上就是增加1或2個彙編指令。 事實上,許多C運行時將在“發布”版本上為您執行此操作,而您將不會更改某個內容。

但是你應該把它變成現實,因為你現在有這個保證。

不初始化的一個原因是與調試有關。 一些運行時間,例如。 MS CRT,將用預先確定和記錄的模式初始化內存,您可以識別。 所以當你通過內存灌輸時,你可以看到內存確實是未初始化的,並沒有被使用和重置。 這可以在調試中有所幫助。 但那是在調試過程中。


它應該強制性的。 原因與性能無關,而是使用單位變量的危險。 但是,有些情況看起來很荒謬。 例如,我看到:

struct stat s;
s.st_dev = -1;
s.st_ino = -1;
s.st_mode = S_IRWXU;
s.st_nlink = 0;
s.st_size = 0;
// etc...
s.st_st_ctime = -1;
if(stat(path, &s) != 0) {
   // handle error
   return;
}

跆拳道???

請注意,我們正在處理錯誤,所以如果統計失敗會發生什麼情況是沒有問題的。


這是過早優化是萬惡之源的一個很好的例子

完整的報價是:

毫無疑問,效率的瓶頸會導致濫用。 程序員浪費了大量的時間來思考或者擔心程序中非關鍵部分的速度,而這些效率的嘗試實際上在考慮調試和維護時會產生很大的負面影響我們應該忘記小效率,大約97%的時間:不成熟的優化是萬惡之源。 但是,我們不應該把這個關鍵的3%放在一邊。 一個好的程序員不會被這樣的推理所迷惑,他會聰明地仔細看看關鍵的代碼; 但只有在代碼已經確定之後。

這是來自唐納德·克努特 。 你會相信誰?你的同事或Knuth?
我知道我的錢在哪裡

回到原來的問題:“我們應該強制初始化嗎?
我會這樣說:

變量應該初始化,除非可以證明通過不初始化可以實現顯著的性能增益。 來武裝硬數字...


讓我告訴你一個關於我在1992年工作過的產品的故事,後來為了這個故事的目的,我們會打電話給Stackster。 我被分配了一個錯誤,導致應用程序崩潰在Mac上,但不是在Windows上,哦,錯誤是不可靠的重現。 花了一個星期的時間,QA花了大約十分之一的時間來製作一個配方。

由於實際發生的事故發生後,發生了很大的事情,所以這是在追查事件的根源。

最終,我通過為編譯器編寫自定義代碼分析器來追踪它。 編譯器會很高興地把調用注入到全局的prof_begin()和prof_end()函數中,你可以自由地實現它們。 我寫了一個分析器,它從堆棧中獲取返回地址,找到堆棧框架創建指令,在堆棧中定位代表函數本地的塊,並為它們塗上一層美味的垃圾,如果有的話會導致總線錯誤元素被取消了。

這引發了一些類似於在初始化之前使用的指針錯誤,包括我正在尋找的錯誤。

發生的事情是,大多數情況下,堆棧的價值顯然是良性的,如果它們被廢除的話。 其他時候,價值會導致應用程序霰彈槍自己的堆,取出應用程序很久以後的應用程序。

我花了兩個多星期試圖找到這個錯誤。

課程:初始化你的本地人。 如果有人吠叫你的表現,給他們看這個評論,並告訴他們,你寧願花兩週的時間來分析代碼,找出瓶頸,而不是去追踪這樣的錯誤。 調試工具和堆檢查器已經變得更好了,因為我必須這樣做,但坦率地說,他們更好地補償這樣的不良做法的錯誤。

除非你在一個微小的系統上運行(嵌入等),否則初始化本地應該幾乎是免費的。 MOVE / LOAD指令非常非常快。 編寫代碼要堅實和可維護的第一。 重構它是高性能的第二。


是的: 總是初始化你的變量,除非你有一個很好的理由不要。 如果我的代碼不需要一個特定的初始值,我會經常初始化一個變量的值,如果下面的代碼被破壞,將保證一個明顯的錯誤。





optimization