「C語言筆記」assert怎麼用?

一、什麼是assert()?

編寫代碼時,我們總是會做出一些假設,斷言(assert)就是用於在代碼中捕捉這些假設,可以將斷言看作是異常處理的一種高級形式。

斷言表示為一些布爾表達式,程序員相信在程序中的某個特定點該表達式值為真。可以在任何時候啟用和禁用斷言驗證,因此可以在測試時啟用斷言,而在部署時禁用斷言。同樣,程序投入運行後,最終用戶在遇到問題時可以重新啟用斷言。

注意assert()是一個宏,而不是函數。

二、assert怎麼用?

1、assert所在的頭文件及原型

在MinGW工具中,assert()宏在存在於頭文件assert.h中,其關鍵內容如下:

#ifdef NDEBUG#define assert(x)((void)0)#else /* debugging enabled */_CRTIMP void __cdecl __MINGW_NOTHROW _assert (const char*, const char*, int) __MINGW_ATTRIB_NORETURN;#define assert(e)       ((e) ? (void)0 : _assert(#e, __FILE__, __LINE__))#endif/* NDEBUG */

assert()宏接受一個整形表達式參數。如果表達式的值為假,assert()宏就會調用_assert函數在標準錯誤流中打印一條錯誤信息,並調用abort()(abort()函數的原型在stdlib.h頭文件中)函數終止程序。

當我們認為已經排除了程序的bug時,就可以把宏定義#define NDEBUG寫在包含assert.h位置前面。

小知識:

  • __cdecl是C Declaration的縮寫(declaration,聲明),表示C語言默認的函數調用方法:所有參數從右到左依次入棧。
  • _CRTIMP是C run time implement的簡寫,C運行庫的實現的意思。作為用戶代碼,不應該使用這個東西。提示是使用dll的動態 C 運行時庫還是靜態連接的 C 運行庫的一個宏。
#ifndef _CRTIMP#ifdef _DLL#define _CRTIMP __declspec(dllimport)#else /* ndef _DLL */#define _CRTIMP#endif /* _DLL */#endif /* _CRTIMP */
  • __MINGW_NOTHROW與__MINGW_ATTRIB_NORETURN是異常處理相關標識

這幾個標識符在C語言標準庫文件中都有用得到,但是我們不需要關心,在我們用戶的角度來看,以上函數原型我們看成:void _assert(const char*, const char*, int);即可。

2、assert應用

assert主要用於類型檢查及單元測試中。

單元測試(unit testing),是指對軟件中的最小可測試單元進行檢查和驗證。對於單元測試中單元的含義,一般來說,要根據實際情況去判定其具體含義,如C語言中單元指一個函數。

(1)例子一:除法運算

/*編譯工具:mingw32  gcc6.3.0*/#include #include  
int main(void){int a, b, c;printf("請輸入b, c的值:");scanf("%d %d", &b, &c);a = b / c;printf("a = %d", a);return 0;}

此處,變量c作為分母是不能等於0,如果我們輸入2 0,結果是什麼呢?結果是程序會蹦:

「C語言筆記」assert怎麼用?

這個例子中只有幾行代碼,我們很快就可以找到程序蹦的原因就是變量c的值為0。但是,如果代碼量很大,我們還能這麼快的找到問題點嗎?

這時候,assert()就派上用場了,以上代碼中,我們可以在a = b / c;這句代碼之前加上assert(c);這句代碼用來判斷變量c的有效性。此時,再編譯運行,得到的結果為:

「C語言筆記」assert怎麼用?

可見,程序蹦的同時還會在標準錯誤流中打印一條錯誤信息:

Assertion failed:c, file hello.c, line 12

這條信息包含了一些對我們查找bug很有幫助的信息:問題出在變量c,在hello.c文件的第12行。這麼一來,我們就可以迅速的定位到問題點了。

這時候細心的朋友會發現,上邊我們對assert()的介紹中,有這麼一句說明:如果表達式的值為假,assert()宏就會調用_assert函數在標準錯誤流中打印一條錯誤信息,並調用abort()(abort()函數的原型在stdlib.h頭文件中)函數終止程序。

所以,針對我們這個例子,我們的assert()宏我們也可以用以下代碼來代替:

if (0 == c){puts("c的值不能為0,請重新輸入!");abort();}

這樣,也可以給我們起到提示的作用:

「C語言筆記」assert怎麼用?

但是,使用assert()至少有幾個好處:

1)能自動標識文件和出問題的行號。

2)無需要更改代碼就能開啟或關閉assert機制(開不開啟關係到程序大小的問題)。如果認為已經排除了程序的bug,就可以把下面的宏定義寫在包含assert.h的位置的前面:

#define NDEBUG

並重新編譯程序,這樣編輯器就會禁用工程文件中所有的assert()語句。如果程序又出現問題,可以移除這條#define指令(或把它註釋掉),然後重新編譯程序,這樣就可以重新啟用了assert()語句。

(2)例子二:STM32庫函數

我們來看我們比較熟悉的GPIO初始化函數:

「C語言筆記」assert怎麼用?

可見,該函數的實現中,有三條assert_param()這樣的語句,其作用就是對一些函數入口參數進行一些有效性檢查。其實assert_param()這就類似與我們C標準庫中的assert()。針對stm32f10x系列來說,其被定義在文件stm32f10x_conf.h中:

「C語言筆記」assert怎麼用?

這是一個例子,除了GPIO初始化函數之外,STM32固件庫函數中的其他函數都是會做這樣的參數檢查。

三、assert與if的比較?

assert()斷言功能好像用if也能實現,仔細一看這兩者還是有區別。下面看一下它們的區別:

先看一個例子,我們使用malloc函數定義一個存著堆空間中的變量,我們該怎麼定義及該怎麼做一些防禦處理呢?

首先,我們要知道,malloc函數如果分配成功內存則返回指向被分配內存的指針(此存儲區中的初始值不確定),否則返回空指針NULL。看如下代碼:

int* p = (int*)malloc(sizeof(int));assert(p);/* 錯誤示例 */

這麼寫會有問題嗎?

看似沒問題,但是問題很大!我們的assert()會在我們調試完畢之後禁用掉,這麼一來以上代碼就相當於只有下面這一句了:

int* p = (int*)malloc(sizeof(int));

此時,當我們的程序在跑的時候malloc申請不到內存空間了,也沒有做一些解決措施,可能就會產生致命錯誤。

我們應該把以上代碼改寫為:

int* p = (int*)malloc(sizeof(int));if (NULL == p) /*請使用if來判斷,這是有必要的*/{    /* 做一些處理 */} 


下面看一下assert與if做防錯處理的幾點用法區別:

1、assert語句用在debug版本的調試中;if(NULL!=p)是在release版本中檢驗指針的有效性;

2、assert一般用與檢查函數參數的合法性(有效性)而不是正確性,但是合法的程序並不見得是程序邏輯正確的程序,該用if做判斷處理的地方還是得做處理。

也就是assert在調試期間用來檢查一些不允許出現的情況是否有發生,一旦發生就表明我們的程序很可能有BUG,而if判斷的就是我們理所應當處理的各種情況,且這些情況如果發生並不代表程序發生BUG。


四、_Static_assert(C11標準)

assert()是在運行時進行檢查的,如果一份工程很大,編譯起來需要很長時間,一些情況在運行時檢查,效率就比較低了。

這時候_Static_assert()就派上用場了,這是C11標準中的一個特性,_Static_assert()在編譯時進行檢查,如果編譯時檢測到代碼裡的一些異常情況,就會導致程序無法通過編譯。下面來看一個例子:

/*編譯環境:mingw32  gcc6.3.0編譯命令:gcc -std=c11 hello.c -o hello.exe*/#include #include   /*CHAR_BIT是limits.h中的一個宏*/_Static_assert(CHAR_BIT == 16, "16-bit char falsely assumed");int main(void){printf("歡迎關注嵌入式大雜燴!查看更多筆記\\n");return 0;}

_Static_assert接受兩個參數,第一個參數是整型常量表達式,第二個參數是一個字符串。如果第一個表達式為0,編譯時就會輸出第二個參數的字符串,而且編譯不通過。

該程序編譯結果如下:

「C語言筆記」assert怎麼用?

可見,編譯報錯了,並且打印提示了我們的問題所在點,打印出了我們_Static_assert第二個參數的字符串,這樣我們就可以很快地定位到導致編譯錯誤的問題了。

以上就是關於assert()斷言宏的一些總結筆記,如有錯誤歡迎指出!


分享到:


相關文章: