Posted by: billconan | September 19, 2006

堆疵及其成因

Heap Corruption应该翻译成什么好呢,翻了一下百度,没有答案,所以这里自作主张地翻译成“堆疵”。
前段时间编程的时候遇到过一次堆疵的情况,崩溃的地方是调用“new”的时候。当时觉得很奇怪,因为似乎只有内存消耗殆尽的时候new才会疵,但是我的程序没有用多少内存,怎么会疵能。后来知道是堆疵造成的。堆疵最麻烦的就是出现错误之后并不马上导致程序崩溃,而是埋下个隐患,等到再次使用出现问题的内存的时候才出现错误。这样经常会让人感到莫名其妙,因为出错的位置怎么看都是对的。好在vs2005里面可以报告出疵内存的地址,然后单步执行观察那片内存区域,这样就可以把疵点找到了。
当时在ogre3d的wiki上找到的文章讲堆疵,挺好的,翻译如下:

啥是堆?
堆是一些内存区域,这些内存区域是操作系统(比如windows)为一个应用程序保留的。堆是存放你程序所使用的数据的地方。
例如:

int main()
{
//下面这一行会分配到 2 * 4 bytes 的内存 (int 占用 4 bytes)
int a, b;
}

啥是堆疵?
出现堆疵就意味着你程序中的某个语句覆盖了堆中的一些数据。如果程序中其他的语句想接触这个数据,会造成坏的数据。
这会造成很多奇怪的错误,甚至无法解释。如果出现了堆疵,它在默认情况下会使得new操作返回NULL。
所以说,如果你出现了一些很奇怪的问题,很可能就是出现了堆疵。这个主要发生在release模式下,虽然在debug模式下程序很可能很正常。出现问题的几个可能的原因如下。

堆疵的常见原因:

悬挂指针
悬挂指针指的是这个指针所指向的实体已经被释放了。如果这时候你要使用这个指针,会产生错误。例如:

class A
{
String mName;

void A(String name) : mName(name)
{
}

void speak()
{
printf(“My name is: %s\n”, mName.c_str());
}
}

A *pointer1, *pointer2;

pointer1 = new A(“Michael”);
pointer2 = pointer1;
//现在两个指针同时指向一个object (class A)

pointer1->speak(); // this will work
pointer2->speak(); // this too

delete pointer1;
//现在删除了object, 但是两个指针仍然指向它们原先指向的内存地址

pointer1->speak(); // 这会产生错误
pointer2->speak(); // 这个也会出现错误

重复地释放
如果你释放一个指针所指向区域两次(=如果你释放一个悬挂指针),你同样会遇到奇怪的错误。这里我想引用wumpus的话:”在这种情况下,有趣的事情会更多”。例如:

A* pointer = new A(“Dave”);
pointer->speak();
delete pointer;

B* pointerB = new B(20);
pointerB->add(50);
pointerB->printResult();
delete pointer; //这个可能只是个笔误

//现在就要发生错误了… 找错误一定很有意思…

数组越界
如果你设定一个数组元素的下标超过了数组的大小,你就会改写在堆中数组之后区域的内存。比如:

int numbers[3];
A* pointer = new A(“Hansi”);
B* pointerB = new B(4);

numbers[0] = 23;
numbers[1] = 42;
numbers[2] = 5;
numbers[3] = 666; //数组越界

//现在你应经把两个指针所指向的位置给覆盖了
//在32位机上,一个指针变量的大小是 4 bytes (unsigned int) 在64位机上是 8 bytes (unsigned long)
//所以覆盖第一个指针(如果是32位系统)。会产生错误:
pointerB->add(16);
pointerB->printResult(); //这个会显示”20″
delete pointerB; //到这里还没有问题

// 这里会有数以亿计的代码… 都没什么问题,除非你要这样做:
A->speak(); //这个会疵掉,除非下面这个成立:
//第三行(int)pointer == 666;

其它在release模式下出现奇怪错误的解释

没有初始化的指针
在release模式下,变量不会自动地初始化,所以如果你没有主动地指定一个指针变量是空的话,它可能是一个随机数。如果你使用这个指针,就会产生错误:

A* pointer;

if (pointer == NULL)
pointer = new A(“John”);

pointer->speak(); //这个在release模式下可能不行,但是在debug下没问题
delete pointer;

int x;
printf(“%i”, ++x); //输出一个随机的数目

为啥写这个文章?
我在网上找了3天。我想知道我的小地图在release模式下出现”assertion failed”的原因。问题出现在一个颜色数组(ColourValue mColors[5]),我只需要5个颜色,但是我往里面放了11个进去…
希望这个能帮你加速找错的过程。好运!

我为啥翻译这个文章?
我的程序堆疵之后我摸不到头脑,索性甩手不干了。后来还是放不下心,玩不进去游戏,看不进去电视。在网上搜索了很多堆疵的资料,大多罗列了上面几种原因,当时我看的时候觉得这些原因都太傻了,我的程序已经反复看了很多遍,不可能因为这么几个弱错误疵掉。后来看了半天内存,发现问题,我在释放了一个指针所指的内存之后又去使用它。

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Categories

%d bloggers like this: