我可以摆脱 CTZ 和添加到指针之间的符号扩展吗?

har*_*old 5 x86 assembly gcc

对于这样的代码:

#include <stdint.h>

char* ptrAdd(char* ptr, uint32_t x)
{
    return ptr + (uint32_t)__builtin_ctz(x);
}
Run Code Online (Sandbox Code Playgroud)

GCC 生成一个符号扩展:(godbolt 链接

xor eax, eax
rep bsf eax, esi
cdqe ; sign-extend eax into rax
add rax, rdi
ret
Run Code Online (Sandbox Code Playgroud)

当然,这完全是多余的——这是公然对无符号整数进行符号扩展。我可以说服海湾合作委员会不要这样做吗?

这个问题自 GCC 4.9.0 以来就存在,但在此之前它曾经是一个显式的零扩展,这也是多余的。

Bee*_*ope 4

部分解决方案是使用 64 位版本的ctz以及-march参数,以便tzcnt使用 来代替bsf,如下所示:

char* ptrAdd(char* ptr, uint32_t x)
{
    return ptr + __builtin_ctzl(x);
}
Run Code Online (Sandbox Code Playgroud)

这会导致无符号扩展:

ptrAdd(char*, unsigned int):
  mov eax, esi
  tzcnt rax, rax
  add rax, rdi
  ret
Run Code Online (Sandbox Code Playgroud)

它有一个mov(用于进行 32 到 64 位零扩展),它取代了 32 位版本中的归零xor(这是为了解决tzcnt 错误依赖于目标问题)。这些成本大致相同,但mov在内联后更有可能消失。64 位的结果tzcnt与 32 位的结果相同,除了未定义的零输入的情况(就内在而言gcc,不是tzcnt)。

不幸的是,如果没有-march让编译器使用的参数,tzcnt它将使用bsf并且在这种情况下仍然进行符号扩展。

似乎bsf和之间不同行为的根源tzcnt在于,在使用该版本的情况下bsf,指令行为未定义为零。因此原则上,该指令可以返回任何内容,甚至是我们通常期望的 0 到 63 范围之外的值。结合返回值被声明为 的事实int,简单地省略符号扩展可能会导致像 这样的“不可能”的情况(__builtin_clzl (x) & 0xff) == 0xdeadbeef

现在,根据 gcc 文档,零输入__builtin_ctzl有一个“未定义的结果” - 但不清楚这是否与 C/C++“未定义的行为”相同,其中任何事情都可能发生(这将允许不可能的事情),或者只是意味着“一些未指定的值”。

您可以在gcc bugzilla上阅读有关此内容的信息,其中一个问题已经开放了大约 7 年。

  • 另请参阅此[相关问题](/sf/ask/2831953701/),其中我问的问题略有不同,但答案实际上是这个问题的答案(链接到相同的错误条目)。 (2认同)