不久前,我正在寻找一些我正在编写的大型图书馆中的错误,它花了我很长一段时间。问题是我违反了一些结构成员的内存边界,但是它没有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 fault
,Before free()
和After free()
,但经过几次迭代后,它会崩溃,始终在free()
(打印After segmentation fault
和{ {1}},但不是Before free()
)。我想知道导致这种行为的原因是什么,以及检测内存访问违规的最佳方法是什么(我很惭愧,但我总是使用After free()
来确定程序崩溃的位置,但是肯定必须有更好的工具来这样做是因为它很难被检测到(通常它不会在违规代码中崩溃,但是,如示例中所示,稍后在代码中,当尝试再次对此内存执行某些操作时)。当然我应该能够释放这个内存,因为我正确地分配它并且没有修改指针。
答案 0 :(得分:3)
您只能检测伪造环境中的违规行为。 在这种情况下,你违反了你从系统中获得的记忆,你再也不能相信了。因为现在发生的一切都是未定义的行为,你不能指望会发生什么,因为没有任何规则。
因此,如果您想检查程序是否存在内存泄漏或某些读/写违规。你必须编写一个程序来获取属于它的内存区域,并将该区域的一部分提供给“待检查”程序。你必须检查过程并跟踪它写入的位置并读入我们的内存,你必须使用内存的其他部分来检查它是否允许在那里读取写入(即在你的伪造环境中通过设置一些标志并检查它们是否有所改变。)
因为如果程序离开您拥有的区域。你无法确定你是否会发现这种行为。 所以你必须让自己的记忆管理来检查这种行为。
答案 1 :(得分:2)
当malloc返回指向一块内存的指针时,它会使用一些有关此指针的附加信息(如分配空间的大小)。此信息通常存储在返回指针之前的地址上。同样经常,malloc可以返回指向比您要求的更大块的指针。因此,指针前后的地址有效。你可以在那里写,而不会引起分段错误或其他系统错误。但是,如果你在那里写,你可能会覆盖数据malloc需要正确释放内存。从那时起,后续调用malloc和free的行为是不确定的。
答案 2 :(得分:2)
在内存中读取或写入时,您不会拥有未定义的行为。
这并不总是导致分段错误。在实践中,代码更有可能破坏其他一些数据,并且程序会在其他地方崩溃,这使得调试变得困难。
在此示例中,您写入了无效的堆地址。您可能会损坏一些内部堆结构,这可能导致程序在任何后续malloc或free调用时崩溃。
有些工具会检查您的堆使用情况,并且可以告诉您是否写出了边界。我喜欢并推荐使用valgrind for linux和gflags for windows。