2012-05-15 29 views
10

我們有幾個中等大小的C代碼庫,它們接收來自具有各種經驗級別的開發人員的提交。一些不太嚴謹的程序員提交了assert()聲明,其副作用會導致聲明被禁用。例如。捕捉帶有副作用的斷言()

assert(function_that_should_always_be_called()); 

我們已經用我們自己的assert()實現,但評估與界定NDEBUG表達將導致不可接受的性能下降。是否有GCC擴展或我們可以通過的標誌會觸發編譯時警告/錯誤?通過簡單的控制流程,GCC應該可以確定您只是調用純函數。

+0

不,GCC不檢查功能是否有副作用。 –

+4

也許需要在提交之前進行代碼審查。 –

+0

爲了澄清我的問題,我們沒有資源對這些存儲庫中的某些存儲庫執行手動代碼審查。對於我們的高優先級,我們做代碼審查。我一直在尋找自動化的東西,這將使我們不得不爲這些微不足道的錯誤承諾提供回扣。 – Matthew

回答

3

使用簡單的控制流程,GCC應該可以確定您只是調用純函數。

如果它不是一個簡單的控制流程,它將如何知道它是否純淨?


像這樣的東西可能是你最好的選擇:

#ifdef NDEBUG 
#define assert(s) do { (s); } while(false) 
#else 
// ... 
#endif 

幾個表情會編出來,包括__attribute__((pure))功能。

最合乎邏輯的解決方案是僅檢查您的代碼並修復錯誤。

+1

同意 - 對於assert()'的正確使用,其中表達式沒有副作用 - 只要啓用了優化,編譯器將能夠刪除代碼。轉換爲'(void)'在這裏很有用,因爲它可能會阻止編譯器警告沒有副作用的語句。 – caf

+0

很明顯,更復雜的控制流和類似遞歸的東西可以把它變成暫停問題,但大多數斷言都是相對簡單的。我正在設想一些超時或最大通話深度限制的檢查。至於這個建議,正如我在問題中所說的那樣,我特別不希望在定義NDEBUG時評估所有斷言。 – Matthew

+0

@Mthethew「我正在設想一些超時或最大通話深度限制的檢查。」 - 與編譯時檢查非純函數有關的是什麼?在絕望中,你無法理解海灣合作委員會的一些特徵,或者對手冊的細讀......顯而易見並不存在。 –

4

即使GCC可以可靠地檢測到純粹的計算(這將需要解決暫停問題),但一個標記必須具有額外的魔力才能注意到非純計算作爲參數傳遞到您自己生成的斷言宏。擴展也無法幫助 - 它究竟應該做什麼?

解決你的問題是

  1. 服務能力的開發者。
  2. 教育您的開發人員如何使用斷言(除其他外)。
  3. 做代碼評論。
  4. 對可交付版本進行所有測試 - 如果斷言在可交付成果中關閉,則斷言(function_that_should_always_be_called())與簡單地省略function_that_should_always_be_called()相同,這是應該在測試中發現的明顯錯誤。
+3

我認爲這些「解決方案」是無益的。但更重要的是,因爲純粹的計算不能被檢測到,所以並不意味着消除純粹的計算並不是非常有用。 http://stackoverflow.com/a/35294344/6918 –

+0

什麼是沒有幫助的:a)恐嚇報價b)陳述個人意見,這些解決方案是沒有幫助的,沒有反駁提供c)明目張膽的稻草人 - 沒有人說檢測大多數純粹的計算是沒有用的d)通過downvotes驅動。布魯諾的答案提供了一個聰明的技巧,它應該被接受,但可以提供這樣的答案而不是一個混蛋。 –

5

儘管這個問題已經收到許多無益的非答案,但我認爲它在遺留代碼庫的背景下有很多優點。

想象一下,多年來積累了許多斷言,但由於沒有使用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-argcc-ranlib作爲任何靜態庫的存檔器/索引器。

這種設置可以極大地減少我不得不調查的斷言的數量。用最少的人力,我能夠使斷言變得清晰。誤報,我仍然不得不手動調低的原因是:

  • 虛函數調用
  • 非平凡循環/遞歸(其中優化不能證明他們是有限的)

此外,我還會得到一些確實含有副作用的斷言,但它們無害或不重要,例如:

  • 包含日誌語句的函數
  • 功能是緩存的結果(S)
0

我不知道它是否會夠你所描述的應用程序,但cppcheck查找「assertWithSideEffect」 S: http://cppcheck.sourceforge.net/devinfo/doxyoutput/checkassert_8cpp_source.html

這裏什麼編譯時消息看起來像: [assertWithSideEffect] myFile.cpp:42:警告:非純函數:'myFunction'在assert語句中調用。斷言語句從發佈版本中刪除,所以assert語句中的代碼不會被執行。如果代碼在發佈版本中也需要,這是一個錯誤。 「Cppcheck是一個用於C/C++代碼的靜態分析工具,與C/C++編譯器和許多其他分析工具不同,它不檢測代碼中的語法錯誤。Cppcheck主要檢測編譯器通常不會檢測到的錯誤類型檢測。目標是僅檢測代碼中的實際錯誤(即,具有零誤報)。「 http://cppcheck.sourceforge.net/