c语言编译时检查逻辑错误吗,C语言陷阱与技巧20节,自定义“编译时”assert方法,在代码编译阶段检查“逻辑”错误...
在C語言程序開發中,程序員寫代碼時應該考慮的“面面俱到”,這樣才能寫出功能穩定的程序。例如,在實現 open() 函數時,先完成它的功能固然是重要的,但是程序員還需要考慮各種“意外”,比如下面這種情況。
假設不存在 /dev/sth 這個文件,仍然調用 open() 函數打開它:
int fd = open("/dev/sth", O_RDONLY);此時 open() 函數不應該感到迷惑,而是具備處理這種“意外”的能力。標準庫的 open() 函數在遇到這種情況時,會返回一個錯誤碼,對應著“文件不存在”的錯誤信息。
所以我們在開發C語言程序的過程中,寫出的代碼也應具備這種處理“意外”的能力。處理“意外”最常用的方式之一就是返回一個錯誤碼,輸出一段錯誤提示信息,這一點其實之前的文章討論過。
使用 assert
在C語言程序開發階段,為了方便,我們可以在可能出現不預期的“意外”處使用 assert()。assert() 的C語言原型如下:
#include void assert(scalar expression);
使用它需要包含 assert.h,assert() 接收一個參數 expression,可以是一個表達式,如果 expression 為真,則什么都不會發生。如果 expression 為假,則 assert() 會終止C語言程序,并且輸出 assert 失敗的代碼位置。
例如下面這段C語言代碼:
int fd = open("/dev/sth", O_RDONLY);assert(fd > 0);printf("fd = %d\n", fd);
編譯并執行,得到如下結果:
# gcc t.c# ./a.out a.out: t.c:11: main: Assertion `fd > 0' failed.Aborted可以看出,第 12 行的 printf() 函數并沒有被執行。這是因為程序運行環境里并沒有 “/dev/sth” 這個文件,所以 open() 函數執行失敗,傳遞給 assert() 的參數為假,C語言程序被終止,并且輸出 t.c 源文件第 11 行代碼 assert 失敗。
assert() 可以輸出出錯的代碼位置,這個特性在較為大型的C語言程序開發中是非常好用的,因為無需程序員再去手工調試代碼,排查出錯代碼的位置了。
不過,assert() 在遇到假參數時,直接將C語言程序終止太過于死板。比如某個C語言程序有兩套邏輯,第一套邏輯在 open() 函數成功打開文件時運行,第二套邏輯則在 open() 函數打開文件失敗時運行。要是使用 assert() 判斷 open() 函數是否成功打開文件,則第二套邏輯永遠沒有機會運行。
所以,assert() 一般僅用于開發階段幫助程序員定位錯誤,不能依賴 assert() 處理“意外”。事實上,為了便于使用,在定義了 NDEBUG 宏之后,assert() 就不再生成代碼了,此時 assert() 相當于一個空格。請看下面這段C語言代碼:
#include #include #include #include #define NDEBUG#include int main(){int fd = open("/dev/sth", O_RDONLY); assert(fd > 0);printf("fd = %d\n", fd);return0; }
編譯上述C語言代碼并執行,得到如下輸出:
# gcc t.c# ./a.out fd = -1編譯時 assert
可以看出,assert() 用于處理C語言程序可能出現諸多預期之外的“意外”時很有用,它能夠自己輸出究竟哪一個“意外”發生。但是 assert() 也是死板的,它在遇到假條件時直接把程序終止,剩余的代碼邏輯不再有機會執行。
另外還有一點要說明,assert() 本身也會影響C語言程序的運行效率,這也是它常常只被使用在開發階段的另一個原因。
其實仔細想想,使用 assert() 的目的其實只是希望它能夠在C語言程序遇到不預期的“意外”時提醒程序員,我們并不關心 assert() 是否參與程序運行。如果使用 assert() 判斷的是常量表達式,那我們可以自己定義一個 static_assert() 宏,并且讓它在編譯時就判斷條件表達式是否成立,這樣的宏可能在某些場合更加好用。
那該如何實現編譯時 assert 這個功能呢?
其實很簡單,首先應該明白數組的長度不可能是負數,基于這一點,static_assert() 宏就容易實現了,請看下面的C語言代碼:
#define static_assert(expr) \do{ char tmp[(expr)?1:-1]; }while(0)如果條件表達式為真,則 static_assert() 宏會定義一個長度為 1 的數組,否則就會嘗試定一個長度為 -1 的數組,此時必定無法編譯通過。這里值得一提的一個小技巧是使用 {} 符號將定義的 tmp 數組的作用域限定在本次調用的 static_assert 宏里,避免多次調用 static_assert 時出現重復定義。
寫出如下C語言代碼測試之:
int main() { static_assert(2>1);printf("assert 2>1\n");static_assert(2<1);printf("assert 2<1\n");return0; }
編譯這段C語言代碼,得到如下輸出:
顯然,static_assert() 宏在編譯階段就將假條件表達式找出來了??赡苡行┳x者會覺得如果 assert 成功,就會定義一個 tmp 數組,雖然它的長度很短,但是仍然浪費了??臻g。其實這里可以把長度為零的數組,即:
#define static_assert(expr) \do{ char tmp[(expr)?0:-1]; }while(0)在 assert 成功時會執行 char tmp[0];,它的長度為 0,感興趣的讀者可以使用 sizeof() 測試一下。到這里,我們就較為粗略的定義好了 static_assert 宏,它在編譯階段就能發現假條件。
小結
本節主要介紹了 assert() 的使用,應該能夠發現,在開發階段,它能夠幫助程序員快速的定位“意外”,也討論了 assert() 的不足之處,并在此基礎上自己定義了“編譯時”的static_assert 宏。按照這樣的思路,其實還有很多定義 static_assert() 宏的其他方法,具體哪些方法留給讀者自己思考了。
總結
以上是生活随笔為你收集整理的c语言编译时检查逻辑错误吗,C语言陷阱与技巧20节,自定义“编译时”assert方法,在代码编译阶段检查“逻辑”错误...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 稀疏矩阵的转置c语言程序,程序有问题求大
- 下一篇: 在c语言中,可以使用动态内存分配技术定义