使用 -fno-inline 对 memcmp 进行错误的 GCC 9(及更高版本)优化

Rom*_*098 6 c++ gcc compiler-optimization

有一个小func函数可以将内存块与静态常量归零数组进行比较。这是一个原始示例来说明问题:

#include <cstring>
#include <memory>

#define MAX_BYTES (256)

inline int my_memcmp(const void * mem1, const void * mem2, const size_t size)
{
    const auto *first  = reinterpret_cast<const uint8_t *>(mem1);
    const auto *second = reinterpret_cast<const uint8_t *>(mem2);
    if (size < 8)
    {
        for (int i = 0; i < size; ++i) {
            if (*first != *second) return (*first > *second) ? 1 : -1;
            ++first; ++second;
        }
        return 0;
    }

    return std::memcmp(mem1, mem2, size);
}

bool func(const uint8_t* in, size_t size)
{
  size_t remain = size;
  static const uint8_t zero_arr[MAX_BYTES] = { 0 };

  while (remain >= MAX_BYTES)
  {
    if (my_memcmp(in, zero_arr, MAX_BYTES) != 0)
    {
      return false;
    }
    remain -= MAX_BYTES;
    in += MAX_BYTES;
  }

  return true;
}
Run Code Online (Sandbox Code Playgroud)
  • 编译器:gcc 9.1 及更高版本
  • 编译器标志: -fno-inline -O3
  • Godbolt 拆解链接:https ://godbolt.org/z/P8vKGq
  • Godbolt 程序执行链接:https ://godbolt.org/z/qr8f16

如果我使用-fno-inline编译器标志,编译器会尝试优化上面的代码并只为my_memcmp函数生成 2 行代码,但它似乎总是返回 0:

my_memcmp(void const*, void const*, unsigned long) [clone .constprop.0]:
        movzx   eax, BYTE PTR [rdi]
        ret
Run Code Online (Sandbox Code Playgroud)

在我添加之前无法重现该问题-fno-inline(我在编译代码进行覆盖测试时遇到了问题,所以我需要添加 no-inline 以使报告更清晰。)另外我发现 gcc 8 没有这样的问题。是否有合理的解释或者它只是 GCC 9 和 10 中的一个错误?

amo*_*kov 8

这是 GCC 错误 95189,https: //gcc.gnu.org/bugzilla/show_bug.cgi ? id = 95189

基本上,如果缓冲区之一具有已知内容,GCC 可以为 memcmp 发出专门的代码,但是如果遇到零字节,这种专门化将无法正常工作(因为它对其他函数如 strcmp 是特殊的)。

它似乎已在 GCC 主开发分支(主干)上修复,但该修复程序尚未向后移植到 9.x 和 10.x 版本分支。

这个 C 中的最小重现在 -O2 处被错误编译,错误的评论中提到了一个类似的例子:

int f(const char *p)
{
    return __builtin_memcmp(p, "\0\0\0", 4);
}
Run Code Online (Sandbox Code Playgroud)