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