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

2020年02月03日12:58:08 科技 1825

一、什麼是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 <stdio.h>#include <assert.h>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 <stdio.h>#include <limits.h>  /*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()斷言宏的一些總結筆記,如有錯誤歡迎指出!

科技分類資訊推薦

探秘vivo影像之美,與X系列技術溝通會共同見證卓越藍圖 - 天天要聞

探秘vivo影像之美,與X系列技術溝通會共同見證卓越藍圖

前段時間的vivo X系列新品發布讓我們記憶深刻,而剛剛結束結束的「影像新藍圖X系列技術溝通會」也十分精彩,這次溝通會不僅告訴了大家vivo在影像領域的最新成果,也讓粉絲們對vivo影像背後的故事有更多了解,在vivo影像的發展歷程中可以分成兩方面,一方面,vivo通過自主研發,不斷提升自身的影像技術實力,另一方面,vivo...
華為再次亮劍,鴻蒙系統亮點多多 - 天天要聞

華為再次亮劍,鴻蒙系統亮點多多

5月15日,華為夏季全場景新品發布會簡直是一場視覺與科技的盛宴,看得我熱血沸騰!不得不說,華為這次真的是大招頻出,新品多到讓人眼花繚亂!先說說那個華為MatePad 11.5 」 S吧,一亮相就讓人眼前一亮!它的設計簡約而不失時尚,屏幕清晰得讓人彷彿置身其中。
【新機】升級了個寂寞?藍廠新機發布 - 天天要聞

【新機】升級了個寂寞?藍廠新機發布

昨天晚上藍廠發布了新機iQOO Neo9s Pro,機子採用了6.78英寸1.5K 8T LTPO直面屏,支持144Hz刷新率。搭載天璣9300+,前置1600萬像素,後置IMX920主攝+5000萬超廣角。電池容量5160mAh,支持120W快充。新增白色版本,用上了白色玻璃後蓋,顏值確實提升了不少。不過還是塑料中框+短焦指紋。12+256GB 2699元
AI Agent在哪些行業領域會產生影響? - 天天要聞

AI Agent在哪些行業領域會產生影響?

在如今飛速前進的科技浪潮中,人工智慧(AI)技術的創新正以前所未有的速度重塑我們的生活和工作的每一個角落。在這一浪潮中,Agent AI智能體以其卓越的能力脫穎而出,它通過模擬人類智能行為,能夠勝任一系列複雜任務,並在多個領域展現出了其無法替代的價值。
續航王者vivo Y200系列掀起購機狂潮,618購機可享多重福利優惠 - 天天要聞

續航王者vivo Y200系列掀起購機狂潮,618購機可享多重福利優惠

智能手機的選擇五花八門,但有一條賽道被vivo獨佔,那就是長續航。剛剛發布的vivo Y200系列憑藉其超長的續航能力和輕薄的設計,一經推出便受到廣大用戶的關注和好評,徹底改變了行業續航的基準線。如今Y200系列新品已正式開啟預售及首銷,期間購機可享多重福利優惠。
【現場】人工關節集采續約開標:企業代表「不緊張」,多家報價壓線進 - 天天要聞

【現場】人工關節集采續約開標:企業代表「不緊張」,多家報價壓線進

界面新聞記者 | 唐卓雅界面新聞編輯 | 謝欣5月21日,國家組織人工關節集中帶量採購協議期滿接續採購(下稱「人工關節續采」)在天津東麗湖恆大酒店國際會議中心開標。此次續標的產品範圍與2021年的首輪集采相同,包括髖關節(陶瓷-陶瓷類、陶瓷-聚乙烯類和合金-聚乙烯類)和膝關節,採購周期由兩年延長為三年,採購需求量為...
產品經理談一談:水果店應該怎麼開 - 天天要聞

產品經理談一談:水果店應該怎麼開

這幾年,不少人都計劃著整個副業,或者以後被畢業了去做什麼。有準備擺攤的,有準備開咖啡店開書店的,也有人準備開水果店的。只是工作這麼久了,相關的經驗能否平移過去?如果用產品思維來看,我們做一個店面,需要怎麼做呢?