free()将内存归零吗?

bro*_*ng0 35 c free gcc memory-management clang

直到今天,我仍然相信,调用free()内存空间会释放它以供进一步分配而无需任何其他修改.特别是,考虑到这个SO问题清楚地表明free()不会将内存归零.

但是,让我们考虑这段代码(test.c):

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

int main()
{
    int* pointer;

    if (NULL == (pointer = malloc(sizeof(*pointer))))
        return EXIT_FAILURE;

    *pointer = 1337;

    printf("Before free(): %p, %d\n", pointer, *pointer);

    free(pointer);

    printf("After free(): %p, %d\n", pointer, *pointer);

    return EXIT_SUCCESS;
}
Run Code Online (Sandbox Code Playgroud)

编译(GCC和Clang):

gcc test.c -o test_gcc
clang test.c -o test_clang
Run Code Online (Sandbox Code Playgroud)

结果:

$ ./test_gcc 
Before free(): 0x719010, 1337
After free(): 0x719010, 0
$ ./test_clang
Before free: 0x19d2010, 1337
After free: 0x19d2010, 0
Run Code Online (Sandbox Code Playgroud)

为什么会这样?我一直生活在谎言中还是我误解了一些基本概念?还是有更好的解释?

一些技术信息:

Linux 4.0.1-1-ARCH x86_64
gcc version 4.9.2 20150304 (prerelease) (GCC)
clang version 3.6.0 (tags/RELEASE_360/final)
Run Code Online (Sandbox Code Playgroud)

AnT*_*AnT 24

你的问题没有一个明确的答案.

  • 首先,释放块的外部行为将取决于它是释放到系统还是存储为进程或C运行时库的内部内存池中的空闲块.在现代操作系统中,"返回系统"的内存将无法访问您的程序,这意味着它是否被清零的问题是没有实际意义的.

(其余部分适用于内部存储器池中保留的块.)

  • 其次,用任何特定值填充释放的内存几乎没有意义(因为你不应该访问它),而这种操作的性能成本可能相当大.这就是为什么大多数实现都没有做任何事情来释放内存.

  • 第三,在调试阶段,使用一些预先确定的垃圾值填充释放的内存可以用于捕获错误(例如访问已释放的内存),这就是为什么标准库的许多调试实现将填充带有一些预定值的释放内存或图案.(零,BTW,不是这种价值的最佳选择.像0xDEADBABE模式之类的东西更有意义.)但是,这只是在库的调试版本中完成,其中性能影响不是问题.

  • 第四,许多(大多数)流行的堆内存管理实现将使用释放块的一部分用于其内部目的,即在那里存储一些有意义的值.这意味着块的那个区域被修改free.但通常它不是"归零".

当然,所有这些都是依赖于实现的.

一般来说,您最初的信念是完全正确的:在代码的发布版本中,释放的内存块不会受到任何块范围的修改.

  • `0xDEADBEEF`更常见.`0xDEADBABE`并不欢迎业内女性. (7认同)

D.S*_*ley 16

free()作为一般规则,不会将内存归零.它只是释放它以供将来调用重用malloc().某些实现可以用已知值填充存储器,但这纯粹是库的实现细节.

Microsoft的运行时很好地利用标记释放和分配的内存以及有用的值(有关更多信息,请参阅在Visual Studio C++中,内存分配表示是什么?).我也看到它充满了值,当执行时会导致明确定义的陷阱.

  • simlar发生在文件系统中,它们只是将空间作为可重用的空间,但在某些实现中,它们可能会在顶部写入默认值 (3认同)

The*_*ant 15

有更好的解释吗?

有.在指针之后取消引用指针会free()导致未定义的行为,因此实现有权做任何它喜欢的事情,包括诱使你相信内存区域已被零填充的行为.

  • @ browning0您可能想使用[Valgrind](http://valgrind.org/)或[AddressSanitizer](https://en.wikipedia.org/wiki/AddressSanitizer)等工具.然而,那些不能检测所有可能的腐败.如果你很幸运,你的程序将是段错误的 - 如果你不喜欢它将变成很难追踪[海神虫](https://en.wikipedia.org/wiki/Heisenbug). (2认同)

gio*_*gim 9

你可能还有另一个陷阱,实际上,这里:

free(pointer);

printf("After free(): %p \n", pointer); 
Run Code Online (Sandbox Code Playgroud)

即使只是读取pointer你之后的值free是未定义的行为,因为指针变得不确定.

当然也不允许解除引用释放的指针 - 如下例所示 - :

free(pointer);

printf("After free(): %p, %d\n", pointer, *pointer);
Run Code Online (Sandbox Code Playgroud)

PS.一般情况下,打印地址时%p(例如printf)将其转换为(void*)例如(void*)pointer- 否则您也会得到未定义的行为

  • 读取指针值不是未定义的行为,只有解引用是。free是一个函数,C语言不提供更改通过值传递的参数的功能。 (2认同)
  • @harper:即使指针的值没有改变,`free()`也会将该值从有效变为不确定. (2认同)

456*_*976 8

free()将内存归零吗?

不可以.对于内部管家数据,glibc malloc实现可能会覆盖前一个用户数据指针大小的四倍.

细节:

以下是malloc_chunkglibc 的结构(见这里):

struct malloc_chunk {

  INTERNAL_SIZE_T      prev_size;  /* Size of previous chunk (if free).  */
  INTERNAL_SIZE_T      size;       /* Size in bytes, including overhead. */

  struct malloc_chunk* fd;         /* double links -- used only if free. */
  struct malloc_chunk* bk;

  /* Only used for large blocks: pointer to next larger size.  */
  struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
  struct malloc_chunk* bk_nextsize;
};
Run Code Online (Sandbox Code Playgroud)

分配的存储器块中的用户数据的存储区域在size条目之后开始.在free称为用户数据已经存储的存储空间之后可以用于空闲存储器块的列表,因此4 * sizeof(struct malloc_chunk *)可能覆盖先前用户数据的第一个字节,因此打印出除前一个用户数据值之外的另一个值.这是未定义的行为.如果分配的块较大,则可能存在分段错误.


Bas*_*tch 5

正如其他人指出的那样,你不允许用freed指针做任何事情(否则这是可怕的未定义行为,你应该总是避免,看看这个).

在实践中,我建议不要简单编码

free(ptr);
Run Code Online (Sandbox Code Playgroud)

但总是编码

free(ptr), ptr=NULL;
Run Code Online (Sandbox Code Playgroud)

(因为实际上这有助于捕获一些错误,除了双frees)

如果ptr之后没有使用,编译器将通过跳过分配来优化NULL

在实践中,编译器知道freemalloc(因为标准C库头可能会声明这些标准函数具有适当的函数属性 - 由GCCClang/LLVM理解),因此可能能够优化代码(根据标准规范)malloc&free....),但是实现mallocfree通常由您的C标准库提供(例如在Linux上经常使用GNU glibcmusl-libc),因此实际行为由您libc(而不是编译器本身)提供.阅读相应的文档,特别是免费(3)手册页.

顺便说一句,在Linux上,这两个glibcmusl-libc是免费的软件,所以你可能会学习他们的源代码,以了解他们的行为.他们有时会使用像mmap(2)这样的系统调用从内核获取虚拟内存(后来使用munmap(2)将内存释放回内核),但是他们通常会尝试重用以前的free内存以供将来使用malloc.

在实践中,free可能munmap你的内存(特别是对于内存malloc-ated区) -然后你会得到一个SIGSEGV,如果你敢提领(更高版本)freed的指针,但往往(特别是对于内存区),它只会设法重用区域以后.确切的行为是特定于实现的.通常free不能清除或写入刚刚释放区.

您甚至可以重新定义(即重新实现)您自己的,malloc并且free可能通过链接特殊库(如libtcmalloc),前提是您的实现具有与C99或C11标准所述的行为兼容的行为.

在Linux上,禁用内存过量使用并使用valgrind.编译gcc -Wall -Wextra(可能-g在调试时;你可能会考虑传递-fsanitize=address到最近gccclang至少捕获一些顽皮的错误.).

BTW,有时 Boehm的保守垃圾收集器可能是有用的; 你将使用(在你的整个程序中)GC_MALLOC而不是malloc你不会关心free内存.