这是 Clang 如何实现 __builtin_memcmp 的错误吗?

Is this a bug in how Clang implements __builtin_memcmp?

好的,这是代码:

#include <cstdio>
#include <cstdint>

template<typename T>
[[nodiscard]] constexpr bool
run_tests() noexcept
{
    T const blah1[6]{4, 8, 15, 16, 23, 42};
    T const blah2[6]{4, 8, 15, 16, 23, 42};
    T const blah3[6]{};

    if (__builtin_memcmp(blah1, blah2, 6 * sizeof(T)) != 0) {
        return false;
    }

    if (__builtin_memcmp(blah1, blah3, 6 * sizeof(T)) == 0) {
        return false;
    }

    return true;
}

int
main() noexcept
{
    static_assert(run_tests<int8_t>());
    // static_assert(run_tests<int64_t>());

    if (run_tests<int8_t>()) {
        printf("success\n");
    }
    else {
        printf("failure\n");
    }

    if (run_tests<int64_t>()) {
        printf("success\n");
    }
    else {
        printf("failure\n");
    }

    return 0;
}

使用 Clang 10(在 Windows 上),编译正常,如果我 运行 它,我得到:

success
success

如我所料。如果我取消注释以下内容:

static_assert(run_tests<int64_t>());

我收到以下编译时错误:

error: static_assert expression is not an integral constant expression

这里有两点很奇怪。首先是使用 int8_tuint8_t 可以很好地编译,但我提供的任何其他整数类型都会重现上述错误。其次,如果我注释掉这个:

if (__builtin_memcmp(blah1, blah3, 6 * sizeof(T)) == 0) {
    return false;
}

它对所有整数类型的编译都很好。因此,只有在数组不相等且不是 8 位的情况下,我才会收到此编译时错误。

想法?这对我来说似乎是一个错误,但我想在提交错误报告之前看看其他人的想法。作为参考,我尝试在启用 C++17 和 C++20 的情况下进行编译,并且我已经验证了 Clang 10 将 __builtin_memcmp 作为 constexpr 实现(均使用 __has 宏并且事实上在某些情况下它可以很好地编译为 constexpr)。

不是真正的答案,但很难将编译器错误消息放入注释中。可以帮到你

使用 clang 版本 9.0.1,我看到了相同的行为。

使用旧版本的 appleClang(Apple LLVM 版本 10.0.0 (clang-1000.11.45.5))我明白了,即使我注释掉 blah3 内容...

z.cpp:27:19: error: static_assert expression is not an integral constant expression
    static_assert(run_tests<int64_t>());
                  ^~~~~~~~~~~~~~~~~~~~
z.cpp:12:9: note: read of dereferenced one-past-the-end pointer is not allowed in a constant expression
    if (__builtin_memcmp(blah1, blah2, 6 * sizeof(T)) != 0) {
        ^
z.cpp:27:19: note: in call to 'run_tests()'
    static_assert(run_tests<int64_t>());
                  ^
1 error generated.

另外,我看了,libc++ 在这里使用它,所以我认为你基于文档的假设是正确的。

inline _LIBCPP_CONSTEXPR_AFTER_CXX14
int
char_traits<char>::compare(const char_type* __s1, const char_type* __s2, size_t __n) _NOEXCEPT
{
    if (__n == 0)
        return 0;
#if __has_feature(cxx_constexpr_string_builtins)
    return __builtin_memcmp(__s1, __s2, __n);
#elif _LIBCPP_STD_VER <= 14
    return memcmp(__s1, __s2, __n);
#else
    for (; __n; --__n, ++__s1, ++__s2)
    {
        if (lt(*__s1, *__s2))
            return -1;
        if (lt(*__s2, *__s1))
            return 1;
    }
    return 0;
#endif
}

FWIW,我将此添加到示例中...

static_assert(__has_feature(cxx_constexpr_string_builtins));

从技术上讲,这不是错误。没有记录在什么条件下 __builtin_memcmp 是 constexpr.

请注意,带有注释 "read of dereferenced one-past-the-end pointer is not allowed in a constant expression" 的另一个答案来自旧版本的 clang,__builtin_memcmp 遵循不同的规则。

如果满足以下条件,现实情况似乎是 clang 10 中的 __builtin_memcmp 是 constexpr:

  • 提供的长度为0
  • 类型是整数
    • 两种类型的大小都是1
    • 答案是0,即足够多的元素比较相等

排序不相等的多字节类型涉及字节顺序,这涉及考虑可能与主机体系结构不同的目标体系结构。这似乎没有在当前版本中解决。

这解决了两个观察结果:

  • 这与 int8_tuint8_t 编译得很好 - 因为它们的大小为 1
  • 如果我注释掉这个(!=)——因为相等是 constexpr

参考:https://reviews.llvm.org/D55510