「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()斷言宏的一些總結筆記,如有錯誤歡迎指出!

科技分類資訊推薦

Neo7 Turbo對友商下狠手 一加對比真我顯單薄 - 天天要聞

Neo7 Turbo對友商下狠手 一加對比真我顯單薄

#雙11國貨出行好物#對比同門品牌一加推出的 Ace 5 競速版,真我Neo7 Turbo 直接用實力完成了一場「自家人打自家人」的無情對決,具體怎麼說,不妨跟著我們一起了解下這款新機吧!
「猴臉識別2.0」上線——不但「辨」猴臉,更「懂」猴子心 - 天天要聞

「猴臉識別2.0」上線——不但「辨」猴臉,更「懂」猴子心

人民網西安5月29日電 (記者李志強)「這隻猴子叫『洋蔥』,可信度85%。」打開手機小程序,對準電腦屏幕上的一張猴子照片進行識別,不到5秒鐘,結果就出來了。隨後的演示圖像中,「洋蔥」的整個身體被標記上了若干個點位,通過分析這些點位的運動規律,就能深度解析它的行為意圖。 5月29日,記者從西北大學獲悉,此前研發出「...
AI眼鏡將取代手機?對話因眼鏡提詞器走紅的杭州公司聯創 - 天天要聞

AI眼鏡將取代手機?對話因眼鏡提詞器走紅的杭州公司聯創

戴著一副透明的眼鏡,你可以在路上導航、脫稿在台上演講,同時還能啟動聲紋支付、語音喚起AI點餐和打車,這些充滿科幻感的場景已經被智能眼鏡頭部廠商Rokid(杭州靈伴科技有限公司)實現了。Rokid即將在6月上線新一代智能眼鏡Rokid Glasses。伴隨AI關鍵技術的發展,尤其是大模型的快速推進,AI眼鏡有望成為下一代人機交互的...
黃仁勛:無論有沒有美國晶元,中國AI發展都會繼續 - 天天要聞

黃仁勛:無論有沒有美國晶元,中國AI發展都會繼續

5月29日,英偉達發布了第一財季財報。隨後,英偉達CEO黃仁勛、CFO克萊特·克羅斯出席電話會議。會上,談到美國晶元出口管制對英偉達的影響,黃仁勛表示,出口管制對英偉達造成了巨大的損失,但這並不能阻礙中國人工智慧的發展,反而會刺激中國的創新,增強中國在海外的實力,削弱美國的地位。他認為,AI競賽不僅僅是關於芯...
川南大數據中心即將投運 政府搭台促成企業間合作 - 天天要聞

川南大數據中心即將投運 政府搭台促成企業間合作

封面新聞記者 黃曉慶5月28日下午,內江市舉行「雲上甜城·算網為先」川南大數據中心助力內江發展新質生產力推介會。記者從會上獲悉,即將投入運營的川南大數據中心總投資4億元、設計機架500個、算力超過10000VCPU、存儲能力50PB,是四川
當手機叫車融入大眾日常,計程車為何開始到處排隊趴活? - 天天要聞

當手機叫車融入大眾日常,計程車為何開始到處排隊趴活?

在商場邊,打計程車似乎越來越好打了,而且直接上車就能走。筆者近日在廣州深圳等地,感受到了街頭打出租的變化。一個工作日的晚7點半左右,廣州北京路天河城門口,停著幾輛計程車,車頂的「空車」燈在暮色中格外醒目。司機阿強搖下車窗,望著匆匆而過的行人:「三年前這裡根本不擔心沒活,現在蹲半小時能拉一單就算快了。...
數量銳減30萬台,ATM機借勢AI捲土重來 - 天天要聞

數量銳減30萬台,ATM機借勢AI捲土重來

在移動支付全國普及的今天,當你路過街角的ATM機時,是否曾疑惑:這些金屬櫃體是否終將淪為數字時代的遺迹?最新數據揭示了一個反直覺的現實,2024年全國ATM保有量降至80.27萬台,較峰值縮減近30萬台,但每台設備的價值正經歷前所未有的重構。這場靜默的變革背後,是一場關乎金融普惠與科技革新的生死蛻變。ATM行業的冰火兩...
大模型牽手手機銀行,開啟無菜單時代|銀行APP測評 - 天天要聞

大模型牽手手機銀行,開啟無菜單時代|銀行APP測評

2025年上半年,銀行依舊以將大模型裝進手機銀行作為要務,六大行率先對其大模型進行了升級優化,例如工商銀行在同業中率先發布企業級千億金融大模型技術體系 「工銀智涌」,累計調用量超 10 億次,賦能 20 多類業務、200 余個場景,較 2024 年在應用場景數量和調用量上有顯著提升,其大模型技術能力在同業中率先輸出至中小...