堆内存损坏

问题描述:

int main() 
{ 
    char *p = new char[100]; 
    strcpy(p, "Test"); 
    cout << "Before heap corruption: " << p << endl; 
    p[150] = '\0'; 

    cout << "after heap corruption: " << p; 

    delete p; 

    getchar(); 
    return 0; 
} 

在上面提到的代码上面,我正在写一个内存位置'\ 0'不是我的,即使没有例外抛出。 如果上面的代码运行注释delete p,则不会引发任何怀疑。但如果未注释,则会抛出附加的异常。因此,它是删除验证内存所有权。所以,我可能知道如何eaxctly删除的作品,为什么有nosuch验证而写出的内存块堆内存损坏

enter image description here

+3

'delete p;'应该是'delete [] p;' –

+1

简单的未定义行为。它可能会崩溃,无所事事,或者两者之间的任何事情,仍然坚持标准。 –

+1

没有验证你的代码。你只是在写无效的记忆。在一些快乐的情况下,电脑可能会发现并给你一个警告。 – Totonga

即时您使用未定义行为的,全盘皆输。

它可以很好地工作,立即失败或者在两周后以某种模糊的方式失败。它甚至可以根据需要格式化硬盘驱动器,并通过声卡嘲笑你:-)

在分配或释放内存之前,它不会失败的原因是因为这是进行检查的理想时间,因为这是内存分配函数将拆分或合并块以提供内存或收回内存的时间。

您经常会看到诸如Memory arena corrupted之类的消息,因为在尝试操纵竞技场时,内存分配代码已注意到结构已损坏(校验和和标记值可能不是预期的)。

为了赶上这样的:

p[150] = '\0'; 

通常会采取相当多的运行时间开销的,东西是完全不必要的,如果遵守规则。

如果您使用C++集合(如vector)而不是直阵列,但以原始性能为代价,则可以运行时保护。

这是未定义的行为。如果你访问内存你不应该,那么任何事情都可能发生。没有必要确认你没有做到这一点;这取决于你编写你的程序,否则它不会。

如果你想运行时间验证,就需要更高层次的抽象,而不是原始数组和指针:

std::vector<char> p(100); 
p.at(150) = 0;    // out of bounds, throws exception 

C++使用的“未定义行为”的概念,让编译器/操作系统决定什么他们想要做的。 C++不会抛出异常(或者很好地退出程序),因为执行所有这些操作都需要在代码中进行额外的检查。在C++中,你不会得到你不支付的东西(即C++关心性能)。

未定义的行为就是这样:任何事情都可能发生,当然有一些概率。访问不属于你的内存就是这样。

检查非常数组访问都需要与每一个索引操作至少两个额外的指令 - 如果你想赶上s++ = 0;,其中s是一个指针到一些阵列(更多我们不仅需要跟踪的大小,又哪里指针区域开始),如果数据也是动态分配的(甚至更多)(因为现在我们需要跟踪原始分配的位置以及它的大小)。我在Pascal编译器中进行了数组边界检查,它增加了大约15%的开销 - 在某些情况下为300%,有些情况下为5-10%。但它只适用于固定大小的数组,而不是由于上述问题而动态分配内存。 5-15%对于大多数代码来说并不是一个大问题。 300%的情况是一个问题 - 如果它支持动态分配的内存,情况会更糟!

以上是我们知道内存“来自哪里”的简单情况。如果你有一个函数需要一个指向某个东西的指针,那么每个指针都需要额外的存储空间来安排存储指针内存大小的地方,并且每次访问内存时都必须读取内存,比较并且必须添加分支指令。通常,指针访问只是一条指令,所以我们现在至少增加了三条指令(和一个从不是好东西的分支)。当然,这些数据必须在使用之前填写 - 理想情况下不会破坏人们对内存中数据布局的想法...

这就是为什么运行代码与valgrind和类似的工具比运行“全速”慢10倍左右。

向存储器分配添加一点“填充”(又名“crumble-zone”),并在delete时检查“填充”是否仍然完好无损,因此在大多数情况下是首选方法情况 - 它本身只有几个百分点的慢,并且捕捉到“你的代码没有像你期望的那样行事”,即使它没有立即捕捉到它。

随机将零置入内存位置不会产生可重复的行为,并且可能会或可能不会触发旨在帮助您避免麻烦的内存保护方法之一。

因此,验证内存所有权是删除。那么,我可以知道eaxctly删除工作如何

它取决于编译器的标准库实现。例如,在调试模式下,MSVC会在前后用已知模式填充堆块,以便检测超限。您不会在发布模式下找到它,因为它会降低性能。

为什么会有nosuch验证而写出的内存块周围的每一个内存访问插入边界检查的

会痛苦地昂贵。这样做的代码工具工具确实存在。我记得在90年代使用Bounds Checker,我的良善很痛苦,但有时候是找到一个难题的最方便的方法。

也就是说,CPU通过其集成MMU执行一定数量的access checks。如果你访问的内存没有被分配给你的进程,那么它将被困住。同样,如果您尝试使用与应该不匹配的模式尝试access a page,那么它将被困住。