克服 C++ 中的错误内存分配
Posted
技术标签:
【中文标题】克服 C++ 中的错误内存分配【英文标题】:Overcoming wrong memory allocation in C++ 【发布时间】:2009-06-11 11:49:07 【问题描述】:在我编写的 C++ 程序中:
#include<iostream>
#include<vector>
using namespace std;
int main()
vector<int> a;
a.resize(1);
for( int i = 0 ; i < 10 ; i++ )
cout << a[i] << " ";
return 0;
此程序打印 a[0] 的正确值(因为它已分配),但也打印其余 10 个位置的值,而不是给出分段错误。
在编写代码时如何克服这个问题?当您的代码计算某些东西并愉快地访问不打算访问的内存时,这会导致问题。
【问题讨论】:
这里实际上并没有打印这些值 - 编译器在实现未定义行为方面有所不同。 我用的是 GNU 编译器.....你用的是哪一个 【参考方案1】:使用这个:
a.at(i)
如果索引超出范围,at()
将抛出 out_of_range
异常。
operator []
不做边界检查的原因是效率。您可能希望养成使用at()
来索引向量的习惯,除非您有充分的理由在特定情况下不这样做。
【讨论】:
这是一种解决错误编写代码的方法。您可以改为正确编写代码 - 使用 size() 或迭代器。 当然,在理想的世界中,我们总是会正确编写代码——而且我个人认为我根本没有使用过向量索引运算符。但是,在非性能关键代码(可能是大多数地方)中使用at()
是一个很好的“安全带和大括号”/“防御性编码”的事情,特别是对于有些缺乏经验的编码人员。
不同意。应该审查没有经验的编码人员,而不是放在填充单元格中。【参考方案2】:
当您调用 resize() 时,向量实现会将缓冲区重新分配到 足够 的大小以存储请求的元素数量 - 它不能小于您的要求,但实现是免费的它更大以减少内存碎片并减少重新分配的频率。
避免此类错误的唯一方法是仅在代码的有效索引范围内循环。对于您提供的代码,如下所示:
for ( int i = 0 ; i < a.size(); i++ )
cout << a[i] << " ";
【讨论】:
【参考方案3】:使用迭代器可以完全避免这个问题。然后 for 循环看起来像
for(vector<int>::iterator i = a.begin(); i != a.end(); ++i)
cout << *i << " ";
【讨论】:
【参考方案4】:这本身不是内存分配问题,而是边界检查问题。如果您超出的内存(读取或写入)仍在程序的合法范围内,则不会出现段错误。
在过去,我见过一个重载的 [ 进行边界检查的运算符。将 C++ 转换为 ForTran(我可能会补充说,这是 ForTran 的一个更好的特性)需要做很多工作。
除了使用向量和迭代器,最好的答案是使用好的编程技术。
【讨论】:
【参考方案5】:检查您为向量分配的大小
【讨论】:
【参考方案6】:通过在访问内存时更加认真来解决这个问题:检查边界!
【讨论】:
【参考方案7】:我还不能评论,但是 resize() 不是内存分配的提示。 根据STL documentation,resize(n) 在向量末尾插入或删除元素。所以在调用 resize(1) 之后,向量正好包含 1 个元素。 要提前分配内存,必须调用reserve(n)。
【讨论】:
【参考方案8】:发生分段错误是因为硬件(内存管理单元)重新确认您无权访问该区域,因此它会引发异常。操作系统收到该异常并决定如何处理它;在这些情况下,它会意识到您正在进行非法访问,并因分段错误而终止您的应用程序。
相同的机制是如何实现交换;操作系统可能会重新确认您确实可以访问内存,但它现在在磁盘上。然后它会从磁盘中调入内存并允许您的程序继续运行。
但是,整个内存保护方案仅对内存页面具有足够的分辨率,例如一次 4k。所以 MMU 不能保护你免受你可能做的每一个小超支。有诸如 ElectricFences 之类的工具可以替代 malloc 和 free 并利用 MMU,但这些工具仅用于“抽查”……它们非常适合调试,但您不想永远那样运行。
【讨论】:
【参考方案9】:访问分配对象边界之外的元素会导致未定义的行为。这意味着实现对它发生的任何事情都是免费的。如果你很幸运,它可能会抛出异常。如果你很不走运,它只会看起来有效。
原则上,它可以让恶魔从你的鼻子里飞出来。
它的行为是未定义。
【讨论】:
以上是关于克服 C++ 中的错误内存分配的主要内容,如果未能解决你的问题,请参考以下文章
C# 中锯齿状数组的内存分配与 C++ 中的二维数组内存分配