C語言 巨集 assert()

2021-10-20 20:20:51 字數 4382 閱讀 8626

編寫**時,我們總是會做出一些假設,斷言(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);即可。

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

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

(1)例子一:除法運算

左右滑動檢視全部**》

/*

編譯環境:mingw32 gcc6.3.0

*/#include

#include

intmain

(void

)

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

可見,程式蹦的同時還會在標準錯誤流中列印一條錯誤資訊:

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

這條資訊包含了一些對我們查詢bug很有幫助的資訊:問題出在變數c,在hello.c檔案的第12行。這麼一來,我們就可以迅速的定位到問題點了。

細心的朋友會發現,上邊我們對assert()的介紹中,有這麼一句說明:如果表示式的值為假,assert()巨集就會呼叫_assert函式在標準錯誤流中列印一條錯誤資訊,並呼叫abort()(abort()函式的原型在stdlib.h標頭檔案中)函式終止程式。

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

if(0

== c)

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

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

1)能自動標識檔案和出問題的行號。

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

#define ndebug

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

(2)例子二:stm32庫函式

我們來看我們比較熟悉的gpio初始化函式:

可見,該函式的實現中,有三條assert_param()這樣的語句,其作用就是對一些函式入口引數進行一些有效性檢查。

其實assert_param()這就類似與我們c標準庫中的assert()。針對stm32f10x系列來說,其被定義在檔案stm32f10x_conf.h中:

除了gpio初始化函式之外,stm32韌體庫函式中的其他函式都是會做這樣的引數檢查。

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。

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");

intmain

(void

)

_static_assert接受兩個引數,第乙個引數是整型常量表示式,第二個引數是乙個字串。如果第乙個表示式為0,編譯時就會輸出第二個引數的字串,而且編譯不通過。

該程式編譯結果如下:

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

C語言ASSERT巨集

一 assert函式的目的 斷言可以對在系統中隱藏很深,用其它手段極難發現的問題進行定位,並且輸出資訊很詳細,可以很好地解決大多數c語言編譯器錯誤資訊輸出不詳細的問題 二 assert巨集的原型,assert在 定義的 c語言中,assert斷言的原型定義在檔案中。assert是乙個巨集定義,並不是...

C語言 assert巨集的用法

assert巨集的原型定義在中,其作用是如果它的條件返回錯誤,則終止程式執行,原型定義 include void assert int expression assert的作用是現計算表示式 expression 如果其值為假 即為0 那麼它先向stderr列印一條出錯資訊,然後通過呼叫 abort...

C語言的assert巨集的用法

assert巨集的原型定義在中,其作用是如果它的條件返回錯誤,則終止程式執行,原型定義 include void assert int expression assert的作用是現計算表示式 expression 如果其值為假 即為0 那麼它先向stderr列印一條出錯資訊,然後通過呼叫 abort...