assert() 斷言函數,用於在調試過程中捕捉程序錯誤

轉載: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 的終止條件時,結束循環。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章