为什么在释放指针后取消引用时会得到不同的结果?

我对C中的内存管理(以及Debian GNU / Linux下的GCC 4.3.3)提出了疑问。

根据K&R的C编程语言书(第7.8.5节),当我释放指针然后取消引用它时,是一个错误。 但是我有些怀疑,因为我注意到有时候,就像我在下面粘贴的源代码一样,编译器(?)似乎按照明确定义的原则工作。

我有一个像这样的简单程序,它显示了如何返回动态分配的数组:

#include  #include  int * ret_array(int n) { int * arr = (int *) malloc(10 * sizeof(int)); int i; for (i = 0; i < n; i++) { arr[i] = i*2; } printf("Address pointer in ret_array: %p\n", (void *) arr); return arr; } int * ret_oth_array(int n) { int * arr = (int *) malloc(10 * sizeof(int)); int i; for (i = 0; i < n; i++) { arr[i] = i+n; } printf("Address pointer in ret_oth_array: %p\n", (void *) arr); return arr; } int main(void) { int *p = NULL; int *x = NULL; p = ret_array(5); x = ret_oth_array(6); printf("Address contained in p: %p\nValue of *p: %d\n", (void *) p, *p); free(x); free(p); printf("Memory freed.\n"); printf("*(p+4) = %d\n", *(p+4)); printf("*x = %d\n", *x); return 0; } 

如果我尝试使用一些参数编译它: -ansi -Wall -pedantic-errors ,它不会引发错误或警告。 不只; 它也运行良好。

 Address pointer in ret_array: 0x8269008 Address pointer in ret_oth_array: 0x8269038 Address contained in p: 0x8269008 Value of *p: 0 Memory freed. *p+4 = 8 *x = 0 

*(p + 4)为8,* x为0.为什么会发生这种情况? 如果*(p + 4)是8,则x不应该是6,因为x数组的第一个元素是6?

如果我尝试将调用的顺序更改为free,则会发生另一件奇怪的事情。 例如:

 int main(int argc, char * argv[]) { /* ... code ... */ free(p); free(x); printf("Memory freed.\n"); printf("*(p+4) = %d\n", *(p+4)); printf("*x = %d\n", *x); return 0; } 

实际上在这种情况下输出(在我的机器上)将是:

 *p+4 = 8 *x = 142106624 

为什么x指针真正被“释放”,而p指针被释放(我希望)“不同”? 好吧,我知道在释放内存之后我应该把指针指向NULL,但我只是好奇:P

free() (和malloc() )不是来自gcc。 它们来自C库,在Debian上通常是glibc。 所以,你看到的是glibc的行为,而不是gcc(并且会随着不同的C库或不同版本的C库而改变)。

特别是,在你使用free()你发布的内存块malloc()给了你。 它不再属于你了。 由于它不应再被使用,glibc中的内存管理器可以随意对内存块做任何事情,包括使用它的一部分作为自己的内存结构(这可能是你看到它的内容发生变化的原因;它们已经被簿记信息覆盖,可能会指向其他区块或某种类型的计数器)。

还有其他事情可以发生; 特别是,如果你的分配大小足够大,glibc可以向内核请求一个单独的内存块(使用mmap()或类似的调用),并在free()期间将其释放回内核 。 在这种情况下,您的程序将崩溃。 理论上,这也可能在某些情况下发生,即使分配很少(glibc可以增加/缩小堆)。

它是未定义的行为,因此,因为奇怪的事情可能(并且将会)发生,因此free d指针是一个错误。

free()不会更改指针的值,因此它会一直指向进程地址空间中的堆 – 这就是为什么你没有得到段错误,但是没有指定它,理论上在某些平台上你可以得到段错误你试图在free后立即取消引用指针。

为了防止这种情况,在free后将指针指向NULL是一个好习惯,因此它会以可预测的方式失败 – 段错误。

请注意,在某些操作系统(HP-UX,也可能是其他操作系统)上,允许取消引用NULL指针,只是为了防止段错误(从而隐藏问题)。 我发现它相当愚蠢,因为它使诊断起来更加困难,尽管我不知道这背后的全部故事。

这可能不是您正在寻找的答案,但无论如何我都会试一试:

既然你正在玩未定义的行为,你永远不应该以任何方式,形状或forms依赖它,它有什么好处才能知道一个给定的实现如何处理它?

由于gcc可以在任何给定时间,版本,架构之间或根据月亮的位置和亮度自由地改变处理,因此在了解它如何处理它时没有任何用处。 至少不是使用gcc的开发人员。

*(p + 4)为8,* x为0.为什么会发生这种情况? 如果*(p + 4)是8,则x不应该是6,因为x数组的第一个元素是6?

一个可能的解释是printf(“…%i …”…)可能在内部使用malloc为它的字符串插值分配一个临时缓冲区。 这将在第一次输出后覆盖两个数组的内容。

通常,如果程序在释放后依赖于指针的值,我会认为这是一个错误。 我甚至会说它是一个非常糟糕的代码味道,如果它在释放后保留指针的值(而不是让它超出范围或用NULL覆盖它)。 即使它在非常特殊的情况下工作(具有特定堆管理器的单线程代码)。

一旦释放动态内存变量,它就不是你的了。 内存管理器可以自由地执行它所指向的那段内存。 据我所知,编译器对释放的内存块没有做任何事情,因为它是一个函数而不是由语言定义。 即使它是由语言定义的,编译器也只是插入对底层OS函数的调用。

只是想说,它是由语言定义的,所以你必须检查你的操作系统并在释放它后观察那块内存。 这种行为可能是随机的,因为有时其他程序要求记忆,有时候不会!

顺便说一句,在我的机器上它是不同的,两个指针的值都会改变。

虽然您所看到的行为似乎是一致的,但并不能保证这一点。 不可预见的情况可能会导致这种行为发生变化(更不用说这是完全依赖于实现的事实)。

具体来说,在您的示例中,您可以释放()数组,然后在访问数组时获取旧内容。 如果你在free()之后会有额外的malloc()调用 – 那么旧的内容可能会丢失。

即使存储器是free ,也不一定会将其用于某些其他目的。 指向进程内存的旧指针仍然是有效的指针(尽管是未分配的内存),因此您也不会出现分段错误。