儘管這個問題已經收到許多無益的非答案,但我認爲它在遺留代碼庫的背景下有很多優點。
想象一下,多年來積累了許多斷言,但由於沒有使用NDEBUG進行構建/測試的習慣,因此一些副作用已經流入斷言中,現在您不敢斷言斷言了。
您可以打開NDEBUG並檢測測試套件中的某些測試失敗,但將測試失敗與「有效」斷言關聯起來並不容易,因爲它可能距離檢測失敗的地方非常遠。即使是具有良好覆蓋範圍的測試套件也不能完全信任。
您可以對代碼中的所有斷言進行代碼審查,但這可能是很多工作並容易出現人爲錯誤。如果某些靜態分析已經可以消除所有可以證明不會出現副作用的斷言,並且只需要調查那些不能保證其缺席的情況,那將會好得多。
下面介紹如何使用編譯器的優化器進行這種靜態分析。假設你通過組織來代替assert
宏的定義:
extern int not_supposed_to_survive;
#define assert(expr) ((void)(not_supposed_to_survive || (expr)))
如果expr
有任何副作用,效果的執行是以全局變量not_supposed_to_survive
的價值條件。但是如果expr
沒有任何副作用,那麼全局變量的值無關緊要(注意expr
結果將被丟棄)。 好的優化器知道這一點,並將消除全局變量not_supposed_to_survive
的負載,因此該變量的名稱。
如果我們的程序沒有包含符號not_supposed_to_survive
的定義,那麼當負載沒有被消除時,我們會得到一個鏈接錯誤,我們可以使用它來檢測潛在的有效斷言。
E.g.與海灣合作委員會4.8:
int g;
int foo() { return ++g; }
int main() {
assert(foo());
return 0;
}
gcc -O2 assert_effect.c
/tmp/ccunynya.o: In function `main':
assert_effect.c:(.text.startup+0x2): undefined reference to `not_supposed_to_survive'
collect2: error: ld returned 1 exit status
編譯器幫助我找到一個可疑的斷言!另一方面,如果我將++g
替換爲g+1
,則鏈接錯誤消失,我不必調查。事實上,這個斷言是保證無害的。
當然,可證明無副作用的概念受限於優化程序「可以看到」的內容。爲了更精確的分析,我建議使用鏈接時間優化(gcc -flto
)來分析各個編譯單元。
更新:我將此應用於使用gcc 5.3的現實生活C++代碼庫。要使用鏈接時優化,基本上使用gcc -flto -g
作爲編譯器/鏈接器(編譯器/鏈接器上的-g
選項以獲取鏈接錯誤的行參考),並使用gcc-ar
和gcc-ranlib
作爲任何靜態庫的存檔器/索引器。
這種設置可以極大地減少我不得不調查的斷言的數量。用最少的人力,我能夠使斷言變得清晰。誤報,我仍然不得不手動調低的原因是:
- 虛函數調用
- 非平凡循環/遞歸(其中優化不能證明他們是有限的)
此外,我還會得到一些確實含有副作用的斷言,但它們無害或不重要,例如:
不,GCC不檢查功能是否有副作用。 –
也許需要在提交之前進行代碼審查。 –
爲了澄清我的問題,我們沒有資源對這些存儲庫中的某些存儲庫執行手動代碼審查。對於我們的高優先級,我們做代碼審查。我一直在尋找自動化的東西,這將使我們不得不爲這些微不足道的錯誤承諾提供回扣。 – Matthew