我对以下代码的行为/输出感到困惑,这是一个错误,或者我遗漏了一些东西。 (Ubuntu 16.04 on skylake arch)
#include <iostream>
int wrap(unsigned long long val) {
return __builtin_clzll(val);
}
using namespace std;
int main() {
cout << __builtin_clzll(0) << " " << wrap(0) << endl;
cout << __builtin_clzll(1) << " " << wrap(1) << endl;
cout << __builtin_clzll(2) << " " << wrap(2) << endl;
}
以下是不同编译设置的不同输出。我知道如果传递零,clz可能会返回一个未定义的结果。然而,直接内联调用总是很好,但只要涉及堆栈,编译器就会搞砸。
snk@maggy:~/HCS$ g++ -O0 test.cpp -o test
snk@maggy:~/HCS$ ./test
64 4196502
63 63
62 62
snk@maggy:~/HCS$
-O&gt; 0级别不会改变结果,我猜gcc是内联的。这是预期的结果......
snk@maggy:~/HCS$ g++ -O1 test.cpp -o test
snk@maggy:~/HCS$ ./test
64 64
63 63
62 62
使用-mlzcnt会更好:
snk@maggy:~/HCS$ g++ -O0 -mlzcnt test.cpp -o test
snk@maggy:~/HCS$ ./test
64 0
63 0
62 1
snk@maggy:~/HCS$ g++ -O1 -mlzcnt test.cpp -o test
snk@maggy:~/HCS$ ./test
64 64
63 63
62 62
snk@maggy:~/HCS$ g++ --version
g++ (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
谢谢, CH
答案 0 :(得分:2)
这个问题中有趣的案例是-mlzcnt
的行为。这在2013年报告为GCC bug 58928,但错误报告后来被撤消,因为当您为不支持-mlzcnt
操作码的英特尔CPU提供LZCNT
时,这是“预期”行为。 / p>
事实证明,LZCNT
是BSR
(位搜索反向),前缀为F3
;在未实现LZCNT的Intel CPU上,它被解释为无效操作码,它被解释为BSR,它返回1位的位位置(位0为低位),而不是前面的0的数量。
如上所述,使用参数0调用__builtin_clz
会产生未定义的行为。你应该对未定义行为的结果没有期望;甚至不会两次都是相同的结果。
答案 1 :(得分:1)
根据GCC documentation for built-in functions(添加粗体文字)
内置函数: int __ builtin_clz ( unsigned int x )
从最多开始,返回 x 中前导0位的数量 重要的位置。 如果 x 为0,则结果未定义。
...
内置函数: int __ builtin_clzll ( unsigned long long )
与
__builtin_clz
类似,但参数类型为unsigned long long
。
0
的结果未定义。
答案 2 :(得分:1)
让我们看看没有优化的wrap
:
.globl wrap(unsigned long long)
wrap(unsigned long long):
pushq %rbp
movq %rsp, %rbp
movq %rdi, -8(%rbp)
bsrq -8(%rbp), %rax
xorq $63, %rax
popq %rbp
ret
如果源为(0)
,AMD和Intel 都不会修改目标寄存器 - 尽管我相信只有AMD会记录此行为。实际上,您将获得之前设置的%rax
(它不是调用者保存的寄存器 - 它用于某些返回值),与(63)
xor'd。因此%rax
可以设置为某些“之前”的结果。可能甚至取决于address space randomization的结果。
此功能的堆栈不是罪魁祸首。 x86-64 ELF(和OS X Mach-O)ABI中的第一个参数在%rdi
中传递。缺乏优化会将其溢出到堆栈帧的内存中,但它仍在(0)
处存储-8(%rbp)
。
TL; DR - %rax
在输入时保留无关的垃圾,并且不会被(0)
源操作数修改为bsr
。 xor
可能会翻转几个低位。