眾所周知,我們在實際開發(fā)C程序的時候,往往是編碼容易——調(diào)試?yán)щy,修改容易——排查困難。我們在開發(fā)過程中,debug占據(jù)了我們很大一部分的時間,而正確地使用各種編碼手段,可以有效地提升排查問題代碼的效率。筆者從自己的實踐經(jīng)驗出發(fā),給大家分享一個用于編碼/調(diào)試階段高效發(fā)現(xiàn)問題代碼的利器,這就是大名鼎鼎的**assert**。通過閱讀本文,你將了解到以下內(nèi)容:
- 什么是assert?
- assert有什么用?
- assert怎么使用?
- assert的常規(guī)操作有哪些?
什么是assert?
? assert它的中文含義是“斷言”,它被包含在中,往往給使用者呈現(xiàn)的形式為: assert() 。因此,很多開發(fā)者認(rèn)為它就是一個函數(shù),可能它的原型就是void assert(int expression); 但研究過assert.h的,一定會發(fā)現(xiàn),其實并不是。
? assert的真身,其實是一個宏定義,只不過是一個帶參數(shù)輸入的宏定義,與我們之前一篇八卦Linux內(nèi)核設(shè)計的max宏定義 (【Linux內(nèi)核】從小小的宏定義窺探Linux內(nèi)核的精妙設(shè)計)類似的。廬山真面目如下所示:
#define assert(e) ((e) ? (void)0 : _assert(#e, __FILE__, __LINE__))
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-zc6EAp8U-1661923571346)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]
? 從它的定義,我們可以很清晰的知道,真正起到打印作用的是_assert,而它才是真正的一個函數(shù)。原型為:
void _assert(const char *e, const char *file, int line);
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-dax1ldZf-1661923571352)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]
assert有什么用?
? 本文的主題是利用assert高效排查問題代碼,自然assert的用途就是排查代碼;但是,具體它的功能是怎么體現(xiàn)呢?假設(shè)有如下代碼,一個測試函數(shù)的實現(xiàn)片段:
int test_function(int a, int *b)
{
assert(a > 1); /* 斷言:入?yún)的值一定大于1 */
assert(b); /* 斷言: 入?yún)指針一定不是NULL */
/* Do other things here ... */
}
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ejym7Jij-1661923571353)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]
? 如代碼所示,有一個測試函數(shù)test_function,接收2個入?yún)ⅲ粋€是int型的a變量,一個int *類型的b指針;在函數(shù)的開始,我們就用assert分別對a和b做了斷言,確保它們有正確的輸入。假設(shè)我們有如下的函數(shù)調(diào)用的測試代碼:
{
int a = 7;
int *b = &a;
test_function(a, b);
/* Do other things here ... */
}
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-E4QQ143R-1661923571355)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]
? 很明顯,當(dāng)如上代碼調(diào)用test_fucntion時,內(nèi)部的兩個assert判斷均為【真】,那么什么事情也不會發(fā)生,assert就像一個優(yōu)雅的淑女,靜靜地站在那里看著你。
? 當(dāng)我們的測試代碼做如下調(diào)整:
{
int a = 0;
int *b = &a;
test_function(a, b);
/* Do other things here ... */
}
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-qPbDKDBb-1661923571357)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]
? 很明顯,test_function的第一個assert語句不為【真】,那么它就像山洪一樣要爆發(fā)了,終止程序運行的同時,會輸出類似的錯誤提示: Assertion failed: a > 1,file xxx.c, line 128,這段錯誤提示,不僅告訴了我們哪個條件判斷出錯了,并且還告訴了我們出錯的位置在哪個文件的哪一行,這是多么智能啊!由此可見,它真正的威力在于【代碼出錯】時,即當(dāng)代碼沒有按照我們的斷言進(jìn)行時,我們就應(yīng)該停下來,排查下為何會有錯誤的參數(shù)輸入,這樣我們就可以將bug在出現(xiàn)苗頭的時候就把它消滅掉。
assert怎么使用?
? 其實,上面的示例代碼已經(jīng)展示了如何使用assert,但是我們需要補充的是,一般在使用assert斷言語句的時候,需要在對應(yīng)的.c文件加上對assert.h的引用,否則編譯會報錯誤。
? assert這么智能的利器是非常有利于我們寫出高質(zhì)量不易出錯的代碼的,通常富有經(jīng)驗的程序員都會很擅長使用assert語句,把assert打在恰當(dāng)?shù)恼Z句中,可以最大限度地提升我們的代碼質(zhì)量。但是,很多開發(fā)者開始有疑惑了,要是每條語句,每個判斷都加上assert,那么就算全部assert的情況都是【真】,也夠CPU忙一會了,這樣似乎有些浪費CPU的計算能力,以追求高效的C語言編程,可容不下這樣的事情發(fā)生。那,這可怎么辦呢?
? 為避免以上情況的發(fā)生,我們作為assert的使用者,一般只需要在開發(fā)調(diào)試階段才使用assert,而在正式發(fā)布的版本是需要去掉assert的。這樣疑惑就更大了,發(fā)布版本一條條刪掉assert調(diào)用,萬一刪錯了代碼呢?設(shè)計者總是聰明的,他們也早就想到了這一點,這不他們也提供了解決方案。開頭的時候,我們介紹了assert是一個宏,但并沒有完全展示它的全貌?,F(xiàn)在開始展示它的真容:
#ifdef NDEBUG
#define assert(e) (void)0
#else
#define assert(e) ((e) ? (void)0 : _assert(#e, __FILE__, __LINE__))
#endif
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-69cPczpO-1661923571359)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]
? 聰明的你,一定也發(fā)現(xiàn)了,我們只需要在.c文件#include 之前,加上一句#define NDEBUG 1就可以把相應(yīng).c中的assert(e)全部變成((void)0);而((void)0)本身是個無效調(diào)用代碼,在實際的編譯過程中是會被優(yōu)化掉的,這樣我們僅增加對NDEBUG(NO DEBUG的意思)的宏定義,就可以把全部的assert給摒棄了,是不是很智能呢?
assert的常規(guī)操作有哪些?
? 正如上面所述,assert這么智能,但是我們也不能濫用,只需要在恰當(dāng)?shù)奈恢米鳛樘囟ǖ呐袛啵煌ǔ碚f,我們有以下一些情景可以考慮使用assert語句:
- 函數(shù)的入?yún)⑴袛?,對錯誤的入?yún)⒓皶r處理
- 對重點調(diào)用的系統(tǒng)函數(shù)的返回結(jié)果做判斷,使用assert保證系統(tǒng)調(diào)用的結(jié)果是正確的,避免外部使用不正確的系統(tǒng)調(diào)用而出現(xiàn)錯上加錯的情況;
- switch語句中,如果不允許出現(xiàn)default的情況,可以考慮在default分支中加入assert(0);
- 執(zhí)行計算時,做計算的輸入或計算結(jié)果的輸出等做下判斷,比如除數(shù)不能為0,比如一個百分比值不能超過100%等等。
? 綜述,assert是把雙刃劍,出錯時它能很優(yōu)秀地暴露問題代碼,非常有利于我們排查代碼,從而以最快的速度找到問題并解決問題;同時,它的頻繁調(diào)用,一定程度上加上了CPU的處理,做一些無畏的判斷,“簡直就是在浪費生命”。所以,在實際開發(fā)過程中,我們務(wù)必要嚴(yán)謹(jǐn)細(xì)致地使用assert,讓它更好地為我們服務(wù)。
? 只有不自負(fù)且思維嚴(yán)謹(jǐn)?shù)娜瞬拍苁褂煤胊ssert,我們只有做到了不自負(fù),不對自己的代碼打100%的包票,相信是代碼總會有出錯的時候,才會逐步養(yǎng)成思維嚴(yán)謹(jǐn)?shù)牧?xí)慣,反而對自己的代碼質(zhì)量有更大的提升。
? 本文對assert的介紹和使用做了一番總結(jié),文中難免有紕漏之處,還望讀者誠心指正,感謝。
-
C語言
+關(guān)注
關(guān)注
180文章
7605瀏覽量
136959 -
C程序
+關(guān)注
關(guān)注
4文章
254瀏覽量
36041 -
ASSERT
+關(guān)注
關(guān)注
0文章
17瀏覽量
7252
發(fā)布評論請先 登錄
相關(guān)推薦
評論