C++:向量边界

Posted

技术标签:

【中文标题】C++:向量边界【英文标题】:C++: Vector bounds 【发布时间】:2012-12-23 23:48:07 【问题描述】:

我来自 Java,目前正在学习 C++。我正在使用 Stroustrup 的编程原则和使用 C++ 的实践。我现在正在使用向量。在第 117 页,他说访问向量的不存在元素将导致运行时错误(在 Java 中也是如此,索引超出范围)。我正在使用 MinGW 编译器,当我编译和运行这段代码时:

#include <iostream>
#include <cstdio>
#include <vector>

int main() 
 
    std::vector<int> v(6);
    v[8] = 10;
    std::cout << v[8];
    return 0;

它给了我输出 10。更有趣的是,如果我不修改不存在的向量元素(我只是打印它期望运行时错误或至少一个默认值),它会打印一些大整数。那么……是 Stroustrup 错了,还是 GCC 有一些奇怪的 C++ 编译方式?

【问题讨论】:

使用v.at(8),你会得到一个运行时异常。 快速浏览一下std::vector&lt;&gt; 的array operator,它可能会对您的问题有所启发,特别是缺少边界检查(at() 的成员 做)。您的代码是 UB 原样。 @WhozCraig UB 是什么意思?抱歉这个愚蠢的问题,但我是新手 :( @lekroif:“未定义的行为”,即任何事情都可能发生,包括它按照您的预期进行。 【参考方案1】:

这本书有点模糊。它不是“运行时错误”,而是在运行时出现的未定义行为。这意味着任何事情都可能发生。但错误只出在,而不是程序执行,事实上,甚至谈论具有未定义行为的程序的执行是不可能和不明智的。

C++ 中没有任何东西可以保护您免受编程错误的影响,这与 Java 完全不同。


正如@sftrabbit 所说,std::vector 有一个替代接口,.at(),它总是给出一个正确的程序(尽管它可能会抛出异常),因此是可以推理的。


让我用一个例子来重复这一点,因为我相信这是 C++ 的一个重要的基本方面。假设我们正在从用户那里读取一个整数:

int read_int()

    std::cout << "Please enter a number: ";
    int n;
    return (std::cin >> n) ? n : 18;

现在考虑以下三个程序:

危险的:这个程序的正确性取决于用户输入!它不一定不正确,但它是不安全(到了我会称之为损坏的地步)。

int main()

    int n = read_int();
    int k = read_int();
    std::vector<int> v(n);
    return v[k];

无条件正确:无论用户输入什么,我们都知道这个程序的行为。

int main() try

    int n = read_int();
    int k = read_int();
    std::vector<int> v(n);
    return v.at(k);

catch (...)

    return 0;

理智的人:上面带有.at()的版本很尴尬。最好检查并提供反馈。因为我们进行了动态检查,所以实际上保证了未检查的向量访问是没问题的。

int main()

    int n = read_int();

    if (n <= 0)  std::cout << "Bad container size!\n"; return 0; 

    int k = read_int();

    if (k < 0 || k >= n)   std::cout << "Bad index!\n"; return 0; 

    std::vector<int> v(n);
    return v[k];

(我们忽略了向量构造可能引发自身异常的可能性。)

C++ 中的许多操作都是不安全的,并且只能在条件下正确,但程序员希望您提前进行必要的检查。语言不会为你做这件事,所以你不需要为此付费,但你必须记住去做。这个想法是无论如何你都需要处理错误情况,因此与其在库或语言级别执行昂贵的、非特定的操作,责任留给了程序员,他们可以更好地集成检查进入无论如何都需要编写的代码。

如果我想变得有趣,我会将这种方法与 Python 进行对比,Python 允许您编写非常short正确 程序,而无需任何用户编写的错误处理一点也不。不利的一面是,任何使用这种程序的尝试都只会略微偏离程序员的意图,这会给您留下非特定的、难以阅读的异常和堆栈跟踪,并且几乎没有关于什么的指导你应该做得更好。您不必编写任何错误处理程序,而且通常最终不会编写任何错误处理程序。 (我无法将 C++ 与 Java 完全对比,因为虽然 Java 通常安全,但我还没有看到一个 简短 Java 程序。)

【讨论】:

值得一提std::vector::at? 您的示例使用 operator[] 根据 cplusplus.com 的说法不是边界检查,因此 try/catch 将不起作用。 [除非我读错了 cplusplus.com!] 我同意最好的办法是检查你的代码——这在某种意义上也有帮助,你可以在函数的开头检查一个值 [例如]然后依靠我的 for 循环或类似的工作从 0 .. n 开始的事实,并且我已经检查过 n 在范围内,所以较低的数字应该没问题 [检查当然应该检查 n >= 0!跨度> 当然,C++ 之所以如此高效[如果使用正确] 的部分原因在于它不会“以防万一”到处进行大量额外检查。这是 Bjarne 创建语言时的最初目标——它应该“像 C 一样高效,但像 Simula 一样灵活和面向对象”。【参考方案2】:

C 和 C++ 并不总是进行边界检查。它可能会导致运行时错误。而且,如果您将数字超出了足够多,例如 10000 左右,那么几乎肯定会导致问题。

你也可以使用vector.at(10),它肯定会给你一个例外。 看: http://www.cplusplus.com/reference/vector/vector/at/ 和....相比: http://www.cplusplus.com/reference/vector/vector/operator%5B%5D/

【讨论】:

operator[] 不做边界检查。这一点是正确的,但老实说,我认为它不会导致运行时错误,除非您尝试访问一个非常大的超出内存限制的索引 如果你跑出堆栈足够长,你会崩溃程序。这是一种“运行时错误”。但是,是的,正如另一个答案所说,这里“错误”的定义非常模糊 - 它肯定是“不能可靠地工作”,但实际发生的情况尚不清楚(称为“未定义行为”) 未定义的行为比运行时错误更糟糕。有时有效,有时无效...【参考方案3】:

这是@Evgeny Sergeev 的宝贵评论,我将其推广为答案:

对于 GCC,您可以 -D_GLIBCXX_DEBUG 将标准容器替换为安全实现。最近,这似乎也适用于 std::array。更多信息在这里:gcc.gnu.org/onlinedocs/libstdc++/manual/debug_mode.html

我要补充一点,也可以使用 gnu_debug:: 命名空间前缀而不是 std:: 来捆绑单独的“安全”版本的 vector 和其他实用程序类。

换句话说,不要重新发明***,数组检查至少可以使用 GCC。

【讨论】:

【参考方案4】:

我希望vector的“operator[]”会像“at()”那样检查边界,因为我没有那么小心。 :-)

一种方法是继承向量类并覆盖 operator[] 来调用 at(),这样就可以使用更具可读性的“[]”,而无需将所有“[]”替换为“at()”。您还可以将继承的向量(例如:safer_vector)定义为法线向量。 代码会是这样的(在 C++11 中,Xcode 5 的 llvm3.5)。

#include <vector>

using namespace std;

template <class _Tp, class _Allocator = allocator<_Tp> >
class safer_vector:public vector<_Tp, _Allocator>
private:
    typedef __vector_base<_Tp, _Allocator>           __base;
public:
    typedef _Tp                                      value_type;
    typedef _Allocator                               allocator_type;
    typedef typename __base::reference               reference;
    typedef typename __base::const_reference         const_reference;
    typedef typename __base::size_type               size_type;
public:

    reference operator[](size_type __n)
        return this->at(__n);
    ;

    safer_vector(_Tp val):vector<_Tp, _Allocator>(val);;
    safer_vector(_Tp val, const_reference __x):vector<_Tp, _Allocator>(val,__x);;
    safer_vector(initializer_list<value_type> __il):vector<_Tp, _Allocator>(__il);
    template <class _Iterator>
    safer_vector(_Iterator __first, _Iterator __last):vector<_Tp,_Allocator>(__first, __last);;
    // If C++11 Constructor inheritence is supported
    // using vector<_Tp, _Allocator>::vector;
;
#define safer_vector vector

【讨论】:

对于 GCC,您可以-D_GLIBCXX_DEBUG 将标准容器替换为安全实现。最近,这似乎也适用于std::array。更多信息在这里:gcc.gnu.org/onlinedocs/libstdc++/manual/debug_mode.html 谢谢!是的,它看起来在 libstdc++ 上运行良好!虽然,它不适用于 Mac OS X 上的默认 C++ 库 libc++...

以上是关于C++:向量边界的主要内容,如果未能解决你的问题,请参考以下文章

转换向量索引超出数组边界 - matlab

如何在gmsh中提取边界节点处的法线向量?

支持向量分类器的决策边界图(与分离超平面的距离)

支持向量机实例

在 ARM NEON 中的数组边界上加载向量

从支持向量机返回最佳支持向量