对于这样的代码:
#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起存在该问题,但在此之前它曾经是显式的零扩展,这也是多余的。
最佳答案
部分解决方案是使用ctz
的64位版本以及-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
(可以在tzcnt
false-dependency-on-destination issue周围解决)。这些成本大致相同,但是内联后mov
更有可能消失。 64位tzcnt
的结果与32位1相同,除了未定义零输入的情况(就gcc
内在函数而言,不是tzcnt
)。不幸的是,如果没有
-march
参数允许编译器使用tzcnt
,它将使用bsf
,在这种情况下仍会进行符号扩展。似乎
bsf
和tzcnt
之间行为不同的根源是,在使用bsf
版本的情况下,指令行为未定义为零。因此,原则上,该指令可以返回任何内容,甚至返回我们通常期望的0到63范围之外的值。结合将返回值声明为int
的事实,仅省略符号扩展名可能会导致“不可能”的情况,例如(__builtin_clzl (x) & 0xff) == 0xdeadbeef
。现在,根据gcc文档,对
__builtin_ctzl
的零输入具有“未定义的结果”-但尚不清楚这是否与可能发生任何事情(这将允许不可能的事情)的C/C++“未定义的行为”相同,还是表示“一些未指定的值”。您可以在gcc bugzilla上阅读有关此内容的信息,该问题已经存在大约7年了。
关于gcc - 我可以摆脱CTZ与指针之间的符号扩展吗?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/48634422/