C++: Vector bounds

C++: Vector bounds

本文关键字:bounds Vector C++      更新时间:2023-10-16

我来自Java,目前正在学习c++。我正在使用Stroustrup的编程原理和使用c++的实践。我现在正在研究向量。在第117页,他说访问vector中不存在的元素会导致运行时错误(在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。更有趣的是,如果我不修改不存在的vector元素(我只是打印它,预计会出现运行时错误或至少是默认值),它会打印一些大整数。所以…是Stroustrup错了,还是GCC有一些奇怪的编译c++的方式?

这本书有点模糊。与其说是"运行时错误",不如说是未定义行为在运行时表现出来。这意味着任何事情都可能发生。但是,错误严格地与有关,而与程序执行无关,实际上,甚至谈论具有未定义行为的程序的执行都是不可能的,也是不明智的。

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()版本是尴尬的。最好是检查并提供反馈。因为执行了动态检查,所以实际上可以保证未检查的vector访问是正确的。

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];
}

(我们忽略了vector构造可能抛出自身异常的可能性)

寓意是c++中的许多操作都是不安全的,并且只有在条件下才正确,但是程序员应该提前进行必要的检查。语言不会为你做这些,所以你不用为它付钱,但你必须记得去做。其思想是,无论如何您都需要处理错误条件,因此,与其在库或语言级别强制执行昂贵的非特定操作,不如将责任留给程序员,他们处于更好的位置,可以将检查集成到需要编写的代码中。

如果我想开玩笑,我会将这种方法与Python进行对比,Python允许您编写令人难以置信的正确的程序,而根本不需要任何用户编写的错误处理。另一方面,任何使用这种程序的尝试,只要与程序员的意图有轻微的偏差,就会给您留下一个非特定的、难以阅读的异常和堆栈跟踪,并且几乎没有指导您应该做得更好。您不会被迫编写任何错误处理,而且通常最终不会编写任何错误处理。(我不能将c++与Java进行比较,因为Java通常是安全的,但我还没有看到一个短的 Java程序)

这是@Evgeny Sergeev的一个有价值的评论,我将其推广到答案:

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

我想补充一点,也可以通过使用gnu_debug::命名空间前缀而不是std::.

来捆绑单独的"安全"版本的vector和其他实用程序类。

换句话说,不要重新发明轮子,数组检查至少在GCC中是可用的。

C和c++并不总是做边界检查。它可能导致运行时错误。如果你把你的数字过度,比如10000左右,几乎肯定会引起问题。

你也可以使用vector.at(10),这肯定会给你一个异常。看到的:http://www.cplusplus.com/reference/vector/vector/at/相比:http://www.cplusplus.com/reference/vector/vector/operator%5B%5D/

我希望vector的"operator[]"会像"at()"那样检查边界,因为我不太小心。: -)

一种方法是继承vector类并覆盖操作符[]来调用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