C++:向量边界

16

我来自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++的方式有些奇怪?


6
使用 v.at(8) 会导致运行时异常。 - JaredC
尝试快速查看std::vector<>数组运算符,它可能会为您的问题提供一些启示,特别是缺少边界检查(成员at()确实会执行)。您的代码目前存在未定义行为。 - WhozCraig
1
@WhozCraigUB 是什么意思?抱歉问这么愚蠢的问题,但我是新手 :( - lekroif
1
@lekroif:“未定义行为”,即任何事情都可能发生,包括它做你期望的事情。 - Benjamin Bannier
4个回答

20
这本书有点模糊。它不是那种明显的“运行时错误”,而是在运行时表现为未定义行为。这意味着任何事情都可能发生。但错误严格来说是由 您引起的,而不是程序执行的问题。实际上,谈论具有未定义行为的程序的执行是不可能和无意义的。

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进行对比,它允许你编写极其简短和正确的程序,完全不需要用户编写任何错误处理。另一方面,任何尝试使用这样的程序,只要稍微偏离程序员的意图,就会导致一个非特定的、难以阅读的异常和堆栈跟踪,并且很少有指导告诉你应该做得更好。你不必强制编写任何错误处理,通常也没有编写错误处理。 (我不能完全将C++与Java进行对比,因为虽然Java通常是安全的,但我还没有看到过一个简短的Java程序。)</rantmode>

2
值得一提的是 std::vector::at 吗? - Joseph Mansfield
1
你的示例使用operator[],根据cplusplus.com的说法,它不进行边界检查,因此try/catch无法起作用。[除非我误解了cplusplus.com的意思!]我确实同意最好的方法是在代码中进行检查-这也有助于您可以在函数开头检查值[例如],然后依赖于我的for循环或类似的工作从0..n,并且我已经检查了n是否在范围内,所以较小的数字应该没问题[当然,检查应该检查n> = 0! - Mats Petersson
3
当然,C++ 之所以如此高效 [如果使用正确],部分原因在于它不会为了“以防万一”而在代码中到处添加大量额外的检查。这是 Bjarne 在创建这种语言时最初的目标——它应该“像 C 一样高效,但像 Simula 一样灵活和面向对象”。 - Mats Petersson

7
这是@Evgeny Sergeev的一条有价值的评论,我将其提升为答案:
对于GCC,您可以使用-D_GLIBCXX_DEBUG来替换标准容器,并使用安全实现。最近,这似乎也适用于std::array。更多信息在此处:gcc.gnu.org/onlinedocs/libstdc++/manual/debug_mode.html。
我想补充一点,通过使用gnu_debug::名称空间前缀而不是std::,也可以捆绑单个“安全”版本的向量和其他实用程序类。
换句话说,不要重复造轮子,GCC至少提供了数组检查。

6

operator[]不会进行边界检查。这一点是正确的,但说实话,除非您尝试访问超出内存限制的真正巨大的索引,否则它永远不会导致运行时错误。 - Moataz Elmasry
如果你在堆栈上运行足够多的程序,它会崩溃。这是一种“运行时错误”。但是,正如其他答案所说,“错误”的定义在这里相当模糊——它肯定“不能可靠地工作”,但实际发生的情况并不清楚(称为“未定义行为”)。 - Mats Petersson
2
未定义行为比运行时错误更糟糕。有时它能正常工作,有时候却无法正常运行... - Pascalius

4

我希望vector的"operator[]"像"at()"一样检查边界,因为我不够仔细。 :-)

一种方法是继承vector类并重载operator[]以调用at(),这样就可以使用更易读的"[]"而无需将所有"[]"替换为"at()"。您还可以将继承的vector(例如: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

5
对于GCC,您可以使用-D_GLIBCXX_DEBUG将标准容器替换为安全实现。最近,这似乎也适用于std::array。更多信息请参见:http://gcc.gnu.org/onlinedocs/libstdc++/manual/debug_mode.html - Evgeni Sergeev
1
谢谢!是的,它在libstdc++上看起来工作正常! 但是,在Mac OS X上默认的C++库libc++上无法工作... - Tsuneo Yoshioka

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接