内存分配违规后free()的奇怪行为

时间:2014-01-24 10:27:47

标签: c memory-management segmentation-fault

不久前,我正在寻找一些我正在编写的大型图书馆中的错误,它花了我很长一段时间。问题是我违反了一些结构成员的内存边界,但是它没有segmentation fault或只是一个简单的崩溃,它做了一些意想不到的事情(至少我没想到)。让我举一个例子:

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 faultBefore free()After free(),但经过几次迭代后,它会崩溃,始终在free()(打印After segmentation fault和{ {1}},但不是Before free())。我想知道导致这种行为的原因是什么,以及检测内存访问违规的最佳方法是什么(我很惭愧,但我总是使用After free()来确定程序崩溃的位置,但是肯定必须有更好的工具来这样做是因为它很难被检测到(通常它不会在违规代码中崩溃,但是,如示例中所示,稍后在代码中,当尝试再次对此内存执行某些操作时)。当然我应该能够释放这个内存,因为我正确地分配它并且没有修改指针。

3 个答案:

答案 0 :(得分:3)

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

因此,如果您想检查程序是否存在内存泄漏或某些读/写违规。你必须编写一个程序来获取属于它的内存区域,并将该区域的一部分提供给“待检查”程序。你必须检查过程并跟踪它写入的位置并读入我们的内存,你必须使用内存的其他部分来检查它是否允许在那里读取写入(即在你的伪造环境中通过设置一些标志并检查它们是否有所改变。)

因为如果程序离开您拥有的区域。你无法确定你是否会发现这种行为。 所以你必须让自己的记忆管理来检查这种行为。

答案 1 :(得分:2)

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

答案 2 :(得分:2)

在内存中读取或写入时,您不会拥有未定义的行为。

这并不总是导致分段错误。在实践中,代码更有可能破坏其他一些数据,并且程序会在其他地方崩溃,这使得调试变得困难。

在此示例中,您写入了无效的堆地址。您可能会损坏一些内部堆结构,这可能导致程序在任何后续malloc或free调用时崩溃。

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