2

我对 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?

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

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

4

7 回答 7

13

这是未定义的行为,因此尊重freed 指针是错误的,因为可能(并且将会)发生奇怪的事情。

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

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

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

于 2009-07-03T09:59:38.327 回答
10

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

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

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

于 2009-07-03T12:47:11.810 回答
9

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

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

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

于 2009-07-03T10:02:46.400 回答
4

*(p+4) 是 8 而 *x 是 0。为什么会这样?如果 *(p+4) 是 8,那么 *x 不应该是 6,因为 x 数组的第一个元素是 6?

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

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

于 2009-07-03T12:43:35.093 回答
3

一旦你释放了动态内存变量,它就不是你的了。内存管理器可以自由地使用您指向的那块内存来做它认为更好的事情。据我所知,编译器不会对释放的内存块做任何事情,因为它是一个函数,而不是由语言定义的。即使它是由语言定义的,编译器也只是插入对底层操作系统函数的调用。

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

顺便说一句,在我的机器上有所不同,两个指针的值都发生了变化。

于 2009-07-03T10:10:28.097 回答
2

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

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

于 2009-07-03T10:00:09.403 回答
2

即使内存是freed,它也不一定被重用于其他目的。指向您的进程内存的旧指针仍然是有效的指针(尽管指向未分配的内存),因此您也不会遇到分段错误。

于 2009-07-03T10:01:57.510 回答