【问题标题】:Is this a bug in how Clang implements __builtin_memcmp?这是 Clang 如何实现 __builtin_memcmp 的错误吗?
【发布时间】:2020-04-02 15:02:50
【问题描述】:

好的,代码如下:

#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 在一些场景)。

【问题讨论】:

标签: c++ clang llvm


【解决方案1】:

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

请注意,另一个带有注释“在常量表达式中不允许读取取消引用的过去指针”的答案来自旧版本的 clang,__builtin_memcmp 遵循不同的规则。

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

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

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

这解决了两个观察结果:

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

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

【讨论】:

  • 这是有道理的,尽管我会说他们要么需要解决这个问题,要么更新文档,因为它确实具有误导性。
【解决方案2】:

不是一个真正的答案,但很难将编译器错误消息放在评论中。可以帮助你

在 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));

【讨论】:

  • 有趣。我认为这是足够的证据让我提交这个错误并看看会发生什么。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-09-20
  • 1970-01-01
  • 1970-01-01
  • 2012-09-11
  • 2019-12-14
  • 1970-01-01
  • 2013-12-02
相关资源
最近更新 更多