簡體   English   中英

c ++編譯時斷言

[英]c++ compile-time assertion

我正在使用一個可能很危險的宏:

#define REMAINDER(v, size) ((v) & (size -1))

顯然它假設大小是2的冪。

我想確保大小確實是2的冪,但在編譯時。 (在運行時測試很容易,但不是我想要的)。

對我來說一個充分的測試就是大小總是一個常數(從不變量)。

我會使用BOOST_STATIC_ASSERT ,但我無法弄清楚如何將它用於我需要的東西。

首先要做的事情是:不需要微觀優化。 b是編譯時常量(實際上是2的冪)時,任何啟用了優化的合適編譯器都會將a % b轉換為該構造。

然后在特定斷言上,您可以使用相同的構造來斷言它[*]:

BOOST_STATIC_ASSERT( !(size & (size-1)) );

[*]請注意,正如Matthieu M指出的那樣,只有在size為無符號類型時才有效。 應該斷言 - 在編譯時不能斷言參數是非負的較小要求:

BOOST_STATIC_ASSERT( (X(0)-1) > X(0) ); // where X is the type of the argument

最后評論后編輯:

你錯過了這一點。 要使靜態斷言宏起作用, size必須是編譯時常量。 如果它是一個編譯時常量,那么只要在定義常量時斷言這也是最佳位置,因為它將作為文檔,並指向需要修改的代碼的精確點:

template <typename N>
class hash_map {
public:
   const std::size_t size = N;
   BOOST_STATIC_ASSERT( !(size & (size-1) ) ); // N must be power of 2 for fast %
   //...
};

同時聲明在編譯時保持不變量對於效率很重要,模糊代碼不是:只需將模運算保留在原位,因為編譯器將優化:

std::size_t hash_map::index_of( std::size_t hash ) const {
   return hash % size;
}

因為size是一個編譯時常量,並且它是2的冪(你之前斷言過),優化器會將%轉換為優化操作,而代碼仍然需要維護它的人可讀。

編輯:除非你有分析證明這是你的代碼中的瓶頸,並且你的編譯器沒有適當地優化(v%8),只需編寫明顯的代碼。

否則,因為你在這里處理整數,你可以使用模板而不是宏嗎? 然后你應該能夠在模板內部靜態斷言,這樣它就會在它被錯誤地實例化時標記出來。

例如:

template <int size>
int remainder(int v)
{
    BOOST_STATIC_ASSERT(!(size & (size - 1)));

    return v & (size -1);
};

斷言:

size && (size & (size - 1) == 0)

編輯:解釋。

如果size是2的冪,則只設置一位。 減1將產生一個值,其中所有位都設置為原始位。 ANDing這些給出0。

如果大小不是2的冪,則設置至少兩位。 減1將產生一個值,其中所有位都設置為原始的最低設置位。 對這些進行AND運算不會產生0,因為仍然設置了第二個和后一個(從右)位。

1000 & 0111 == 0000
1100 & 1011 == 1000
// Only use this if size is a power of 2
#define REMAINDER(v, size) ((v) & (size -1))

不要像白痴一樣對待你的用戶。 記錄您的功能和宏,然后繼續。


或者,如果你真的必須愚弄人,請使用模板:

template <size_t SIZE>
size_t remainder(size_t v) {
   // Perform whatever assertions you like on SIZE here

   return v & (SIZE - 1);
}

請注意,我必須對輸入和輸出的類型做出一些假設。

窺視孔優化 :在非常小的指令集上執行優化

這包括:

  • 常量折疊 :例如,如果size是常量,則在編譯期間將評估size - 1
  • 強度減少 :包括用更快的速度替換慢速操作......當結果相同時(顯然)

現在,讓我們看一下使用LLVM后端優化器的示例:

// C
int iremainder(int i) { return i % 4; }

unsigned uremainder(unsigned u) { return u % 4; }

// LLVM IR
define i32 @iremainder(i32 %i) nounwind readnone {
entry:
  %0 = srem i32 %i, 4                             ; <i32> [#uses=1]
  ret i32 %0
}

define i32 @uremainder(i32 %u) nounwind readnone {
entry:
  %0 = and i32 %u, 3                              ; <i32> [#uses=1]
  ret i32 %0
}

我們分析吧,好嗎?

  • u / 4收益率and i32 %u, 3 (換算成C表示u & 3
  • i / 4產生srem i32 %i, 4 (在C中翻譯為i % 4

多么聰明的編譯器 它並沒有忘記對有符號整數執行按位運算不會產生我想要的結果(只要整數是負數,並且分支比分支更昂貴)。

士氣:這種優化幾乎沒用,你甚至弄錯了。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM