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++ 编译方式?

4

4 回答 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 进行对比,Python 允许您编写非常正确的程序,而根本不需要任何用户编写的错误处理。不利的一面是,任何尝试使用与程序员的意图略有不同的此类程序的任何尝试都会给您留下非特定的、难以阅读的异常和堆栈跟踪,并且几乎没有关于您应该做得更好的指导。您不必编写任何错误处理程序,而且通常最终不会编写任何错误处理程序。(我无法将 C++ 与 Java 完全对比,因为虽然 Java 通常是安全的,但我还没有看到一个简短的Java 程序。)</rantmode>

于 2012-12-23T23:51:53.810 回答
6

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

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

于 2012-12-23T23:53:21.157 回答
6

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

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

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

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

于 2018-02-02T12:20:59.773 回答
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
于 2013-11-29T14:35:31.383 回答