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