背景
在很长一段时间内,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
,这使得它们可以在unsigned
、unsigned long
和unsigned long long
的3个ctz
/ clz
内置函数周围的包装函数模板的SFINAE表达式中使用
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)
的定义有多不明确
std::invalid_argument
std::invalid_argument
x64吗?对于当前的gcc发行版,他们会返回底层类型的大小吗?发布于 2013-10-23 05:43:56
不幸的是,即使是x86-64实现也可能有所不同--与英特尔的instruction set reference、BSF
和BSR
不同,源操作数值为(0)
的x86-64实现没有定义目标,并设置了ZF
(零标志)。因此,微体系结构或AMD和Intel之间的行为可能并不一致。(我相信AMD保持目的地不变。)
较新的LZCNT
和TZCNT
指令并不普遍。两者都只存在于Haswell架构(针对英特尔)。
发布于 2014-11-08 04:01:18
该值未定义的原因是,它允许编译器使用结果未定义的处理器指令,而这些指令是获得答案的最快方法。
但重要的是要理解,不仅结果是不确定的,它们也是不确定的。例如,根据英特尔的指令参考,该指令返回当前时间的低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)返回#位的处理器/编译器上工作得很好。但在决定返回伪随机值的处理器/编译器上,编译器可能会决定“我可以为ctznz(0)返回任何我想要的东西,如果我返回#bit,代码就会更小,所以我会这样做”。然后代码就会一直调用ctznz,即使它产生了错误的答案。
换句话说:编译器的未定义结果不一定是未定义的,就像正在运行的程序的未定义结果一样。
确实没有办法绕过这一点。如果必须使用源操作数可能为零的__builtin_clz,则必须始终添加检查。
https://stackoverflow.com/questions/19527897
复制相似问题