如果自动化工具(如电子围栏或valgrind)无法解决问题,并且仔细查看代码以尝试确定可能出错的位置也没有帮助,而禁用/启用各种操作(直到您在先前执行或未执行的操作与堆损坏存在相关性)以缩小范围也不起作用,则可以尝试这种技术,它试图尽早发现损坏,以便更容易追踪源头:
创建自己的自定义 new 和 delete 运算符,将损坏明显的警戒区域放置在分配的内存区域周围,类似于以下内容:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <new>
static int GUARD_BAND_SIZE_BYTES = 64;
static void * MyCustomAlloc(size_t userNumBytes)
{
char * buf = (char *) malloc(GUARD_BAND_SIZE_BYTES+sizeof(userNumBytes)+userNumBytes+GUARD_BAND_SIZE_BYTES);
if (buf)
{
char * w = buf;
memset(w, 'B', GUARD_BAND_SIZE_BYTES); w += GUARD_BAND_SIZE_BYTES;
memcpy(w, &userNumBytes, sizeof(userNumBytes)); w += sizeof(userNumBytes);
char * userRetVal = w; w += userNumBytes;
memset(w, 'E', GUARD_BAND_SIZE_BYTES); w += GUARD_BAND_SIZE_BYTES;
return userRetVal;
}
else throw std::bad_alloc();
}
static void MyCustomDelete(void * p)
{
if (p == NULL) return;
char * internalCP = ((char *) p)-(GUARD_BAND_SIZE_BYTES+sizeof(size_t));
char * cp = internalCP;
for (int i=0; i<GUARD_BAND_SIZE_BYTES; i++)
{
if (*cp++ != 'B')
{
printf("CORRUPTION DETECTED at BEGIN GUARD BAND POSITION %i of allocation %p\n", i, p);
abort();
}
}
size_t userNumBytes = *((const size_t *)cp);
cp += sizeof(userNumBytes);
cp += userNumBytes;
for (int i=0; i<GUARD_BAND_SIZE_BYTES; i++)
{
if (*cp++ != 'E')
{
printf("CORRUPTION DETECTED at END GUARD BAND POSITION %i of allocation %p\n", i, p);
abort();
}
}
free(internalCP);
}
void * operator new(size_t s) throw(std::bad_alloc) {return MyCustomAlloc(s);}
void * operator new[](size_t s) throw(std::bad_alloc) {return MyCustomAlloc(s);}
void operator delete(void * p) throw() {MyCustomDelete(p);}
void operator delete[](void * p) throw() {MyCustomDelete(p);}
以上方法足以实现类似Electric-Fence的功能,即如果有任何东西向新/删除内存分配的开头或末尾的两个64字节的“警戒带”中写入,那么当删除该分配时,MyCustomDelete()将注意到损坏并使程序崩溃。
如果这还不够好(例如,由于在删除发生时,自损坏之后已经发生了许多事情,很难确定是什么导致了自损坏),则可以通过使MyCustomAlloc()将分配的缓冲区添加到全局双向链表中,并使MyCustomDelete()从同一列表中删除它来更进一步(如果您的程序是多线程的,请确保串行化这些操作!)。这样做的优点是,您可以添加另一个名为CheckForHeapCorruption()的函数,该函数将遍历该链表并检查链接列表中每个分配的警戒带,如果其中任何一个分配已被破坏,则报告。然后,您可以在代码中分散呼叫CheckForHeapCorruption(),因此当堆损坏发生时,它将在下一次调用CheckForHeapCorruption()时被检测到,而不是在以后的某个时间点上。最终,您会发现,一个CheckForHeapCorruption()的呼叫通过了测试,接着就是下一个CheckForHeapCorruption()的呼叫,仅相隔几行代码,检测到了损坏,在这一点上,您就知道是哪段代码导致了损坏,并且可以研究特定的代码以找出它做错了什么,或者根据需要向该代码添加更多的CheckForHeapCorruption()呼叫。
重复以上步骤,直至bug清晰明了。祝你好运!
memset
函数时,为什么要写成256 + 1
,即使你只分配了256
字节的空间? - T.Z