为什么在释放指针后取消引用时会得到不同的结果?
我对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
,也不一定会将其用于某些其他目的。 指向进程内存的旧指针仍然是有效的指针(尽管是未分配的内存),因此您也不会出现分段错误。