2014-01-24 29 views
3

不久之前,我在编写一个大数据库的时候正在寻找一个bug,它耗费了我很长时间。问题在于我违反了某些结构成员的内存边界,而不是segmentation fault或者只是一个普通的崩溃,它做了一些意想不到的事情(至少我没有想到它)。让我介绍一个例子:内存分配违规后free()的奇怪行为

segmentation_fault.c

#include <stdlib.h> 
#include <stdio.h> 
#include <errno.h> 
#include <signal.h> 

#define N 100 /* arbitrary large number */ 

typedef unsigned char byte; 

void exitError(char *); 
void segmentationFaultSignalHandler(int); 

sig_atomic_t segmentationFaultFlag = 0; 


int main(void) 
{ 
    int i, memorySize = 0; 
    byte *memory; 
    if (setvbuf(stdout, NULL, _IONBF, 0)) 
     exitError("setvbuf() failed"); 
    if (signal(SIGSEGV, segmentationFaultSignalHandler) == SIG_ERR) 
     exitError("signal() failed"); 
    for (i = 0; i < N; ++i) 
    { 
     printf("Before malloc()\n"); 
     if ((memory = malloc(++memorySize * sizeof(byte))) == NULL) 
      exitError("allocation failed"); 
     printf("After malloc()\n"); 
     printf("Before segmentation fault\n"); 
     memory[memorySize] = 0x0D; /* segmentation fault */ 
     if (segmentationFaultFlag) 
      exitError("detected segmentation fault"); 
     printf("After segmentation fault\n"); 
     printf("Before free()\n"); 
     free(memory); 
     printf("After free()\n"); 
    } 
    return 0; 
} 


void segmentationFaultSignalHandler(int signal) 
{ 
    segmentationFaultFlag = 1; 
} 


void exitError(char *errorMessage) 
{ 
    printf("ERROR: %s, errno=%d.\n", errorMessage, errno); 
    exit(1); 
} 

正如我们所看到的线memory[memorySize] = 0x0D;显然是违反了由malloc()给出的内存边界,但它不会崩溃或提高的信号(我根据ISO C99/ISO C11了解,信号处理是实现定义的,并且在违反内存边界时根本不需要加任何处理)。它在打印行After segmentation fault,Before free()After free()上移动,但经过几次迭代后崩溃,始终在free()(打印After segmentation faultBefore free(),但不是After free())。我想知道是什么导致了这种行为,以及什么是检测内存访问违规的最佳方式(我很惭愧,但我总是有点用printf来确定程序崩溃的位置,但确定必须有更好的工具来做到这一点)它很难被检测到(大多数情况下,它不会在违规代码中崩溃,但是,例如,在后面的代码中,当试图再次使用该内存时)。当然,我应该能够释放这个内存,因为我正确地分配它并且不修改指针。

+0

这是未定义的行为。顾名思义,“未定义行为”意味着任何事情都可能发生,没有崩溃保证,只是......任何事情。 – SirDarius

+2

根据定义,未定义的行为是未定义的。它可以导致崩溃(分段错误(或其他)),它可以*看起来很好,或者它可能在一个完全不相关的地方崩溃,甚至导致[鼻恶魔](http://www.catb.org/)。行话/ HTML/N /鼻demons.html)。 –

+0

它可能导致'free'问题的原因是某些分配算法(特别是在调试模式下)将数据存储在分配的内存之外。 –

回答

2

在内存中阅读或写作时,您并不拥有自己的未定义行为。

这并不总是导致分段错误。在实践中,代码很可能会破坏一些其他数据,并且您的程序会在其他地方崩溃,从而导致难以调试。

在这个例子中,你写了一个无效的堆地址。很可能您会损坏一些内部堆结构,这可能导致程序在任何以下malloc或免费调用中崩溃。

有一些工具可以检查堆的使用情况,并可以告诉你是否写出了界限。我喜欢并会推荐valgrind for linux和gflags for windows。

+1

只是为了精确。写入完全随机的地址更可能触及未定义和未分配的内存。如果您比较正在运行的程序的“正常”大小为几兆字节与可寻址空间的大小为2^32(或2^64),触摸代码或数据的概率约为1:1000(或1:2^40)。 – Marian

3

您只能检测伪造环境中的违规行为。 在这种情况下,你违反了你从系统中获得的记忆,你不能相信任何事情。因为现在发生的所有事情都是未定义的行为,并且你不能指望会发生什么,因为没有任何规则。

所以,如果你想检查一个程序的内存不足或一些读/写违规。你必须编写一个程序来获取属于它的存储区域,并将该区域的一部分给予“待检查”程序。你必须检查过程并跟踪它在哪里写入并读入我们的内存中,并且必须使用内存的其他部分来检查它是否允许读取写入或不读取(即通过设置在您的假冒环境中一些标志和检查他们是否改变了)。

因为如果程序正在离开你所拥有的区域。你不能确定你会发现这种行为与否。 所以你必须让自己的记忆管理来检查这样的行为。

2

当malloc返回指向内存块的指针时,它会使用关于此指针的一些附加信息(如分配空间的大小)。这些信息通常存储在返回的指针之前的地址上。 malloc也经常会返回一个指向比你所要求更大的块的指针。在指针有效之前和之后的结果地址中。您可以在那里写入,而不会引发分段错误或其他系统错误。但是,如果您在那里编写,则可能会覆盖正确释放内存所需的数据malloc需求。自此以后,malloc和free的后续调用的行为是不确定的。