2011-03-09 136 views
25

我偶然發現了一篇內容豐富的文章:http://cnicholson.net/2009/02/stupid-c-tricks-adventures-in-assert/ 其中指出了我當前調試宏套件中存在的大量問題。自定義C++聲明宏

如果您關注該鏈接,則會在文章結尾處給出宏的最終版本的完整代碼。

所呈現的一般形式是這樣的(有人請糾正我,如果我在調換它是錯的):

#ifdef DEBUG 
#define ASSERT(cond) \ 
    do \ 
    { \ 
     if (!(cond)) \ 
     { \ 
      ReportFailure(#cond, __FILE__, __LINE__, 0); \ 
      HALT(); \ 
     } \ 
    } while(0) 
#else 
#define ASSERT(cond) \ 
    do { (void)sizeof(cond); } while(0) 

雖然想修改我的代碼有什麼我已經學會了,我注意到一對夫婦有趣的變化張貼在該文章的意見:

一個是你不能使用這個宏與三元運算符(即cond?ASSERT(x):func()),並建議將if()替換爲三元運算符和一些括號以及逗號運算符。後來另一位評論者提供這樣的:

#ifdef DEBUG 
#define ASSERT(x) ((void)(!(x) && assert_handler(#x, __FILE__, __LINE__) && (HALT(), 1))) 
#else 
#define ASSERT(x) ((void)sizeof(x)) 
#endif 

我想使用的邏輯和&&在這種情況下,特別聰明,在我看來,這個版本比一個使用if甚至三元?:更加靈活。更好的是,assert_handler的返回值可以用來確定程序是否應該停止。雖然我不確定它爲什麼是(HALT(), 1)而不是HALT()

這裏有我忽略的第二個版本有什麼特別的缺點嗎?它消除了圍繞宏的do{ } while(0),但在這裏似乎沒有必要,因爲我們不需要處理if s。

您認爲如何?

+1

+1。有趣的鏈接。 – Maxpm 2011-03-09 21:35:52

+0

另請參見我的答案如下:http://stackoverflow.com/a/21827201/216063 – 2014-04-26 12:41:24

回答

26

在C和C++標準庫中,assert是充當函數所需的宏。該要求的一部分是用戶必須能夠在表達式中使用它。例如,標準assert我可以做

int sum = (assert(a > 0), a) + (assert(b < 0), b); 

這在功能上相同

assert(a > 0 && b < 0) 
int sum = a + b; 

即使前者可能不是寫一個表情一個很好的辦法,關鍵還是很在許多更合適的情況下有用。

這直接意味着,如果一個人想自己定製ASSERT宏模仿標準assert行爲和可用性,然後在ASSERT定義使用ifdo { } while (0)是毫無疑問的。其中一種僅限於表達式,即使用?:運算符或短路邏輯運算符。

當然,如果一個人不在乎製作像標準一樣的自定義ASSERT,那麼可以使用任何東西,包括if。鏈接的文章似乎沒有考慮這個問題,這很奇怪。在我看來,函數式的斷言宏肯定比非函數式更有用。

至於(HALT(), 1) ...這樣做是因爲&&運算符需要一個有效的參數。 HALT()的返回值可能不代表&&的有效參數。對於我所知道的,它可能是void,這意味着僅僅HALT()根本不會編譯爲&&的參數。 (HALT(), 1)始終評估爲1,並且類型爲int,它始終是&&的有效參數。因此,無論HALT()的類型如何,(HALT(), 1)始終是&&的有效參數。

您對do{ } while(0)的最新評論似乎沒有多大意義。將宏包含進do{ } while(0)的要點是在宏定義內處理外部if,而不是處理if。您總是必須處理外部if s,因爲您的宏總是有機會在外部使用if。在後面的定義中不需要do{ } while(0),因爲該宏是表達式。作爲一個表達,它已經自然地與外部的if s沒有問題。所以,沒有必要對他們做任何事情。而且,正如我上面所說的那樣,將它封入do{ } while(0)將徹底擊敗它的目的,將其變爲非表達。

+2

「在我看來,類似功能的斷言宏比非功能類更有用。」 - 爲什麼?這一切何時有用?我認爲斷言應該始終獨立於代碼中,永遠不會用在表達式中。 – 2011-03-09 22:02:37

+0

我會說這是優越的,因爲assert可以在表達式中使用_able_以及在典型的「獨立」方式中使用。更多的用戶選擇。 @AndreyT,謝謝你讓我清楚地看到這種區別。它看起來很酷,但現在看起來'do {} while(0)'結構並不是非常有用。事實是,我一直在使用相當多的宏,這些宏只包含一個大的if語句,在這一點上顯然不是正確的做法。 – 2011-03-09 22:14:45

6

雖然我不知道爲什麼它是(HALT(), 1),而不是僅僅HALT()

我想HALT可能是一個宏(或其他替代名稱)爲exit。假設我們想要使用exit(1)作爲我們的HALT命令。 exit返回void,它不能作爲&&的第二個參數進行評估。如果您使用逗號運算符來評估它的第一個參數,然後評估並返回它的第二個參數的值,我們有一個整數(1)返回到&&,即使我們從未達到那個點,因爲HALT()會導致我們停止早在那之前。

基本上,任何填寫HALT的函數都可能返回值爲void,因爲它返回任何值都沒有意義。我們可能使它返回一個int,只是爲了宏觀,但如果我們已經用宏觀黑客多一點hackery不能傷害,可以嗎?

+1

所以這是一種讓編譯器很開心的方式,並且通過給它一個返回的值來調用exit作爲副作用(終止程序!相當一個我必須說的副作用)。整齊。 – 2011-03-09 21:50:51

8

爲了完整起見,我發表了一個活動的2個文件斷言宏實現在C++:

#include <pempek_assert.h> 

int main() 
{ 
    float min = 0.0f; 
    float max = 1.0f; 
    float v = 2.0f; 
    PEMPEK_ASSERT(v > min && v < max, 
       "invalid value: %f, must be between %f and %f", v, min, max); 

    return 0; 
} 

將提示您:

Assertion 'v > min && v < max' failed (DEBUG) 
    in file e.cpp, line 8 
    function: int main() 
    with message: invalid value: 2.000000, must be between 0.000000 and 1.000000 

Press (I)gnore/Ignore (F)orever/Ignore (A)ll/(D)ebug/A(b)ort: 

  • (I)gnore:忽略當前聲明
  • 忽略(F)記得文件和行,其中斷言解僱, 忽略它的程序的剩餘執行
  • 忽略(A)LL:忽略所有剩餘的斷言(所有文件和線)
  • (d)ebug:闖入如果調試器連接,否則abort()(在Windows上, 系統會提示用戶附加一個調試器)
  • A(b)ORT:調用abort()立即

你可以找到更多關於它的存在:

希望有所幫助。

+0

謝謝,這是非常整潔。現在我會堅持使用自己的解決方案,但我會說,與你在這裏相比,它看起來像是一個醜陋的黑客。 – 2014-02-17 11:04:13

+0

如果您決定切換,請隨時與我聯繫。我到目前爲止在Mac,Linux,Windows,iOS和Android上測試過它。 – 2014-02-17 14:16:31