標籤:

為什麼C++編譯器不能發現未初始化的變數?

摘自C++ Primer 4th

Java使用未初始化變數會報錯,似乎說明編譯器能檢測出,為什麼C++編譯器不能,差別在哪?

書是盜版的,匿了


主流的C++編譯器可以檢測出一些明顯的未初始化問題(比如剛定義一個未初始化的臨時變數就把它賦給其他變數),但沒法抓出所有未初始化變數的使用。

根本原因是C++與Java對於變數初始化的要求不同。

C++標準只規定一個可能會被優化到寄存器中的自動存儲變數(非靜態的局部變數)如果沒有初始化或者沒有賦值過,將它賦給lvalue就會導致Undefined Behavior,但並沒有規定一個變數必須要初始化或者以什麼形式、什麼時刻被賦值。所以對於

int i;
init(i); // initialize i
int j=i;

這樣的code,如果init函數是在其他的編譯單元中實現的,編譯器就不可能知道i到底有沒有被賦值過,自然沒法判定將i賦給j的行為合不合法。

而Java不同,Java標準第16章明確規定一個變數定義後必須被確定性賦值,否則會導致編譯失敗。這個要求就高的多,像下面這個例子都是編譯不通過的(雖然k在使用前明顯會被賦值過)

{
int k;
int n = 5;
if (n &> 2)
k = 3;
System.out.println(k); /* k is not "definitely assigned"
before this statement */
}


不是「C++編譯器不能發現」。

這段話的意思是

  1. C++標準沒有強制要求C++編譯器要去發現所有這類錯誤;
  2. 現實中的C++編譯器沒有一個能在所有情況下發現這類錯誤。

實際上可能C++編譯器能在90%的情況下該報warning就報warning,不至於說「不能發現」。

至於為什麼達不到100%,Gomo Psivarh舉的例子可以說明。

注意,實際上任何編譯器想做到「100%地報warning」都是容易的,但是在C++語言的語法規定下,這麼做很容易帶來過多的false positive。所以實際上編譯器都是盡量提高分析能力的前提下在漏報和誤報之間找個合適的平衡。漏報的例子Gomo Psivarh的例子就是,誤報的例子給一個:

int x;
if (some_function_that_always_returns_zero() == 0)
{
x = 0;
}
return x;

編譯器可能會無法分析出if永遠被滿足而誤報。


非不能也,實不為也……


能啊,g++,clang,cl都有代碼檢查啊


非C++專業,求輕噴。

我的理解是:Java編譯器能做到初始化檢查,是因為Java語言規範保證了你一旦定義了一個變數,這個變數所指向的內存就只能通過這個變數名來訪問了,因此編譯器能輕鬆通過變數名的使用情況判斷是否已初始化。而C或C++允許各種定址方式繞過原本的變數名去訪問其所對應的內存(比如說,引用傳參),編譯器無法在所有場景中通過單一變數名的使用情況確定其對應的內存是否已被初始化。


int foo() {

int i;

maybe_init(i);

i++;

}


java的沒有初始化是真的沒有初始化,c++的不一定。對於非內置類型,沒有初始化只是沒有調用帶參數的構造函數而已,構造函數還是被調用了,所以初始化還是進行了


指針未初始化會編譯器沒有報錯提示的!

例如:

char * alpha;

strcpy (alpha, "balabala");

/* 例如這種果斷Segmentation fault, 其實還是要寫

alpha = (char *) malloc (sizeof (char) * 16);

的嗎! */


你自己在IDE里試一下吧,vs肯定是可以的


有些是否初始化,需要運行時確定,編譯時怎麼檢測出來?


推薦閱讀:

數據結構課本中的「生命遊戲」有哪些奇葩的玩法?
如何理解《Effective C++》第31條將文件間的編譯依賴關係降低的方法?
輪子哥的C++為什麼學的這麼好?請分享一下學習C++秘訣?
為什麼bs虛函數表的地址(int*)(&bs)與虛函數地址(int*)*(int*)(&bs) 不是同一個?
C++ 鏈接時間過長,如何找到原因?

TAG:Java | C | 編譯器 |