編譯期assert函式

2021-06-18 16:30:30 字數 3799 閱讀 8778

編譯期assert函式的目的在於當條件不滿足時,阻止編譯,從而防止錯誤的邏輯通過編輯。

而執行期assert的目的在於執行時發現條件不滿足時,產生乙個debug事件(debugbreak),從而讓偵錯程式停下來方便使用者檢查原因。

需求描述

有些比較關係,我們期望在編譯期就能確保正確,

需求情形:比如a,b,我們要求編譯期就能保證a>b,否則編譯不能通過。

很明顯,如果使用普通的方式,比如

if(a>b)

#error a must be bigger than b

則是行不通的,因為if只能在執行期間求值,而編譯期不行。

解決辦法:

編譯期assert的原理就是利用編譯期條件判斷,最好執行一段不合法或者未定義的**。

其實實現編譯期assert的難點不在於構造不合法的**或者未定義的**,而在於實現編譯期條件判斷,即編譯期對表示式求值。

ø不合法的**方法

當條件達到時,**流轉向不合法的**。

int static_assert_impl[ sizeof(typea)==sizeof(typeb)?1:-1];

當不滿足sizeof(typea)==sizeof(typeb)條件時,int static_assert_impl [-1];是不合法的,因為長度不能為負。從而達到了阻止編譯的要求。

ø未定義的**方法

當條件達到時,**流轉向未定義的模板**:利用模板來實現編譯期assert函式。使用模板類來解決:模板是在編譯期初始化的,利用這個特性,可以達到編譯期執行某些例項。

當乙個模板類例項化時,沒有相關的例項化定義,則編譯器就會報錯。

那麼只定義允許的例項化定義,不定義不允許的例項化定義。

具體到實踐上即:只定義求值結果為true的例項化定義,不定義求值結果為false的例項化定義。

注:為了提高效率,我們使用struct來代替class,因為編譯器不為struct產生預設的構造、賦值等函式,而這些我們都不需要。

不定義條件為false的實現化定義:static_assert_impl

再定義乙個巨集來生成模板引數為true的static_assert_impl:

#define static_assert(x)  static_assert_impl<(bool)(x)> tem_obj;

這樣,當在使用static_assert(a>b)時,如果a沒有定義,而產生編譯錯誤,從而達到了我們的目的。

衍生的兩個問題

a)     

重複定義問題

這些巨集定義有個缺陷:在乙個作用域範圍內,只能使用乙個static_assert(x);這是不能容忍的。如果使用{}來對作用域降級的話,則static_assert(x)只能在函式內使用,不能滿足我們的全域性作用域內assert.

所以,不能用生成臨時物件的方法來實現。

即如果滿足了相等的條件,則定義了intstatic_assert_impl [1];這是合法的。但是如果多個地方都滿足了條件,那麼程式中就會定義多個intstatic_assert[1];很明顯,這是重複定義的錯誤,同時還會導致消耗不必要的記憶體。(儘管只有乙個位元組)。

解決辦法:c++為我們提供了乙個關鍵字typedef,似乎這個關鍵字天生就是為我們測試/校驗語法而生的。typedef不產生物件例項,只定義型別,其可以多次重複使用,如果多次定義則是後來覆蓋前面的定義。其附帶乙個好處時,其僅僅產生乙個語法上的定義,而不消耗任何實際的空間,其也就不產生任何例項,所以就不存在重複定義的說法。ok,完美的定義:

#define static_assert(x) typedefstatic_assert_impl[(x)?1:-1] static_assert_def;

#define static_assert(x) typedefstatic_assert_impl<(bool)(x)> static_assert_def;

這個定義有乙個問題就是:typdef並不產生例項,所以其並不對x求值。如果需要對x求值,必須static_assert_defobj,即定義static_assert_def物件,這又回到老路上去了。

b)     

編譯期求值問題

即必須找到編譯期求值的操作。在c/c++中,能夠編譯期求值的辦法:常量定義,sizeof運算子,模板例項化。

sizeof還有乙個好處是,其不僅僅可以對物件求值,還可以對型別求值--這就是我們需要的:sizeof(static_assert_impl<(bool)(x)>),而且sizeof還必須初始化static_assert_impl<(bool)(x)>。ok,達到了初始化static_assert_impl<(bool)(x)>的目的了。如果在全域性作用域區,單獨地寫上乙個整數:sizeof(static_assert_impl<(bool)(x)>),同樣編譯錯誤。即,如果在cpp檔案的前面寫上乙個4,明顯不合c,c++語法。我們必須要用這個整數來做點什麼,才合語法要求。

能用整數做什麼而又不產生臨時物件呢--例項化c++模板類/函式不會產生臨時物件。ok。定義乙個引數為int的類模板即可。

templatestruct static_assert_tester{};

則巨集轉換為:

#define static_assert(x) typedefstatic_assert_tester)>static_assert_def;

完整的**定義:

應用測試:

static_assert(3>1);                        //ok,編譯通過。

static_assert(3>4);                        //fail,編譯錯誤:

error c2027:use of undefined type 'static_assert_impl'

error c2371: assert_def': redefinition; different basic types     

在vc許多原始檔中,都有現成的編譯期assert可用:_static_assert。

østatic_assert之衍生應用:測試#if條件編譯

除了上面static_assert的原本作用外,利於static_assert我們還可以測試某些額外的功能。比如測試乙個表示式是否在當前編譯器下為編譯期常量。

乙個例子:#ifa==b

上述#if後,通常要求a==b是個常量表示式,如果a,b都是模板引數,從語法上講,它們都應該是常量性質的,示例**:

上面的,dosensortype是模板的特化引數,此時已經是編譯期常量的,edosensortypeid_dosensortype_optical是乙個常量巨集,#if

dosensortype==edosensortypeid_dosensortype_optical

從語法上分析應該是乙個編譯期常量表示式的,應該符合#if的邏輯意圖的。

但是,在ms編譯器下,#if

dosensortype==edosensortypeid_dosensortype_optical的邏輯結果就是#if 1,即條件編譯裡的語句始終執行。這是錯誤的,但是ms編譯器能夠通過編譯。

這應該是編譯器的乙個bug,但是我們應該避免,適當的時候,應該自己做些測試來避免編譯器的bug。測試就是利用static_assert。

當編譯器沒有bug時,上述**應該總是編譯成功的。

但是事實上,當dosensortype不等於edosensortypeid_dosensortype_optical時,其將觸發編譯錯誤,即巨集#ifdosensortype==edosensortypeid_dosensortype_optical條件編譯竟然總是為1,從而可以幫助我們發現這個編譯器bug,而改用其它的解決方案實現意圖。

assert 函式用法

assert巨集的原型定義在中,其作用是如果它的條件返回錯誤,則終止程式執行,原型定義 include void assert int expression assert的作用是現計算表示式 expression 如果其值為假 即為0 那麼它先向stderr列印一條出錯資訊,然後通過呼叫 abort...

assert 函式介紹

assert 斷言 可以有兩種形式 assert expression1 assert expression1 expression2 expression1 應該總是產生乙個布林值。expression2 可以是得出乙個值的任意表示式。這個值用於生成顯示更多除錯資訊的 string 訊息。斷言在預...

assert 函式用法

assert 函式用法 2007 10 17 12 15 assert巨集的原型定義在中,其作用是如果它的條件返回錯誤,則終止程式執行,原型定義 include void assert int expression assert的作用是現計算表示式 expression 如果其值為假 即為0 那麼它...