6

在我的测试中,我发现可以在 free() 之后使用指针。我有以下代码:

typedef struct{

    int module_id;
    int adc_id;
    struct config_line * pnext;

} config_line;
config_line * create_list()
{

config_line * phead = (config_line *) malloc(sizeof(config_line));
phead->pnext=NULL;
phead->module_id = 1;
phead->adc_id = 2;

printf("module_id=%d adc_id=%d\n",phead->module_id, phead->adc_id);

free(phead);

printf("module_id=%d adc_id=%d\n",phead->module_id, phead->adc_id);

phead->module_id = 2;
phead->adc_id = 5;

printf("module_id=%d adc_id=%d\n",phead->module_id, phead->adc_id);
}

这段代码的输出是:

module_id=1 adc_id=2
module_id=0 adc_id=2
module_id=2 adc_id=5

为什么在 free(phead) 之后我可以访问(读取和写入)指针?为什么没有分段错误?

4

5 回答 5

23

因为使用无效指针会调用未定义的行为。这意味着行为是......嗯......未定义。它没有义务崩溃。

于 2013-03-15T12:12:49.160 回答
14

当您调用时,指向free(phead)的内存正在被释放,但 的值保持不变,从而使指针悬空。访问已释放的内存会产生未定义的行为pheadpheadphead

NULL释放指针指向的内存后,分配给指针是一个好习惯:

free(phead);
phead = NULL;
于 2013-03-15T12:14:39.860 回答
8

因为内存仍然映射到您的进程中,但没有分配。C 程序中有两个级别的内存管理:首先,内核为您提供可以写入的页面。如果进程需要更多已映射的内存,则必须从内核(sbrk)请求更多。虽然这是大块的,所以 malloc 为你将它分成块,根据需要请求更多的页面,但尽可能使用已经分配给程序的内存。free 不能返回页面,直到所有使用它的分配都被释放。

所以,内核给你的内存访问违规(SIGSEGV)是相当粗略的,并且不能拾取大多数内存错误,只有从内核的角度来看是致命的事情。您可以随意丢弃 malloc 的跟踪数据,在大多数分配结束后和多次释放后阅读,等等。

不过,永远不要犯内存错误。使用非常严格的 VM 使用 valgrind 运行您的应用程序,以检查所有错误并立即压缩它们。

于 2013-03-15T12:16:19.753 回答
5

为什么鸡头被砍掉了还能继续乱跑乱跳?请注意,这并不总是发生。有些鸡可能会立即停止移动,而另一些鸡可能会继续奔跑数月。当它发生时,它发生是因为它发生了。这是否意味着我们应该砍掉我们的宠物的头?

就像砍掉我们的宠物的头一样,使用已释放的内存是个坏主意。这并不意味着它会产生负面结果;您的程序可能会继续在您的机器上按预期运行,而if (fubar)(其中fubar是您的无效指针)可能足以导致您的程序在其他实现上出现故障。

实现定义(或不​​定义)行为的这种想法自由正式称为未定义行为;行为是未定义的,因为 C 标准,一组规定 C 实现应该如何表现的文档,没有定义行为。因此,就像砍掉我们的宠物的头一样,我们应该避免所有未定义的行为。

于 2013-03-15T12:31:46.293 回答
4

因为释放内存(指针)只是将该内存放回空闲内存池中。内存不仅会消失,它的内容也不会被清除,直到再次分配并写入该内存。

因此,在内存被释放后访问内存是非常有可能的。就像您想象的那样不是一个好主意,因为它可以随时分配和修改。

于 2013-03-15T13:10:18.527 回答