轉載:http://c.biancheng.net/ref/assert.html
void assert (int expression);
斷言函數,用於在調試過程中捕捉程序的錯誤。
“斷言”在語文中的意思是“斷定”、“十分肯定地說”,在編程中是指對某種假設條件進行檢測,如果條件成立就不進行任何操作,如果條件不成立就捕捉到這種錯誤,並打印出錯誤信息,終止程序執行。
assert() 會對錶達式expression進行檢測:
1、如果expression的結果爲 0(條件不成立),那麼斷言失敗,表明程序出錯,assert() 會向標準輸出設備(一般是顯示器)打印一條錯誤信息,並調用 abort() 函數終止程序的執行。
2、如果expression的結果爲非 0(條件成立),那麼斷言成功,表明程序正確,assert() 不進行任何操作。
要打印的錯誤信息的格式和內容沒有統一的規定,這和標準庫的具體實現有關(也可以說和編譯器有關),但是錯誤信息至少應該包含以下幾個方面的信息:
1、斷言失敗的表達式,也即expression;
2、源文件名稱;
3、斷言失敗的代碼的行號;
大部分編譯器的格式如下所示:
Assertion failed: expression, file filename, line number
參數:
expression
要檢測的表達式。如果表達式的值爲 0,那麼斷言失敗,程序終止執行;如果表達式的值爲非 0,那麼斷言成功,assert() 不進行任何操作。
返回值:無返回值
assert() 的用法和機制:
在大部分編譯器下,assert() 是一個宏;在少數的編譯器下,assert() 就是一個函數。我們無需關心這些差異,只管把 assert() 當做函數使用即可。
assert() 的用法很簡單,我們只要傳入一個表達式,它會計算這個表達式的結果:如果表達式的結果爲“假”,assert() 會打印出斷言失敗的信息,並調用 abort() 函數終止程序的執行;如果表達式的結果爲“真”,assert() 就什麼也不做,程序繼續往後執行。
下面是一個具體的例子:
#include <stdio.h>
#include <assert.h>
int main(){
int m, n, result;
scanf("%d %d", &m, &n);
assert(n != 0); //寫作 assert(n) 更加簡潔
result = m / n;
printf("result = %d\n", result);
return 0;
}
本例用來計算兩個數相除的結果,由於被除數不能爲 0,所以我們加入了 assert() 來檢測錯誤。
如果輸入100 20,那麼 n 的值爲 20,n != 0這個條件成立,assert() 不進行任何操作,最終的輸出結果爲:
100 20
result = 5
如果輸入100 0,那麼 n 的值爲 0,n != 0這個條件不成立,assert() 就會報告錯誤,並終止程序執行,最終的結果如下所示:
100 0
Assertion failed: n != 0, file E:\\CDemo\main.c, line 6
NDEBUG 宏
如果查看 <assert.h> 頭文件的源碼,會發現 assert() 被定義爲下面的樣子:
#ifdef NDEBUG
#define assert(e) ((void)0)
#else
#define assert(e) \
((void) ((e) ? ((void)0) : __assert (#e, __FILE__, __LINE__)))
#endif
這意味着,一旦定義了NDEBUG宏,assert() 就無效了。
NDEBUG 是”No Debug“的意思,也即“非調試”。有的編譯器(例如 Visual Studio)在發佈(Release)模式下會定義 NDEBUG 宏,在調試(Debug)模式下不會定義定義這個宏;有的編譯器(例如 Xcode)在發佈模式和調試模式下都不會定義 NDEBUG 宏,這樣當我們以發佈模式編譯程序時,就必須自己在編譯參數中增加 NDEBUG 宏,或者在包含 <assert.h> 頭文件之前定義 NDEBUG 宏。
調試模式是程序員在測試代碼期間使用的編譯模式,發佈模式是將程序提供給用戶時使用的編譯模式。在發佈模式下,我們不應該再依賴 assert() 宏,因爲程序一旦出錯,assert() 會拋出一段用戶看不懂的提示信息,並毫無預警地終止程序執行,這樣會嚴重影響軟件的用戶體驗,所以在發佈模式下應該讓 assert() 失效。
修改上面的代碼,在包含 <assert.h> 之前定義 NDEBUG 宏:
#define NDEBUG
#include <stdio.h>
#include <assert.h>
int main(){
int m, n, result;
scanf("%d %d", &m, &n);
assert(n);
result = m / n;
printf("result = %d\n", result);
return 0;
}
當以發佈模式編譯這段代碼時,assert() 就會失效。如果希望繼續以調試模式編譯這段代碼,去掉 NDEBUG 宏即可。
在代碼中顯式地增加 NDEBUG 宏比較麻煩,因爲當以調試模式編譯代碼時還得再去掉它,更加科學的做法是在 IDE 的編譯參數中添加 NDEBUG 宏。不同的 IDE 添加宏的方式不同,這裏不再深入探討。
注意事項
(1) 使用 assert() 時,被檢測的表達式最好不要太複雜,以下面的代碼爲例:
assert( expression1 && expression2 && expression3);
當發生錯誤時,assert() 只會告訴我們expression1 && expression2 && expression3整個表達式爲不成立,但是這個大的表達式還包含了三個小的表達式,並且它們之間是&&運算,任何一個小表達式爲不成立都會導致整個表達式爲不成立,這樣我們就無法推斷到底是expression1有問題,還是expression2或者expression3有問題,從而給排錯帶來麻煩。
這裏我們應該遵循使用 assert() 的一個原則:每次斷言只能檢驗一個表達式。根據這個原則,上面的代碼應改爲:
assert(expression1);
assert(expression2);
assert(expression3);
如此,一旦程序出錯,我們就知道是哪個小的表達式斷言失敗了,從而快速定位到有問題的代碼。
(2) 使用 assert() 的另外一個注意事項是:不要用會改變環境的語句作爲斷言的表達式。請看下面的代碼:
#include <stdio.h>
#include <assert.h>
int main(){
int i = 0;
while(i <= 110){
assert(++i <= 100);
printf("我是第%d行\n",i);
}
return 0;
}
在 Debug 模式下運行,程序循環到第 101 次時,i 的值爲 100,++i <= 100不再成立,斷言失敗,程序終止運行。
在 Release 模式下運行,編譯參數中設置了 NDEBUG 宏(如果編譯器沒有默認設置,那麼需要你自己來設置),assert() 會失效,++i <= 100這個表達式也不起作用了,while() 無法終止,成爲一個死循環。
定義了 NDEBUG 宏後,assert(++i <= 100)會被替換爲((void)0)。
導致程序在 Debug 模式和 Release 模式下的運行結果大相徑庭的罪魁禍首就是++運算,我們本來希望它改變 i 的值,逐漸達到 while 的終止條件,但是在 Release 模式下它失效了。
修改這段不規範的代碼也很容易,將++i移出 assert() 即可:
#include <stdio.h>
#include <assert.h>
int main(){
int i = 0;
while(i <= 110){
++i;
assert(i <= 100);
printf("我是第%d行\n",i);
}
return 0;
}
如此,不管是 Debug 模式還是 Release 模式,每次循環 i 的值都會增加,逐漸達到 while 的終止條件時,結束循環。