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<>
的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++:向量边界的主要内容,如果未能解决你的问题,请参考以下文章