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

时间:2009-07-03 09:55:02

标签: c gcc memory-management free malloc

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

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

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

#include <stdio.h>
#include <stdlib.h>


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,则会发生另一件奇怪的事情。 E.g:

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

7 个答案:

答案 0 :(得分:13)

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

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

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

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

答案 1 :(得分:10)

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

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

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

答案 2 :(得分:9)

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

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

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

答案 3 :(得分:4)

  

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

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

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

答案 4 :(得分:3)

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

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

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

答案 5 :(得分:2)

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

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

答案 6 :(得分:2)

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