GCC 與 Clang 會針對其前端可直接辨識的運算提供內建函式形式。位元計數內建函式可以根據選取的目標與最佳化內容,降低為目標指令、函式庫指令序列或最佳化後的運算式。原始碼應選擇能夠明確表達其可攜性與零輸入契約的介面。
GNU 風格的位元內建函式 #
GCC 與 Clang 支援以下適用於 unsigned int、unsigned long 與 unsigned long long 的函式族:
| 操作 | unsigned int | unsigned long | unsigned long long |
|---|---|---|---|
| 設定位元數量 | __builtin_popcount | __builtin_popcountl | __builtin_popcountll |
| 前導零位元數量 | __builtin_clz | __builtin_clzl | __builtin_clzll |
| 尾端零位元數量 | __builtin_ctz | __builtin_ctzl | __builtin_ctzll |
回傳型別為 int;尾綴用來選擇引數寬度。當邏輯寬度很重要時,請明確進行轉型,而不要依賴整數提升。常見 ABI 對 unsigned long 使用不同寬度,因此當演算法要求特定寬度時,請對 unsigned long long 值使用 ll 形式,或使用固定寬度的標準函式。
__builtin_clz(0) 與 __builtin_ctz(0) 會產生未定義行為。如果輸入可能為零,請先進行檢查:
#include <limits>
unsigned trailing_or_width(unsigned value) {
return value == 0 ? std::numeric_limits<unsigned>::digits
: static_cast<unsigned>(__builtin_ctz(value));
}popcount(0) 已有定義,並會回傳零;但是,使用一致的標準介面可以避免為相關操作建立不同的契約。
可攜式 C++ 優先使用 C++20 <bit>
#
C++20 在 <bit> 中為無號整數型別提供 std::popcount、std::countl_zero 與 std::countr_zero。不同於 GNU 的前導零與尾端零內建函式,這些計數函式將零輸入時的結果定義為該型別的值位元數量:
#include <bit>
#include <cstdint>
constexpr std::uint32_t bits = 0b101101;
static_assert(std::popcount(bits) == 4);
static_assert(std::countl_zero(std::uint32_t{0}) == 32);
static_assert(std::countr_zero(std::uint32_t{16}) == 4);如果必須支援函式庫功能邊界,請在包含 <bit> 後檢查 __cpp_lib_bitops;C++20 為此功能規定的值為 201907L。對於編譯器特定的程式碼,只有在先保護其使用後,才能透過 __has_builtin 檢查內建函式是否可用:
#if defined(__cpp_lib_bitops) && __cpp_lib_bitops >= 201907L
// Use std::popcount, std::countl_zero, and std::countr_zero.
#elif defined(__has_builtin)
# if __has_builtin(__builtin_popcount)
# define PROJECT_HAS_BUILTIN_POPCOUNT 1
# endif
#endifGCC 與 Clang 都有記錄這些特定的內建函式,因此明確支援兩者的專案也可以集中處理編譯器家族檢查,並為其他編譯器保留可攜式的替代方案。不要假設 __has_builtin 一定存在,也不要使用它來選擇零值前置條件未由公開 API 強制執行的內建函式。
程式碼產生與常數求值 #
對於效能關鍵的目標,請檢查產生的程式碼,因為內建函式的寫法並不保證會產生單一指令。硬體支援、目標功能、運算元寬度、周圍的控制流程與最佳化等級會共同決定降低結果。符合標準的實作可以為標準 <bit> 函式提供等價的語意資訊給最佳化器,同時保留可攜性;當其運算元與函式庫實作允許時,也能用於常數求值。對於 C++20 以前的函式庫,或經測量確認的目標差異,可以採用內建函式替代方案;但該方案應置於單一、能感知寬度且具有已文件化前置條件的包裝器後方。
評論
在 GitHub 上閱讀或評論(發表評論需要登入)