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] 指针通常不会引起问题,除非您取消引用它,但它仍然是未定义的行为,这会阻止它成为常量表达式的一部分。消毒剂只能诊断未定义行为的有限子集。