防御式編程的重點(diǎn)就是需要防御一些程序未曾預(yù)料的錯(cuò)誤,這是一種提高軟件質(zhì)量的輔助性方法,斷言assert就用于防御式編程,編寫代碼時(shí),我們總是會(huì)做出一些假設(shè),斷言就是用于在代碼中捕捉這些假設(shè)。 使用斷言是為了驗(yàn)證預(yù)期的結(jié)果——當(dāng)程序執(zhí)行到斷言的位置時(shí),對(duì)應(yīng)的斷言應(yīng)該為真; 若斷言不為真時(shí),程序會(huì)終止執(zhí)行,并給出錯(cuò)誤信息。 可以在任何時(shí)候啟用和禁用斷言驗(yàn)證,因此可以在程序調(diào)試時(shí)啟用斷言而在程序發(fā)布時(shí)禁用斷言。 同樣,程序投入運(yùn)行后,最終用戶在遇到問題時(shí)可以重新啟用斷言。
1、原型函數(shù)
在大部分編譯器下,assert() 是一個(gè)宏; 在少數(shù)的編譯器下,assert() 就是一個(gè)函數(shù)。 我們不需要關(guān)心這些差異,可以只把 assert()當(dāng)作函數(shù)使用即可。 即:
void assert(int expression)
在程序運(yùn)行時(shí)它會(huì)計(jì)算括號(hào)內(nèi)的表達(dá)式,如果 expression為非0說明其值為真,assert()不執(zhí)行任何動(dòng)作,程序繼續(xù)執(zhí)行后面的語句; 如果 expression為0說明其值為假,assert()將會(huì)報(bào)告錯(cuò)誤,并終止程序的執(zhí)行,值得了解的是,程序終止是調(diào)用abort()函數(shù),這個(gè)函數(shù)功能就是終止程序執(zhí)行,直接從調(diào)用的地方跳出,abort()函數(shù)也是標(biāo)準(zhǔn)庫函數(shù),在
2、詳細(xì)釋義
assert() 在c標(biāo)準(zhǔn)庫中的
#ifdef NDEBUG
#define assert(e) ((void)0)
#else
#define assert(e)
((void) ((e) ? ((void)0) : __assert (#e, __FILE__, __LINE__)))
#endif
可以看到在定義了NDEBUG時(shí),assert()無效,只有在未定義NDEBUG時(shí),assert()才實(shí)現(xiàn)具體的函數(shù)功能。 NDEBUG是“No Debug”的意思,也即“非調(diào)試”。 程序一般分為Debug版本和Release版本,Debug版本是程序員在測試代碼期間使用的編譯版本,Release版本是將程序提供給用戶時(shí)使用的發(fā)布版本,一般來說斷言assert()是僅在Debug版本起作用的宏。 在發(fā)布版本時(shí),我們不應(yīng)該再依賴assert()宏,因?yàn)槌绦蛞坏┏鲥e(cuò),assert()會(huì)拋出一段用戶看不懂的提示信息,并毫無預(yù)警地終止程序執(zhí)行,這樣會(huì)嚴(yán)重影響軟件的用戶體驗(yàn),所以在發(fā)布模式下應(yīng)該讓assert()失效,另外在程序中頻繁的調(diào)用assert()會(huì)影響程序的性能,增加額外的開銷。 因此可以在
#define NDEBUG //定義NDEBUG
#ifdef NDEBUG
#define assert(e) ((void)0)
#else
#define assert(e)
((void) ((e) ? ((void)0) : __assert (#e, __FILE__, __LINE__)))
#endif
- 定義NDBUG時(shí):
當(dāng)定義了NDEBUG之后,assert()執(zhí)行的具體函數(shù)就變成了 ((void)0),這表示啥也不干了,宏里面這樣用的目的是防止該宏被用作右值,因?yàn)関oid類型不能用作右值。 所以當(dāng)在頭文件中定義了NDEBUG之后,assert()的檢測功能就自動(dòng)失效了。
- 未定義NDBUG時(shí):
可以看到assert()執(zhí)行實(shí)際上是通過三目運(yùn)算符來判斷表達(dá)式e的真假,執(zhí)行相應(yīng)的處理。 當(dāng)表達(dá)式e為真時(shí),執(zhí)行(void)0,即什么也不執(zhí)行,程序繼續(xù)運(yùn)行; 當(dāng)表達(dá)式e為假時(shí),那么它會(huì)打印出來assert的內(nèi)容、當(dāng)前的文件名、當(dāng)前行號(hào),接著終止程序執(zhí)行。
3、用法舉例
在未定義NDBUG時(shí),assert()功能生效的情況下,來看一個(gè)簡單的assert()使用的例子:
#include
#include
void main()
{
int i = 8;
assert(i > 0);
printf("i = %d\\n", i);
i = -8;
assert(i > 0);
printf("i = %d\\n", i);
}
可以看出在程序中使用assert(i > 0)來判斷; 當(dāng) i > 0 時(shí),assert的判斷表達(dá)式為真,assert不生效; 當(dāng) i < 0 時(shí),assert的判斷表達(dá)式為假,assert生效。
在程序第5行 i = 8,執(zhí)行完assert后,程序?qū)?zhí)行后續(xù)的printf打印出 i 的值; 而在第8行 i = -8,執(zhí)行完assert后,程序?qū)⒔K止,不會(huì)執(zhí)行后續(xù)的printf。
4、使用注意事項(xiàng)
使用assert的核心原則是:用于處理絕不應(yīng)該發(fā)生的情況,這就是為什么應(yīng)該在程序Debug版本中使用,這是為了將主觀上不應(yīng)該發(fā)生的錯(cuò)誤在Debug版本中就應(yīng)該解決掉,從而在程序Release版本時(shí)不會(huì)產(chǎn)生這種不應(yīng)該發(fā)生的類型的錯(cuò)誤。
- 和if的區(qū)別
assert用函數(shù)來判斷是否滿足表達(dá)式條件后終止程序,在Debug版本中用assert來判斷程序的合法性,定位不允許發(fā)生的錯(cuò)誤,那么什么是不應(yīng)該發(fā)生的錯(cuò)誤,例如像下面這種除0操作,主觀上就不應(yīng)該發(fā)生,就是就要在Debug版本中檢查排除掉這種錯(cuò)誤,以免影響后續(xù)程序的執(zhí)行。
#include
#include
void fun(int a, int b)
{
assert(b != 0);
int i = a / b;
}
if是一個(gè)關(guān)鍵字,一般用于根據(jù)條件來判斷邏輯的正確性,即是否根據(jù)條件對(duì)應(yīng)執(zhí)行,Debug和Release版本中都可以使用,例如下面用if的時(shí)候,就允許這些判斷條件是正常發(fā)生的,是合理的,需要根據(jù)發(fā)生的條件執(zhí)行對(duì)應(yīng)的邏輯,程序可以往下執(zhí)行。
#include
#include
void fun(int a, int b)
{
if(a > 0)
...
else if(a < 0)
...
else
...
}
因此在使用前,可以先判斷下,如果邏輯不允許發(fā)生,那么就使用assert在Debug階段將問題解決掉; 如果邏輯允許的,那么就使用if,當(dāng)然也可以用if判斷后進(jìn)行條件的return操作,來杜絕不允許邏輯,本質(zhì)是防止錯(cuò)誤的邏輯影響后續(xù)程序的執(zhí)行。 例如上述的用來判斷除0操作的例子也可以用if:
#include
#include
void fun(int a, int b)
{
if(0 == b)
return;
int i = a / b;
}
- 用于判斷函數(shù)的入?yún)?/strong>
一般assert可以用于判斷函數(shù)入?yún)⒌暮戏ㄐ裕热缛雲(yún)⒅凳欠穹希羔樖欠駷榭眨?/p>
#include
#include
void fun1(int a)
{
assert(a > 0);
...
}
void fun2(int *p)
{
assert(p != NULL);
...
}
- 不要使用影響正常邏輯的判斷條件語句
assert的判斷條件語句一定是確定的,在Debug版本中使用的排除掉錯(cuò)誤的條件邏輯,不要影響到Release版本時(shí)的正常邏輯。 例如下面的例子,在Debug版本時(shí),i++到>=100時(shí),assert生效,程序終止; 但是到了Release版本,由于要增加NDEBUG宏,assert()無效。 assert(i++ < 100)就變成了空操作(void)0;由于沒有i++語句執(zhí)行,那么while成了死循環(huán)。
#include
#include
void main()
{
int i = 0;
while(i <= 110)
{
assert(i++ < 100);
printf("i = %d\\n",i);
}
}
- 不要用多個(gè)判斷條件語句
一般一個(gè)assert只用一個(gè)判斷語句來實(shí)現(xiàn),如果在一個(gè)assert中使用多條判斷語句,當(dāng)錯(cuò)誤發(fā)生時(shí),會(huì)不知道是哪個(gè)條件語句出現(xiàn)錯(cuò)誤,錯(cuò)誤表現(xiàn)的就不直觀。
#include
#include
void fun1(int a, int b) //錯(cuò)誤使用
{
assert(a > 0 && b > 5);
...
}
void fun2(int a, int b) //正確使用
{
assert(a > 0);
assert(b > 5);
...
}
-
編程
+關(guān)注
關(guān)注
88文章
3592瀏覽量
93596 -
函數(shù)
+關(guān)注
關(guān)注
3文章
4306瀏覽量
62431 -
編譯器
+關(guān)注
關(guān)注
1文章
1618瀏覽量
49051 -
void
+關(guān)注
關(guān)注
0文章
23瀏覽量
9856 -
ASSERT
+關(guān)注
關(guān)注
0文章
17瀏覽量
7228
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論