free() 是否将内存归零?

Is free() zeroing out memory?

直到今天,我一直相信在内存 space 上调用 free() 会释放它以供进一步分配,而无需任何其他修改。特别是,考虑到 this SO question 明确指出 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;
}

编译(GCC 和 Clang):

gcc test.c -o test_gcc
clang test.c -o test_clang

结果:

$ ./test_gcc 
Before free(): 0x719010, 1337
After free(): 0x719010, 0
$ ./test_clang
Before free: 0x19d2010, 1337
After free: 0x19d2010, 0

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

一些技术信息:

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)

free() 通常不会将内存归零。它只是释放它以供将来调用 malloc() 时重新使用。某些实现可能会用已知值填充内存,但这纯粹是库的实现细节。

Microsoft 的运行时很好地利用了用有用的值标记已释放和已分配的内存(有关详细信息,请参阅 In Visual Studio C++, what are the memory allocation representations?)。我还看到它充满了执行时会导致定义明确的陷阱的值。

is there a better explanation?

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

free()实际上可以return内存给操作系统,让进程更小。通常,它所能做的就是允许稍后调用 malloc 以重用 space。同时,space 作为 malloc.

内部使用的空闲列表的一部分保留在您的程序中

这里还有一个你可能不知道的陷阱:

free(pointer);

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

即使 readingfree 之后 pointer 的值也是未定义的行为,因为指针变得不确定。

当然,取消引用已释放的指针(如下例所示)也是不允许的:

free(pointer);

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

ps。通常,当使用 %p 打印地址时(如 printf)将其转换为 (void*),例如(void*)pointer - 否则你也会得到未定义的行为

Is free() zeroing out memory?

没有。 glibc malloc implementation 最多可以为内部管家数据覆盖前用户数据指针大小的四倍。

详情:

下面是glibc的malloc_chunk结构(见here):

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;
};

分配的内存块中的用户数据内存区域在 size 条目之后开始。 free 被调用后,用户数据所在的内存 space 可能被用于空闲内存块列表,因此前用户数据的前 4 * sizeof(struct malloc_chunk *) 字节可能被覆盖,因此打印出不同于先前用户数据值的另一个值。这是未定义的行为。如果分配的块较大,则可能存在分段错误。

正如其他人所指出的,您不能对 freed 指针做任何事情(否则那是可怕的 undefined behavior, which you should always avoid, see this)。

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

free(ptr);

但总是编码

free(ptr), ptr=NULL;

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

如果之后没有使用ptr,编译器会通过跳过NULL

的赋值来优化

实际上,编译器知道 freemalloc(因为标准 C 库头文件可能会在适当的 function attributes -understood by both GCC & Clang/LLVM) so might be able to optimize the code (according to the standard specification of malloc & free....), but the implementation of malloc and free is often provided by your C standard library (e.g. very often GNU glibc or musl-libc on Linux) so the actual behavior is provided by your libc (not the compiler itself). Read appropriate documentation, notably free(3) 手册页中声明这些标准函数。

顺便说一句,在 Linux 上,glibcmusl-libc 都是免费软件,因此您可以研究它们的源代码以了解它们的行为。他们有时会使用像 mmap(2) (and later release back the memory to the kernel using munmap(2)) 这样的系统调用从内核获取虚拟内存,但他们通常会尝试为将来的 mallocs

重用以前的 freed 内存

在实践中,free 可以 munmap 你的记忆(特别是 记忆 malloc 区域) - 然后你会得到一个 SIGSEGV 如果你敢于取消引用(稍后)那个 freed 指针,但通常(特别是对于 small 内存区域)它会稍后只需设法重用该区域即可。确切的行为是特定于实现的。通常 free 不会 清除或写入刚刚释放的区域。

您甚至可以重新定义(即重新实现)您自己的 mallocfree,也许可以通过链接一个特殊的库,例如 libtcmalloc,前提是您的实现具有行为与 C99 或 C11 标准所说的兼容。

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

顺便说一句,有时 Boehm's conservative garbage collector 可能有用;你将使用(在你的整个程序中)GC_MALLOC 而不是 malloc 并且你不会关心 free-ing 内存。

您的问题没有唯一确定的答案。

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

(其余适用于保留在内部内存池中的块。)

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

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

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

当然,所有这些都在很大程度上取决于实现。

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