对于这样的代码:
#include <stdint.h>
char* ptrAdd(char* ptr, uint32_t x)
{
return ptr + (uint32_t)__builtin_ctz(x);
}
GCC生成符号扩展名:(godbolt link)
xor eax, eax
rep bsf eax, esi
cdqe ; sign-extend eax into rax
add rax, rdi
ret
这当然是完全冗余的 - 这是公然符号扩展无符号整数。我可以说服海湾合作委员会不要这样做吗?
自GCC 4.9.0以来存在问题,但在此之前它曾经是一个明确的零扩展,这也是多余的。
答案 0 :(得分:4)
部分解决方案是使用64位版本的ctz
以及-march
参数,以便使用tzcnt
代替bsf
,如下所示:
char* ptrAdd(char* ptr, uint32_t x)
{
return ptr + __builtin_ctzl(x);
}
This results没有符号扩展名:
ptrAdd(char*, unsigned int):
mov eax, esi
tzcnt rax, rax
add rax, rdi
ret
它有mov
(做32到64位的零扩展),取代了32位版本中的归零xor
(可以在{{1}处工作} false-dependency-on-destination issue)。这些费用大致相同,但内联后tzcnt
更有可能消失。 64位mov
的结果与32位的结果相同,除了零输入未定义的情况(就tzcnt
内在而言,而不是{{1} })。
不幸的是,如果没有允许编译器使用gcc
的{{1}}参数,它将使用tzcnt
,并且在这种情况下仍会执行符号扩展。
似乎-march
和tzcnt
之间不同行为的起源是,在使用bsf
版本的情况下,指令行为未定义为零。因此原则上,指令可以返回任何,甚至是我们通常期望的0到63范围之外的值。结合返回值声明为bsf
这一事实,简单地省略符号扩展名可能会导致“不可能”的情况,例如tzcnt
。
现在根据gcc文档,bsf
的零输入有一个“未定义的结果” - 但不清楚这是否与C / C ++相同“未定义的行为”任何事情都可能发生(这将允许不可能发生的事情),或者只是意味着“一些未指明的价值”。
您可以在问题已开放约7年的gcc bugzilla上阅读此内容。