clang++ vs g++ constexpr 与越界引用的区别
clang++ vs g++ constexpr difference with out of bound reference
我有 constexpr std::array<int, N> v1{0}, v2{0};
,它的行为类似于大整数。所以,我写了一个 multiply
函数来计算数字的乘积。
#include <array>
#include <cstdint>
using namespace std;
template <int N>
constexpr array<int, 2 * N> multiply(const array<int, N> &a,
const array<int, N> &b) {
const int as = N, bs = N, rs = as + bs;
array<int, rs> result{0};
__int128_t carry = 0;
auto pr = begin(result);
for (int r = 0, lim = min(rs, as + bs - 1); r < lim; ++r) {
int i = r >= as ? as - 1 : r,
j = r - i,
k = i < bs - j ? i + 1 : bs - j; // min(i+1, bs-j);
auto pa = begin(a) + i;
auto pb = begin(b) + j;
while (k--) {
carry += static_cast<__int128_t>(*(pa--)) * (*(pb++));
}
*(pr++) = static_cast<int64_t>(carry);
}
return result;
}
int main() {
constexpr int N = 20;
constexpr array<int, N> v1{0}, v2{0};
constexpr array<int, 2 *N> result = multiply<N>(v1, v2);
return result[1];
}
请注意,乘法函数不正确以使其最小化。
当我使用 clang++ -std=c++17 -Wall -O0 example.cc
编译此代码时,我 错误地 得到:
example.cc:30:32: error: constexpr variable 'result' must be initialized by a constant expression
constexpr array<int, 2 *N> result = multiply<N>(v1, v2);
^ ~~~~~~~~~~~~~~~~~~~
example.cc:20:50: note: cannot refer to element -1 of array of 20 elements in a constant expression
carry += static_cast<__int128_t>(*(pa--)) * (*(pb++));
^
example.cc:30:41: note: in call to 'multiply(v1, v2)'
constexpr array<int, 2 *N> result = multiply<N>(v1, v2);
^
1 error generated.
但是用 gcc 可以正确编译。
为什么我认为clang的错误是错误的:
为了验证是否存在越界访问,我启用了 libstdc++ 的调试模式并使用 g++ -std=c++17 -Wall -D_GLIBCXX_DEBUG -g -O0 example.cc
进行了编译,并且没有出现如果存在越界访问就会发生的崩溃。
也在消毒剂 (g++ -fsanitize=address,undefined -fno-omit-frame-pointer
) 下代码 运行 成功。
我很好奇为什么 clang 声称越界访问,而实验清楚地表明情况并非如此。
铿锵是对的。你没有“erroneously get”这个错误。
*(pa--)
假设 k
初始设置为 i + 1
,在最后一次在 while
循环中计算此表达式的最后一次之前,pa
指向数组的第一个元素。 pa--
涉及评估 pa - 1
,根据 [expr.add]/4 会导致未定义的行为:(i = 0
, j = 1
)
When an expression that has integral type is added to or subtracted from a pointer, the result has the type of the pointer operand. If the expression P
points to element x[i]
of an array object x
with n
elements, the expressions P + J
and J + P
(where J
has the value j
) point to the (possibly-hypothetical) element x[i + j]
if 0 ≤ i + j ≤ n
; otherwise, the behavior is undefined. Likewise, the expression P - J
points to the (possibly-hypothetical) element x[i − j]
if 0 ≤ i − j ≤ n
; otherwise, the behavior is undefined.
现在,常量表达式不能求值:[expr.const]/2.6
an operation that would have undefined behavior as specified in Clauses [intro] through [cpp] of this International Standard
因此,multiply<N>(v1, v2)
不是常量表达式,这里需要诊断。
当然,这个 [-1]
指针通常不会引起问题,除非您取消引用它,但它仍然是未定义的行为,这会阻止它成为常量表达式的一部分。消毒剂只能诊断未定义行为的有限子集。
我有 constexpr std::array<int, N> v1{0}, v2{0};
,它的行为类似于大整数。所以,我写了一个 multiply
函数来计算数字的乘积。
#include <array>
#include <cstdint>
using namespace std;
template <int N>
constexpr array<int, 2 * N> multiply(const array<int, N> &a,
const array<int, N> &b) {
const int as = N, bs = N, rs = as + bs;
array<int, rs> result{0};
__int128_t carry = 0;
auto pr = begin(result);
for (int r = 0, lim = min(rs, as + bs - 1); r < lim; ++r) {
int i = r >= as ? as - 1 : r,
j = r - i,
k = i < bs - j ? i + 1 : bs - j; // min(i+1, bs-j);
auto pa = begin(a) + i;
auto pb = begin(b) + j;
while (k--) {
carry += static_cast<__int128_t>(*(pa--)) * (*(pb++));
}
*(pr++) = static_cast<int64_t>(carry);
}
return result;
}
int main() {
constexpr int N = 20;
constexpr array<int, N> v1{0}, v2{0};
constexpr array<int, 2 *N> result = multiply<N>(v1, v2);
return result[1];
}
请注意,乘法函数不正确以使其最小化。
当我使用 clang++ -std=c++17 -Wall -O0 example.cc
编译此代码时,我 错误地 得到:
example.cc:30:32: error: constexpr variable 'result' must be initialized by a constant expression
constexpr array<int, 2 *N> result = multiply<N>(v1, v2);
^ ~~~~~~~~~~~~~~~~~~~
example.cc:20:50: note: cannot refer to element -1 of array of 20 elements in a constant expression
carry += static_cast<__int128_t>(*(pa--)) * (*(pb++));
^
example.cc:30:41: note: in call to 'multiply(v1, v2)'
constexpr array<int, 2 *N> result = multiply<N>(v1, v2);
^
1 error generated.
但是用 gcc 可以正确编译。
为什么我认为clang的错误是错误的:
为了验证是否存在越界访问,我启用了 libstdc++ 的调试模式并使用 g++ -std=c++17 -Wall -D_GLIBCXX_DEBUG -g -O0 example.cc
进行了编译,并且没有出现如果存在越界访问就会发生的崩溃。
也在消毒剂 (g++ -fsanitize=address,undefined -fno-omit-frame-pointer
) 下代码 运行 成功。
我很好奇为什么 clang 声称越界访问,而实验清楚地表明情况并非如此。
铿锵是对的。你没有“erroneously get”这个错误。
*(pa--)
假设 k
初始设置为 i + 1
,在最后一次在 while
循环中计算此表达式的最后一次之前,pa
指向数组的第一个元素。 pa--
涉及评估 pa - 1
,根据 [expr.add]/4 会导致未定义的行为:(i = 0
, j = 1
)
When an expression that has integral type is added to or subtracted from a pointer, the result has the type of the pointer operand. If the expression
P
points to elementx[i]
of an array objectx
withn
elements, the expressionsP + J
andJ + P
(whereJ
has the valuej
) point to the (possibly-hypothetical) elementx[i + j]
if0 ≤ i + j ≤ n
; otherwise, the behavior is undefined. Likewise, the expressionP - J
points to the (possibly-hypothetical) elementx[i − j]
if0 ≤ i − j ≤ n
; otherwise, the behavior is undefined.
现在,常量表达式不能求值:[expr.const]/2.6
an operation that would have undefined behavior as specified in Clauses [intro] through [cpp] of this International Standard
因此,multiply<N>(v1, v2)
不是常量表达式,这里需要诊断。
当然,这个 [-1]
指针通常不会引起问题,除非您取消引用它,但它仍然是未定义的行为,这会阻止它成为常量表达式的一部分。消毒剂只能诊断未定义行为的有限子集。