这是 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_t
和 uint8_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_t
和 uint8_t
编译得很好 - 因为它们的大小为 1
- 如果我注释掉这个(
!=
)——因为相等是 constexpr
好的,这是代码:
#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_t
和 uint8_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_t
和uint8_t
编译得很好 - 因为它们的大小为 1 - 如果我注释掉这个(
!=
)——因为相等是 constexpr