【问题标题】:Why does compiler allow out-of-bounds array access even with constexpr index?为什么即使使用 constexpr 索引,编译器也允许越界数组访问?
【发布时间】:2015-02-17 22:05:33
【问题描述】:

例如,如果我们有一个 std::array 并且我们使用 constexpr 实例化一个超出范围的元素,则编译器不会报告错误:

constexpr int EvaluateSpecialArrayIndex(int a)
{ return a * sizeof(int); }

array<int, 5> arr;

cout << arr[98] << endl; //compiles fine

cout << arr[EvaluateSpecialArrayIndex(4)] << endl; //the same as above

我们不能以某种方式限制它吗?

【问题讨论】:

  • 注意:标准不允许arr[n]被拒绝,因为直到执行到那个点才会引起UB;例如这段代码可能在一个永远不会被调用的函数中。
  • @MattMcNabb 可能是not as simple as that
  • @GSerg UB 一旦被击中就可以进行时间旅行,但如果没有被击中则不能(起初听起来很矛盾,但实际上并非如此,正如第一个答案所示)。我猜你可能会争辩说,如果保证代码路径达到arr[98],那么 UB 可以时间旅行回到编译阶段

标签: c++ arrays c++14 constexpr c++17


【解决方案1】:

为确保constexpr 函数在编译时被评估,您必须通过将结果设为constexpr 来强制它们。例如:

#include <array>

int
main()
{
    constexpr std::array<int, 5> arr{1, 2, 3, 4, 5};
    int i = arr[6];  // run time error
}

但是:

#include <array>

int
main()
{
    constexpr std::array<int, 5> arr{1, 2, 3, 4, 5};
    constexpr int i = arr[6];  // compile time error
}

不幸的是,要使其真正起作用,std::array 必须符合 C++14 规范,而不是 C++11 规范。由于 C++11 规范没有将 std::array::operator[]const 重载标记为 constexpr

所以在 C++11 中你运气不好。在 C++14 中,您可以使其工作,但前提是 array 和调用索引运算符的结果都声明为 constexpr

澄清

数组索引的 C++11 规范如下:

                reference operator[](size_type n);
          const_reference operator[](size_type n) const;

而数组索引的 C++14 规范如下:

                reference operator[](size_type n);
constexpr const_reference operator[](size_type n) const;

constexpr 已添加到 C++14 的 const 重载中。

更新

C++17 的数组索引规范如下:

constexpr       reference operator[](size_type n);
constexpr const_reference operator[](size_type n) const;

循环现在完成。 Universe 可以在编译时计算。 ;-)

【讨论】:

  • 没有constconstexpr的含义由C++11改为14。
  • 因此,operator[]std::array 中的非constness 即constexpr 在C++11 中没有影响。它是在 C++14 中添加的,因为含义的变化,以及在constexpr 表达式中改变数据的能力意味着非const constexpr 现在意味着它不是constconstexpr
  • @Yakk:所以我的澄清是试图澄清来自 C++11 的索引运算符没有用constexpr 修饰。 constexpr 仅添加到 C++14 中的 const 重载中。 constexpr 的 C++14 语言更改在这里没有影响。
  • 奇怪这是如何实现的,因为“constexpr”函数不能对参数执行“static_assert”。
  • 我也相信这仅适用于编译时评估的数组,对吗?
【解决方案2】:

如果您在编译时知道数组索引,则可以将std::get 与索引一起使用,如果超出范围,导致编译失败

std::array<int, 4> a{{1,2,3,4}};
std::get<4>(a); // out of bounds, fails to compile

我从 gcc-4.9 得到的错误以:

结尾
error: static assertion failed: index is out of bounds
       static_assert(_Int < _Nm, "index is out of bounds");

std::get 仅适用于常量表达式索引(索引是模板参数),因此使用std::array 它始终可以在编译时检测到越界。

【讨论】:

    【解决方案3】:

    std::array 上的数组访问与常规 C 数组相同,它从不检查索引是否有效,如果超出范围,它只会调用 UB。如果您需要限制,请使用std::array::at(),它会为超出数组边界的值引发std::out_of_range() 异常。

    arr.at(EvaluateSpecialArrayIndex(4)); // terminate called after throwing
                                          // an instance of 'std::out_of_range'
    

    如果您想要编译时错误,请使用 std::get:

    std::get<EvaluateSpecialArrayIndex(4)>(arr); // error: static_assert failed
                                                 // "index is out of bounds"
    

    【讨论】:

    • @MattMcNabb 在 C++14 中有一个 constexpr 版本的 at(),但也许他没有使用那个标准。
    • @Matt std::getstd::array 过载。那是编译时。请参阅 Ryan 的回答。
    • @MattMcNabb 是的,你是对的。 std::get 是另一种选择。
    • 如果它是一个'constexpr',仍然会在运行时抛出错误。使用模板非常好 - 但我也想将左值作为索引。
    • 据我所知,如果arr 是静态或自动持续时间的数组,arr+x-arrconstexpr(等于x)如果xconstexpr 和@ 987654339@ 不大于arr 的长度(元素数)。如果x超过arr的长度,会不会被认为不是constexpr
    【解决方案4】:

    简单的答案,因为 std::array 有单独的 constexpr 重载来检查这类事情会非常昂贵。如果您愿意,您可以围绕std::array 编写自己的包装器,它为编译时常量提供了经过编译检查的访问。比如:

    template<typename T, size_t S>
    class safeArray
    {
        std::array<T, S> m_arr;
        public:
        template<size_t A>
        T& safeAt() { 
            static_assert(A < S, "Index out of bounds");
            return m_arr[A]; 
        }
        // other funcs as needed
    };
    

    然后,您可以执行以下操作:

    safeArray<int, 5> arr;
    cout << arr.safeAt<98>() << endl; // compile error
    

    但是,这可以生成很多函数。大多数情况下,如果您的设计是合理的,那么您将永远不需要这种类型的检查。

    【讨论】:

    • 如果你在一个依赖范围内有safeArray&lt;T, 5&gt; arr,你需要将它称为arr.template safeAt&lt;98&gt;() fyi。这就是为什么std::get 是一个免费功能
    猜你喜欢
    • 2014-09-13
    • 2015-02-19
    • 1970-01-01
    • 2016-11-19
    • 1970-01-01
    • 1970-01-01
    • 2021-02-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多