【问题标题】:clang++ vs g++ constexpr difference with out of bound referenceclang++ 与 g++ constexpr 的区别与超出范围的引用
【发布时间】:2020-01-25 08:32:55
【问题描述】:

我有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 进行编译,如果存在越界访问,则不会发生崩溃。

同样在 sanitizers (g++ -fsanitize=address,undefined -fno-omit-frame-pointer) 代码下运行成功。

我很好奇为什么 clang 声称越界访问,而实验清楚地表明事实并非如此。

【问题讨论】:

  • 为什么是 clang++ -std=c++17 -Wall -O0 ? ..try -O3
  • 没有崩溃不代表程序有效。 UB 只是未定义。什么事情都可能发生。 (我不是说有UB,我只是说没有崩溃不代表没有)。
  • @super,使用 -D_GLIBCXX_DEBUG 编译会处理越界访问,而 Sanitizers 会处理未定义的行为。
  • clang 不是在抱怨越界 access,它是在抱怨创建指向数组外部的指针(它不在乎你没有取消引用它)。
  • @MarcGlisse,如果属实,那么问题就大了,因为我不应该为我没有做的事情买单!警告就够了,但错误太多了!

标签: c++ c++17 undefined-behavior constexpr clang++


【解决方案1】:

Clang 是对的。你没有“erroneously get”这个错误。

*(pa--)

假设k 最初设置为i + 1,在最后一次在while 循环中计算此表达式之前,pa 指向数组的第一个元素。 pa-- 涉及评估pa - 1,这会导致根据[expr.add]/4 的未定义行为:(i = 0j = 1

当一个整数类型的表达式被添加到指针或从指针中减去时,结果具有指针操作数的类型。如果表达式P 指向带有n 元素的数组对象x 的元素x[i],则表达式P + JJ + P(其中J 的值为j)指向(可能是假设的)元素x[i + j] 如果0 ≤ i + j ≤ n;否则,行为未定义。 同样,表达式P - J 指向(可能是假设的)元素x[i − j] if 0 ≤ i − j ≤ n;否则,行为未定义。

现在,常量表达式不能计算:[expr.const]/2.6

本国际标准的 [intro] 到 [cpp] 条款中规定的具有未定义行为的操作

因此,multiply&lt;N&gt;(v1, v2) 不是常量表达式,此处需要进行诊断。

当然,这个[-1] 指针通常不会导致问题,除非您取消引用它,但它仍然是未定义的行为,这会阻止它成为常量表达式的一部分。消毒剂只能诊断有限的未定义行为子集。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-03-29
    • 1970-01-01
    • 1970-01-01
    • 2018-05-09
    • 2017-09-12
    • 1970-01-01
    • 1970-01-01
    • 2022-07-18
    相关资源
    最近更新 更多