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

本文來自博客園,作者:Sharemaker,轉載請注明原文鏈接:https://www.cnblogs.com/Sharemaker/p/16954224.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/539278.html
標籤:嵌入式
