1. 程式人生 > >(四)靜態斷言之一 assert 以及 通過宏定義處理文件包含關系

(四)靜態斷言之一 assert 以及 通過宏定義處理文件包含關系

調試 代碼 預處理 實現 deb 發布者 比較 使用 常見

一、斷言:運行時與預處理時

斷言(assertion)是一種編程常用的手段。想必大家都見過 assert 吧。今天我們就來了解一下它。

通常情況下,斷言就是將一個返回值總是需要為真的判別式放在語句中,用於排除在設計的邏輯上不應該產生的情況。

比如一個函數總需要輸入在一定的範圍內的參數,那麽程序員就額可以對該參數使用斷言,以迫使在該參數發生異常的時候程序退出,從而避免程序陷入邏輯的混亂。

從一些意義上講,斷言並不是正常程序鎖必須的,不過對於程序調試來說,通常斷言能夠幫助程序開發者快速定位那些違反了某些前提條件的程序錯誤。

例子:

#include<cassert>
using
namespace std; char* ArrayAlloc(int n) { assert(n > 0); return new char[n]; } int main() { char* a = ArrayAlloc(0); }

結果顯示,很明顯程序崩潰了。

技術分享圖片

這一點上,雖然異常可以防止程序崩潰,使程序得以繼續執行,但是該結束程序的時候,還是要果斷地用assert 結束程序

我們應該合理應用斷言和異常。

在C++中,程序員也可以定義宏NDEBUG來禁用assert 宏。這對發布程序來說還是必要的。

因為程序用戶退出總是敏感的,而且部分的程序錯誤也未必會導致程序全部功能失效。(比如上一段提到的異常就可以避免這種情況。)

那麽通過定義NDEBUG宏發布程序也可以盡量避免程序退出的狀況。而當程序有問題時,通過沒有定義宏NDEBUG的版本,程序員則可以比較容易地找到出問題的位置。

事實上,assert 宏在<cassert>中的實現方式類似如下形式:

#ifdef NDEBUG
# define assert(expression) (static_cast<void>0)   //!!!!!
#else
  ...
#endif

可以看到,一旦定義了NDEBUG宏,assert 宏將被展開為一條無意義的C語句(通常被編譯器優化掉)

在上一篇(三)中,我們還看到 #error 這樣的預處理指令,而事實上,通過預處理指令#if 和#error 的配合,也可以讓程序員在預處理階段進行斷言。

這樣的用法也是極為常見的。

書上對於這方面是這樣說的,原話如下:(由於沒有相關的設備支持,所以後面自己寫了個簡單的工程測試了一下)

比如GNU的cmathcalls.h 頭文件中(在我們的實驗機上,該文件位於/user/include/bits/cmathcalls.h),我們會看到如下代碼:
#ifndef _COMPLEX_h
#error "Never use <bits/cmathcalls.h> directly; include<complex.h>instead."
#endif
如果程序員直接包含頭文件<bits/cmathcalls.h>並進行編譯,就會引發錯誤。#error 指令會將後面的語句輸出,從而提醒用戶不要直接使用這個頭文件,而應該包含頭文件<complex.h>。

我們用這種方法測試一下:

我們首先寫一個頭文件 test.h 裏面有一個類 _Test 文件定義宏為 _TEST

//test.h
#define _TEST

class _Test
{
    int m_int;
public:
    _Test():m_int(0){}
    const int get_int()const { return m_int; }
};

我們再寫另一個頭文件 test1.h

//test1.h
#ifndef _TEST

  #error "請引用正確的頭文件 test.h"

#endif

意思是,如果該文件沒有重定義宏 _TEST ,那麽就用原版本

主調函數測試:

#include<iostream>
#include"test1.h"
using namespace std;  
int main()
{
    _Test a;
    cout << a.get_int() << endl;
}

我們試圖調用test1.h文件中的_TEST類,但是該文件沒有重新定義該類

技術分享圖片

觸發錯誤。

在次測試:

#include<iostream>
#include"test.h"
#include"test1.h"
using namespace std;  
int main()
{
    _Test a;
    cout << a.get_int() << endl;
}

技術分享圖片

這樣一來,通過預處理時的斷言,庫發布者就可以避免一些頭文件的引用問題。

感謝您的閱讀,生活愉快~

(四)靜態斷言之一 assert 以及 通過宏定義處理文件包含關系