來源:https://blog.csdn.net/houxiaoni01/article
對于斷言,相信大家都不陌生,大多數(shù)編程語言也都有斷言這一特性。簡單地講,斷言就是對某種假設條件進行檢查。
在 C 語言中,斷言被定義為宏的形式(assert(expression)
),而不是函數(shù),其原型定義在 <assert.h>
文件中。
其中,assert 將通過檢查表達式 expression 的值來決定是否需要終止執(zhí)行程序。也就是說,如果表達式 expression 的值為假(即為 0),那么它將首先向標準錯誤流 stderr 打印一條出錯信息,然后再通過調(diào)用 abort 函數(shù)終止程序運行;否則,assert 無任何作用。
原型定義:
#include <assert.h>
void assert( int expression );
默認情況下,assert 宏只有在 Debug 版本(內(nèi)部調(diào)試版本)中才能夠起作用,而在 Release 版本(發(fā)行版本)中將被忽略。當然,也可以通過定義宏或設置編譯器參數(shù)等形式來在任何時候啟用或者禁用斷言檢查(不建議這么做)。同樣,在程序投入運行后,最終用戶在遇到問題時也可以重新起用斷言。
這樣可以快速發(fā)現(xiàn)并定位軟件問題,同時對系統(tǒng)錯誤進行自動報警。對于在系統(tǒng)中隱藏很深,用其他手段極難發(fā)現(xiàn)的問題也可以通過斷言進行定位,從而縮短軟件問題定位時間,提高系統(tǒng)的可測性。
在討論如何使用斷言之前,先來看下面一段示例代碼:
void *Memcpy(void *dest, const void *src, size_t len)
{
char *tmp_dest = (char *)dest;
char *tmp_src = (char *)src;
while(len --)
*tmp_dest ++ = *tmp_src ++;
return dest;
}
對于上面的 Memcpy 函數(shù),毋庸置疑,它能夠通過編譯程序的檢查成功編譯。從表面上看,該函數(shù)并不存在其他任何問題,并且代碼也非常干凈。
但遺憾的是,在調(diào)用該函數(shù)時,如果不小心為 dest 與 src 參數(shù)錯誤地傳入了 NULL 指針,那么問題就嚴重了。輕者在交付之前這個潛在的錯誤導致程序癱瘓,從而暴露出來。否則,如果將該程序打包發(fā)布出去,那么所造成的后果是無法估計的。
由此可見,不能夠簡單地認為“只要通過編譯程序成功編譯的就都是安全的程序”。當然,編譯程序也很難檢查出類似的潛在錯誤(如所傳遞的參數(shù)是否有效、潛在的算法錯誤等)。
面對這類問題,一般首先想到的應該是使用最簡單的if語句進行判斷檢查,如下面的示例代碼所示:
void *Memcpy(void *dest, const void *src, size_t len)
{
if(dest == NULL)
{
fprintf(stderr,'dest is NULL\n');
abort();
}
if(src == NULL)
{
fprintf(stderr,'src is NULL\n');
abort();
}
char *tmp_dest = (char *)dest;
char *tmp_src = (char *)src;
while(len --)
*tmp_dest ++ = *tmp_src ++;
return dest;
}
現(xiàn)在,通過“if(dest == NULL)與if(src == NULL)
”判斷語句,只要在調(diào)用該函數(shù)的時候為 dest 與 src 參數(shù)錯誤地傳入了NULL指針,這個函數(shù)就會檢查出來并做出相應的處理,即先向標準錯誤流 stderr 打印一條出錯信息,然后再調(diào)用 abort 函數(shù)終止程序運行。
從表面看來,上面的解決方案應該堪稱完美。但是,隨著函數(shù)參數(shù)或需要檢查的表達式不斷增多,這種檢查測試代碼將占據(jù)整個函數(shù)的大部分(這一點從上面的 Memcpy 函數(shù)中就不難看出)。這樣代碼看起來非常不簡潔,甚至可以說很“糟糕”,而且也降低了函數(shù)的執(zhí)行效率。
面對上面的問題,或許可以利用 C 的預處理程序有條件地包含或不包含相應的檢查部分進行解決,如下面的代碼所示:
void *MemCopy(void *dest, const void *src, size_t len)
{
#ifdef DEBUG
if(dest == NULL)
{
fprintf(stderr,'dest is NULL\n');
abort();
}
if(src == NULL)
{
fprintf(stderr,'src is NULL\n');
abort();
}
#endif
char *tmp_dest = (char *)dest;
char *tmp_src = (char *)src;
while(len --)
*tmp_dest ++ = *tmp_src ++;
return dest;
}
這樣,通過條件編譯“#ifdef DEBUG
”來同時維護同一程序的兩個版本(內(nèi)部調(diào)試版本與發(fā)行版本),即在程序編寫過程中,編譯其內(nèi)部調(diào)試版本,利用其提供的測試檢查代碼為程序自動查錯。而在程序編完之后,再編譯成發(fā)行版本。
上面的解決方案盡管通過條件編譯“#ifdef DEBUG”能產(chǎn)生很好的結(jié)果,也完全符合我們的程序設計要求,但是仔細觀察會發(fā)現(xiàn),這樣的測試檢查代碼顯得并不那么友好,當一個函數(shù)里這種條件編譯語句很多時,代碼會顯得有些浮腫,甚至有些糟糕。
因此,對于上面的這種情況,多數(shù)程序員都會選擇將所有的調(diào)試代碼隱藏在斷言 assert 宏中。其實,assert 宏也只不過是使用條件編譯“#ifdef”對部分代碼進行替換,利用 assert 宏,將會使代碼變得更加簡潔,如下面的示例代碼所示:
void *MemCopy(void *dest, const void *src, size_t len)
{
assert(dest != NULL && src !=NULL);
char *tmp_dest = (char *)dest;
char *tmp_src = (char *)src;
while(len --)
*tmp_dest ++ = *tmp_src ++;
return dest;
}
現(xiàn)在,通過“assert(dest !=NULL && src !=NULL)
”語句既完成程序的測試檢查功能(即只要在調(diào)用該函數(shù)的時候為 dest 與 src 參數(shù)錯誤傳入 NULL 指針時都會引發(fā) assert),與此同時,對 MemCopy 函數(shù)的代碼量也進行了大幅度瘦身,不得不說這是一個兩全其美的好辦法。
實際上,在編程中我們經(jīng)常會出于某種目的(如把 assert 宏定義成當發(fā)生錯誤時不是中止調(diào)用程序的執(zhí)行,而是在發(fā)生錯誤的位置轉(zhuǎn)入調(diào)試程序,又或者是允許用戶選擇讓程序繼續(xù)運行等)需要對 assert 宏進行重新定義
。
但值得注意的是,不管斷言宏最終是用什么樣的方式進行定義,其所定義宏的主要目的都是要使用它來對傳遞給相應函數(shù)的參數(shù)進行確認檢查。
如果違背了這條宏定義原則,那么所定義的宏將會偏離方向,失去宏定義本身的意義。與此同時,為不影響標準 assert 宏的使用,最好使用其他的名字。例如,下面的示例代碼就展示了用戶如何重定義自己的宏 ASSERT:
/*使用斷言測試*/
#ifdef DEBUG
/*處理函數(shù)原型*/
void Assert(char * filename, unsigned int lineno);
#define ASSERT(condition)\
if(condition)\
NULL; \
else\
Assert(__FILE__ , __LINE__)
/*不使用斷言測試*/
#else
#define ASSERT(condition) NULL
#endif
void Assert(char * filename, unsigned int lineno)
{
fflush(stdout);
fprintf(stderr,'\nAssert failed: %s, line %u\n',filename, lineno);
fflush(stderr);
abort();
}
如果定義了 DEBUG,ASSERT 將被擴展為一個if語句,否則執(zhí)行“#define ASSERT(condition) NULL
”替換成 NULL。
這里需要注意的是,因為在編寫 C 語言代碼時,在每個語句后面加一個分號“;
”已經(jīng)成為一種約定俗成的習慣,因此很有可能會在“Assert(__FILE__,__LINE__)
”調(diào)用語句之后習慣性地加上一個分號。
實際上并不需要這個分號,因為用戶在調(diào)用 ASSERT 宏時,已經(jīng)給出了一個分號。面對這種問題,我們可以使用“do{}while(0)”結(jié)構(gòu)進行處理,如下面的代碼所示:
#define ASSERT(condition)\
do{ \
if(condition)\
NULL; \
else\
Assert(__FILE__ , __LINE__);\
}while(0)
現(xiàn)在,將不再為分號“;”而擔心了,調(diào)用示例如下:
void Test(unsigned char *str)
{
ASSERT(str != NULL);
/*函數(shù)處理代碼*/
}
int main(void)
{
Test(NULL);
return 0;
}
很顯然,因為調(diào)用語句“Test(NULL)
”為參數(shù) str 錯誤傳入一個 NULL 指針的原因,所以 ASSERT
宏會自動檢測到這個錯誤,同時根據(jù)宏 __FILE__
和 __LINE__
所提供的文件名和行號參數(shù)在標準錯誤輸出設備 stderr 上打印一條錯誤消息,然后調(diào)用 abort 函數(shù)中止程序的執(zhí)行。運行結(jié)果如圖 1 所示。
圖 1 調(diào)用自定義 ASSERT 宏的運行結(jié)果
如果這時候?qū)⒆远x ASSERT 宏替換成標準 assert 宏結(jié)果會是怎樣的呢?如下面的示例代碼所示:
void Test(unsigned char *str)
{
assert(str != NULL);
/*函數(shù)處理代碼*/
}
毋庸置疑,標準 assert 宏同樣會自動檢測到這個 NULL 指針錯誤。與此同時,標準 assert 宏除給出以上信息之外,還能夠顯示出已經(jīng)失敗的測試條件。運行結(jié)果如圖 2 所示。
圖 2 調(diào)用標準 assert 宏的運行結(jié)果
從上面的示例中不難發(fā)現(xiàn),對標準的 assert 宏來說,自定義的 ASSERT 宏將具有更大的靈活性,可以根據(jù)自己的需要打印輸出不同的信息,同時也可以對不同類型的錯誤或者警告信息使用不同的斷言,這也是在工程代碼中經(jīng)常使用的做法。當然,如果沒有什么特殊需求,還是建議使用標準 assert 宏。
在函數(shù)中使用斷言來檢查參數(shù)的合法性是斷言最主要的應用場景之一,它主要體現(xiàn)在如下 3 個方面:
1. 在代碼執(zhí)行之前或者在函數(shù)的入口處,使用斷言來檢查參數(shù)的合法性,這稱為前置條件斷言。
2. 在代碼執(zhí)行之后或者在函數(shù)的出口處,使用斷言來檢查參數(shù)是否被正確地執(zhí)行,這稱為后置條件斷言。
3. 在代碼執(zhí)行前后或者在函數(shù)的入出口處,使用斷言來檢查參數(shù)是否發(fā)生了變化,這稱為前后不變斷言。
例如,在上面的 Memcpy 函數(shù)中,除了可以通過“assert(dest !=NULL && src!=NULL);
”語句在函數(shù)的入口處檢查 dest 與 src 參數(shù)是否傳入 NULL 指針之外,還可以通過“assert(tmp_dest>=tmp_src+len||tmp_src>=tmp_dest+len);
”語句檢查兩個內(nèi)存塊是否發(fā)生重疊。如下面的示例代碼所示:
void *Memcpy(void *dest, const void *src, size_t len)
{
assert(dest!=NULL && src!=NULL);
char *tmp_dest = (char *)dest;
char *tmp_src = (char *)src;
/*檢查內(nèi)存塊是否重疊*/
assert(tmp_dest>=tmp_src+len||tmp_src>=tmp_dest+len);
while(len --)
*tmp_dest ++ = *tmp_src ++;
return dest;
}
除此之外,建議每一個 assert 宏只檢驗一個條件,這樣做的好處就是當斷言失敗時,便于程序排錯。
試想一下,如果在一個斷言中同時檢驗多個條件,當斷言失敗時,我們將很難直觀地判斷哪個條件失敗。因此,下面的斷言代碼應該更好一些,盡管這樣顯得有些多此一舉:
assert(dest!=NULL);
assert(src!=NULL);
最后,建議 assert 宏后面的語句應該空一行,以形成邏輯和視覺上的一致感,讓代碼有一種視覺上的美感。同時為復雜的斷言添加必要的注釋,可澄清斷言含義并減少不必要的誤用。
默認情況下,因為 assert 宏只有在 Debug 版本中才能起作用,而在 Release 版本中將被忽略。因此,在程序設計中應該避免在斷言表達式中使用改變環(huán)境的語句。如下面的示例代碼所示:
int Test(int i)
{
assert(i++);
return i;
}
int main(void)
{
int i=1;
printf('%d\n',Test(i));
return 0;
}
對于上面的示例代碼,由于“assert(i++)
”語句的原因,將導致不同的編譯版本產(chǎn)生不同的結(jié)果。如果是在 Debug 版本中,因為這里向變量 i 所賦的初始值為 1,所以在執(zhí)行“assert(i++)”語句的時候?qū)⑼ㄟ^條件檢查,進而繼續(xù)執(zhí)行“i++”,最后輸出的結(jié)果值為 2;如果是在 Release 版本中,函數(shù)中的斷言語句“assert(i++)”將被忽略掉,這樣表達式“i++”將得不到執(zhí)行,從而導致輸出的結(jié)果值還是 1。
因此,應該避免在斷言表達式中使用類似“i++
”這樣改變環(huán)境的語句,使用如下代碼進行替換:
int Test(int i)
{
assert(i);
i++;
return i;
}
現(xiàn)在,無論是 Debug 版本,還是 Release 版本的輸出結(jié)果都將為 2。
在對斷言的使用中,一定要遵循這樣一條規(guī)定:對來自系統(tǒng)內(nèi)部的可靠的數(shù)據(jù)使用斷言,對于外部不可靠數(shù)據(jù)不能夠使用斷言,而應該使用錯誤處理代碼。換句話說,斷言是用來處理不應該發(fā)生的非法情況,而對于可能會發(fā)生且必須處理的情況應該使用錯誤處理代碼,而不是斷言。
在通常情況下,系統(tǒng)外部的數(shù)據(jù)(如不合法的用戶輸入)都是不可靠的,需要做嚴格的檢查(如某模塊在收到其他模塊或鏈路上的消息后,要對消息的合理性進行檢查,此過程為正常的錯誤檢查,不能用斷言來實現(xiàn))才能放行到系統(tǒng)內(nèi)部,這相當于一個守衛(wèi)。
而對于系統(tǒng)內(nèi)部的交互(如子程序調(diào)用),如果每次都去處理輸入的數(shù)據(jù),也就相當于系統(tǒng)沒有可信的邊界,這樣會讓代碼變得臃腫復雜。
事實上,在系統(tǒng)內(nèi)部,傳遞給子程序預期的恰當數(shù)據(jù)應該是調(diào)用者的責任,系統(tǒng)內(nèi)的調(diào)用者應該確保傳遞給子程序的數(shù)據(jù)是恰當且可以正常工作的。這樣一來,就隔離了不可靠的外部環(huán)境和可靠的系統(tǒng)內(nèi)部環(huán)境,降低復雜度。
但是在代碼編寫與測試階段,代碼很可能包含一些意想不到的缺陷,也許是處理外部數(shù)據(jù)的程序考慮得不夠周全,也許是調(diào)用系統(tǒng)內(nèi)部子程序的代碼存在錯誤,造成子程序調(diào)用失敗。
這個時候,斷言就可以發(fā)揮作用,用來確診到底是哪部分出現(xiàn)了問題而導致子程序調(diào)用失敗。在清理所有缺陷之后,就建立了內(nèi)外有別的信用體系。等到發(fā)行版的時候,這些斷言就沒有存在的必要了。因此,不能用斷言來檢查最終產(chǎn)品肯定會出現(xiàn)且必須處理的錯誤情況。
看下面一段示例代碼:
char * Strdup(const char * source)
{
assert(source != NULL);
char * result=NULL;
size_t len = strlen(source) +1;
result = (char *)malloc(len);
assert(result != NULL);
strcpy(result, source);
return result;
}
對于上面的 Strdup 函數(shù),相信大家都不陌生。其中,第一個斷言語句“assert(source!=NULL)”用來檢查該程序正常工作時絕對不應該發(fā)生的非法情況。
換句話說,在調(diào)用代碼正確的情況下傳遞給 source 參數(shù)的值必然不為 NULL,如果斷言失敗,說明調(diào)用代碼中有錯誤,必須修改。因此,它屬于斷言的正常使用情況。
而第二個斷言語句“assert(result!=NULL)”的用法則不同,它測試的是錯誤情況,是在其最終產(chǎn)品中肯定會出現(xiàn)且必須對其進行處理的錯誤情況。
即對 malloc 函數(shù)而言,當內(nèi)存不足導致內(nèi)存分配失敗時就會返回 NULL,因此這里不應該使用 assert 宏進行處理,而應該使用錯誤處理代碼。如下面問題將使用 if 判斷語句進行處理:
char * Strdup(const char * source)
{
assert(source != NULL);
char * result=NULL;
size_t len = strlen(source)+1;
result = (char *)malloc(len);
if (result != NULL)
{
strcpy(result, source);
}
return result;
}
總之記住一句話:斷言是用來檢查非法情況的,而不是測試和處理錯誤的。因此,不要混淆非法情況與錯誤情況之間的區(qū)別,后者是必然存在且一定要處理的。
對于防錯性程序設計,相信有經(jīng)驗的程序員并不陌生,大多數(shù)教科書也都鼓勵程序員進行防錯性程序設計。在程序設計過程中,總會或多或少產(chǎn)生一些錯誤,這些錯誤有些屬于設計階段隱藏下來的,有些則是在編碼中產(chǎn)生的。
為了避免和糾正這些錯誤,可在編碼過程中有意識地在程序中加進一些錯誤檢查的措施,這就是防錯性程序設計的基本思想。其中,它又可以分為主動式防錯程序設計和被動式防錯程序設計兩種。
主動式防錯程序設計是指周期性地對整個程序或數(shù)據(jù)庫進行搜查或在空閑時搜查異常情況。它既可以在處理輸入信息期間使用,也可以在系統(tǒng)空閑時間或等待下一個輸入時使用。如下面所列出的檢查均適合主動式防錯程序設計。
· 內(nèi)存檢查:如果在內(nèi)存的某些塊中存放了一些具有某種類型和范圍的數(shù)據(jù),則可對它們做經(jīng)常性檢查。
· 標志檢查:如果系統(tǒng)的狀態(tài)是由某些標志指示的,可對這些標志做單獨檢查。
· 反向檢查:對于一些從一種代碼翻譯成另一種代碼或從一種系統(tǒng)翻譯成另一種系統(tǒng)的數(shù)據(jù)或變量值,可以采用反向檢查,即利用反向翻譯來檢查原始值的翻譯是否正確。
· 狀態(tài)檢查:對于某些具有多個操作狀態(tài)的復雜系統(tǒng),若用某些特定的存儲值來表示這些狀態(tài),則可通過單獨檢查存儲值來驗證系統(tǒng)的操作狀態(tài)。
· 連接檢查:當使用鏈表結(jié)構(gòu)時,可檢查鏈表的連接情況。
· 時間檢查:如果已知道完成某項計算所需的最長時間,則可用定時器來監(jiān)視這個時間。
· 其他檢查:程序設計人員可經(jīng)常仔細地對所使用的數(shù)據(jù)結(jié)構(gòu)、操作序列和定時以及程序的功能加以考慮,從中得到要進行哪些檢查的啟發(fā)。
被動式防錯程序設計則是指必須等到某個輸入之后才能進行檢查,也就是達到檢查點時才能對程序的某些部分進行檢查。一般所要進行的檢查項目如下:
· 來自外部設備的輸入數(shù)據(jù),包括范圍、屬性是否正確。
· 由其他程序所提供的數(shù)據(jù)是否正確。
· 數(shù)據(jù)庫中的數(shù)據(jù),包括數(shù)組、文件、結(jié)構(gòu)、記錄是否正確。
· 操作員的輸入,包括輸入的性質(zhì)、順序是否正確。
· 棧的深度是否正確。
· 數(shù)組界限是否正確。
· 表達式中是否出現(xiàn)零分母情況。
· 正在運行的程序版本是否是所期望的(包括最后系統(tǒng)重新組合的日期)。
· 通過其他程序或外部設備的輸出數(shù)據(jù)是否正確。
雖然防錯性程序設計被譽為有較好的編碼風格,一直被業(yè)界強烈推薦。但防錯性程序設計也是一把雙刃劍,從調(diào)試錯誤的角度來看,它把原來簡單的、顯而易見的缺陷轉(zhuǎn)變成晦澀的、難以檢測的缺陷,而且診斷起來非常困難。從某種意義上講,防錯性程序設計隱瞞了程序的潛在錯誤。
當然,對于軟件產(chǎn)品,希望它越健壯越好。但是調(diào)試脆弱的程序更容易幫助我們發(fā)現(xiàn)其問題,因為當缺陷出現(xiàn)的時候它就會立即表現(xiàn)出來。
因此,在進行防錯性程序設計時,如果“不可能發(fā)生”的事情的確發(fā)生了,則需要使用斷言進行報警,這樣,才便于程序員在內(nèi)部調(diào)試階段及時對程序問題進行處理,從而保證發(fā)布的軟件產(chǎn)品具有良好的健壯性。
一個很常見的例子就是無處不在的 for 循環(huán),如下面的示例代碼所示:
for(i=0;i<count;i++)
{
/*處理代碼*/
}
在幾乎所有的 for 循環(huán)示例中,其行為都是迭代從 0 開始到“count-1”,因此,大家也都自然而然地編寫成了上面這種防錯性版本。但存在的問題是:如果 for 循環(huán)中的索引 i 值確實大于 count,那么極有可能意味著代碼中存在著潛在的缺陷問題。
由于上面的 for 循環(huán)示例采用了防錯性程序設計方式,因此,就算是在內(nèi)部測試階段中出現(xiàn)了這種缺陷也很難發(fā)現(xiàn)其問題的所在,更加不可能出現(xiàn)系統(tǒng)報警提示。同時,因為這個潛在的程序缺陷,極有可能會在以后讓我們吃盡苦頭,而且非常難以診斷。
那么,不采用防錯性程序設計會是什么樣子呢?如下面的示例代碼所示:
for(i=0;i!=count;i++)
{
/*處理代碼*/
}
很顯然,這種寫法肯定是不行的,當 for 循環(huán)中的索引 i 值確實大于 count 時,它還是不會停止循環(huán)。
對于上面的問題,斷言為我們提供了一個非常簡單的解決方法,如下面的示例代碼所示:
for(i=0;i<count;i++)
{
/*處理代碼*/
}
assert(i==count);
不難發(fā)現(xiàn),通過斷言真正實現(xiàn)了一舉兩得的目的:健壯的產(chǎn)品軟件和脆弱的開發(fā)調(diào)試程序,即在該程序的交付版本中,相應的程序防錯代碼可以保證當程序的缺陷問題出現(xiàn)的時候,用戶可以不受損失;而在該程序的內(nèi)部調(diào)試版本中,潛在的錯誤仍然可以通過斷言預警報告。
因此,“無論你在哪里編寫防錯性代碼,都應該盡量確保使用斷言來保護這段代碼”。當然,也不必過分拘泥于此。例如,如果每次執(zhí)行 for 循環(huán)時索引 i 的值只是簡單地增 1,那么要使索引i的值超過 count 從而引起問題幾乎是不可能的。在這種情況下,相應的斷言也就沒有任何存在的意義,應該從程序中刪除。
但是,如果索引 i 的值有其他處理情況,則必須使用斷言進行預警。由此可見,在防錯性程序設計中是否需要使用斷言進行錯誤報警要視具體情況而定,在編碼之前都要問自己:“在進行防錯性程序設計時,程序中隱瞞錯誤了嗎?”如果答案是肯定的,就必須在程序中加上相應的斷言,以此來對這些錯誤進行報警。否則,就不要多此一舉了。
在日常軟件設計中,如果原先規(guī)定的一部分功能尚未實現(xiàn),則應該使用斷言來保證這些沒有被定義的特性或功能不被使用。例如,某通信模塊在設計時,準備提供“無連接”和“連接”這兩種業(yè)務。但當前的版本中僅實現(xiàn)了“無連接”業(yè)務,且在此版本的正式發(fā)行版中,用戶(上層模塊)不應產(chǎn)生“連接”業(yè)務的請求,那么在測試時可用斷言來檢查用戶是否使用了“連接”業(yè)務。如下面的示例代碼所示:
/*無連接業(yè)務*/
#define CONNECTIONLESS 0
/*連接業(yè)務*/
#define CONNECTION 1
int MessageProcess(MESSAGE *msg)
{
assert(msg != NULL);
unsigned char service;
service = GetMessageService(msg);
/*使用斷言來檢查用戶是否使用了“連接”業(yè)務*/
assert(service != CONNECTION);
/*處理代碼*/
}
在程序設計中,不能夠使用斷言來檢查程序運行時所需的軟硬件環(huán)境及配置要求
,它們需要由專門的處理代碼進行檢查處理。而斷言僅可對程序開發(fā)環(huán)境(OS/Compiler/Hardware)中的假設及所配置的某版本軟硬件是否具有某種功能的假設進行檢查
。例如,某網(wǎng)卡是否在系統(tǒng)運行環(huán)境中配置了,應由程序中正式代碼來檢查;而此網(wǎng)卡是否具有某設想的功能,則可以由斷言來檢查。
除此之外,對編譯器提供的功能及特性的假設也可以使用斷言進行檢查,如下面的示例代碼所示:
/*int類型占用的內(nèi)存空間是否為2*/
assert(sizeof(int)== 2);
/*long類型占用的內(nèi)存空間是否為4*/
assert(sizeof(long)==4);
/*byte的寬度是否為8*/
assert(CHAR_BIT==8);
之所以可以這樣使用斷言,那是因為軟件最終發(fā)行的 Release 版本與編譯器已沒有任何直接關(guān)系。
最后,必須保證軟件的 Debug 與 Release 兩個版本在實現(xiàn)功能上的一致性,同時可以使用調(diào)測開關(guān)來切換這兩個不同的版本,以便統(tǒng)一維護,切記不要同時存在 Debug 版本與 Release 版本兩個不同的源文件。
當然,因為頻繁調(diào)用 assert 會極大影響程序的性能,增加額外的開銷。因此,應該在正式軟件產(chǎn)品(即 Release 版本)中將斷言及其他調(diào)測代碼關(guān)掉(尤其是針對自定義的斷言宏)。在調(diào)試結(jié)束后,可以通過在包含#include <assert.h>
的語句之前插入 #define NDEBUG
來禁用assert調(diào)用,示例代碼如下:
#include <stdio.h>
#define NDEBUG
#include <assert.h>