很长一段时间,gcc has been providing有许多内置的比特函数,特别是尾部和前导0比特的数量(也适用于long unsigned
和long long unsigned
后缀l
和ll
):
- 内置函数:
int __builtin_clz (unsigned int x)
返回
x
中的前导0位数,从最高位开始 位置。如果x
为0,则结果未定义。- 内置函数:
int __builtin_ctz (unsigned int x)
返回
x
中的尾随0位数,从最低有效位开始 位置。如果x
为0,则结果未定义。
然而,在我测试的每个在线(免责声明:仅x64)编译器上,结果是clz(0)
和ctz(0)
都返回底层内置类型的位数,例如
#include <iostream>
#include <limits>
int main()
{
// prints 32 32 32 on most systems
std::cout << std::numeric_limits<unsigned>::digits << " " << __builtin_ctz(0) << " " << __builtin_clz(0);
}
std=c++1y
模式下的最新Clang SVN中继使所有这些函数放宽了C ++ 14 constexpr
,这使得它们可以在SFINAE表达式中用于3 {3}周围的包装函数模板。 {1}} / ctz
内置clz
,unsigned
和unsigned long
unsigned long long
此hack的好处是,为template<class T> // wrapper class specialized for u, ul, ull (not shown)
constexpr int ctznz(T x) { return wrapper_class_around_builtin_ctz<T>()(x); }
// overload for platforms where ctznz returns size of underlying type
template<class T>
constexpr auto ctz(T x)
-> typename std::enable_if<ctznz(0) == std::numeric_limits<T>::digits, int>::type
{ return ctznz(x); }
// overload for platforms where ctznz does something else
template<class T>
constexpr auto ctz(T x)
-> typename std::enable_if<ctznz(0) != std::numeric_limits<T>::digits, int>::type
{ return x ? ctznz(x) : std::numeric_limits<T>::digits; }
提供所需结果的平台可以省略额外的条件来测试ctz(0)
(这可能看起来是微优化,但是当你已经失败时到内置的比特功能水平,它可以产生很大的不同)
内置函数族x==0
和clz(0)
如何未定义?
ctz(0)
例外吗?答案 0 :(得分:13)
值未定义的原因是它允许编译器使用未定义结果的处理器指令,而这些指令是获得答案的最快方法。
但重要的是要明白,不仅结果不明确;他们是不确定的。例如,在给出英特尔指令参考的情况下,它对于返回当前时间的低7位的指令是有效的。
这里有趣/危险:编译器编写者可以利用这种情况来生成更小的代码。考虑一下代码的非模板专业化版本:
using std::numeric_limits;
template<class T>
constexpr auto ctz(T x) {
return ctznz(0) == numeric_limits<T>::digits || x != 0
? ctznz(x) : numeric_limits<T>::digits;
}
这适用于已决定返回ctznz(0)的#bits的处理器/编译器。但是,如果处理器/编译器决定返回伪随机值,编译器可能会决定“我被允许返回我想要的任何ctznz(0),如果我返回#bits,代码会更小,所以我会” 。然后代码最终会一直调用ctznz,即使它产生了错误的答案。
换句话说:编译器的未定义结果不能保证与运行程序的未定义结果相同。
真的没办法解决这个问题。如果必须使用__builtin_clz,源操作数可能为零,则必须始终添加检查。
答案 1 :(得分:11)
不幸的是,即使x86-64实现也有所不同 - 来自英特尔的instruction set reference,BSF
和BSR
,源操作数值为(0)
,离开目标 undefined ,并设置ZF
(零标志)。因此,微架构或AMD和英特尔之间的行为可能不一致。 (我相信AMD不会修改目的地。)
较新的LZCNT
和TZCNT
指令无处不在。两者都只出现在Haswell架构中(针对英特尔)。