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