寫這篇博客源於在閱讀lighttpd源代碼是遇到的一個關於assert應用的疑問。
在閱讀lighttpd源代碼時,發現比比皆是的對malloc的調用結果進行assert檢查,比如:Buffer.c:
buffer* buffer_init(void) {
buffer *b;
b = malloc(sizeof(*b));
assert(b);
b->ptr = NULL;
b->size = 0;
b->used = 0;
return b;
}
這里的assert(b)似乎有問題,實際release版本在運行中難道不會發生malloc返回NULL的情況嗎?之后在閱讀《Writing Solid Code 》一書時找到了答案。
對assert的基本用法就不再累述了,下面總結一下assert的實際應用的Recommended practice吧:
1、要使用斷言對函數參數進行確認
主要有以下情況:
- 指針不是NULL的斷言;
- index值或size值不是負值或小於已知限值的斷言;這一條也可以這么描述:要從程序中刪去無定義的特性或者在程序中使用斷言來檢查出無定義特性的非法使用
2、每個斷言必須在頭文件中的函數功能描述的斷言部分進行說明(不要浪費別人的時間 ─── 詳細說明不清楚的斷言 ),例如:
* Asserts:
* 'size' is no greater then LIMIT.
* 'format' is not NULL.
* The function result is no greater than LIMIT.
*/
如果沒有斷言, 寫 “Nothing”:
* Asserts:
* Nothing
*/
(以上的格式也許嚴格了一些,不過如果真的這么做,對代碼的可閱讀性會很有幫助)
3、斷言和錯誤校驗的區別
正確使用斷言,必須要清楚程序錯誤(program errors)和運行時錯誤(run- time errors)之間的區別;
- 一個程序錯誤是一個bug,永遠不應該發生。
- 一個運行時錯誤是在程序運行的任何時候都可能會發生.
斷言並不是一種處理運行時錯誤的機制。例如在需要輸入正數的時候,用戶輸入了一個負數,如果用斷言來檢測這種情況就不是好的設計。對於這種情況需要用合適的錯誤檢查和恢復處理的代碼來進行處理。
再回到lighttpd中對malloc函數的返回值進行assert斷言,我覺得也屬於這個問題,這應該是一個運行時錯誤,而不是程序錯誤;所以,我覺得《C和指針》一書中對malloc返回NULL處理是通過一個錯誤檢查分配器來處理的。
4、斷言和bug
斷言大致分為前置條件(Preconditions)、后置條件(Postconditions)、不變性條件(Invariants)
如果前置條件不成立,發生Assertion violations,則調用該函數的代碼存在bug,需要盡快找到並解決;
如果后置條件不成立,發生Assertion violations,則(函數的)實現代碼存在bug,需要盡快找到並解決;
例如:
void doBlah(int x)
{
assert(x!=0);
....
}
這段代碼說明這個函數的調用不可能傳入參數0,如果發生這種情況,說明調用這個函數的代碼存在bug;
以上是自己的一點理解,歡迎高手指正!!!
參考:How to use assertions in C