直到今天,我仍然相信在内存空间上调用free()
会释放它以供进一步分配而无需任何其他修改。特别是,考虑this SO question明确指出free()
DOESN&T将零记忆清零。
然而,让我们考虑一下这段代码(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)
答案 0 :(得分:24)
你的问题没有一个明确的答案。
(其余部分适用于内部存储池中保留的块。)
其次,用任何特定值填充释放的内存没有意义(因为你不应该访问它),而这种操作的性能成本可能相当大。这就是为什么大多数实现都没有做任何事情来释放内存。
第三,在调试阶段,使用一些预先确定的垃圾值填充释放的内存对于捕获错误(比如访问已释放的内存)很有用,这就是为什么标准库的许多调试实现将填充释放的内存一些预先确定的价值或模式。 (零,BTW,不是这种价值的最佳选择。像0xDEADBABE
模式之类的东西更有意义。)但是,这只是在库的调试版本中完成,其中性能影响不是问题
第四,许多(大多数)流行的堆内存管理实现将使用释放块的一部分用于其内部目的,即在那里存储一些有意义的值。这意味着块的该区域由free
修改。但通常它不是“归零”。
当然,所有这些都是依赖于实现的。
一般来说,您最初的信念是完全正确的:在代码的发布版本中,释放的内存块不会受到任何块大小的修改。
答案 1 :(得分:16)
free()
不会将内存归零。它只是将其释放以供将来调用malloc()
重用。某些实现可能会用已知值填充内存,但这纯粹是库的实现细节。
Microsoft的运行时很好地利用有用值标记释放和分配的内存(有关更多信息,请参阅In Visual Studio C++, what are the memory allocation representations?)。我也看到它充满了值,在执行时会产生明确定义的陷阱。
答案 2 :(得分:14)
有更好的解释吗?
有。在free()
d之后取消引用指针会导致未定义的行为,因此实现有权做任何它喜欢的事情,包括诱使你相信内存区域已被零填充的行为。
答案 3 :(得分:9)
你可能还有另一个陷阱,实际上,在这里:
free(pointer);
printf("After free(): %p \n", pointer);
即使pointer
free
之后free(pointer);
printf("After free(): %p, %d\n", pointer, *pointer);
的值是%p
,它也是未定义的行为,因为指针变得不确定。
当然也不允许解除引用释放的指针(如下例所示):
printf
PS。一般情况下,使用(void*)
打印地址(例如(void*)pointer
)会将其转换为SerialPort
,例如public interface ISerialPort
{
String PortName { get; set; }
bool IsOpen { get; }
void Open();
void Close();
int Read(byte[] buffer, int offset, int count);
void Write(byte[] buffer, int offset, int count);
}
- 否则你也会得到未定义的行为
答案 4 :(得分:8)
free()将内存归零吗?
没有。对于内部管家数据,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
后,用户数据所在的内存空间可用于空闲内存块的列表,因此前一个用户数据的前4 * sizeof(struct malloc_chunk *)
字节可能被覆盖,因此另一个值不是打印出以前的用户数据值。这是未定义的行为。如果分配的块较大,则可能存在分段错误。
答案 5 :(得分:5)
正如其他人指出的那样,你不允许用free
d指针做任何事情(否则这是可怕的undefined behavior,你应该总是避免,请参阅this)。
在实践中,我建议不要简单地编码
free(ptr);
但总是编码
free(ptr), ptr=NULL;
(因为实际上这有助于捕获一些错误,除了双free
s)
如果之后没有使用ptr
,编译器将通过跳过NULL
实际上,编译器知道free
和malloc
(因为标准C库头可能会声明这些标准函数以及function attributes所理解的适当GCC &amp; Clang/LLVM)因此可以优化代码(根据malloc
&amp; free
的标准规范....),但malloc
的实现free
通常由您的C标准库提供(例如,Linux上经常使用GNU glibc或musl-libc),因此实际行为由libc
提供(不是编译器)本身)。阅读相应的文档,特别是free(3)手册页。
glibc
和musl-libc
都是免费软件,因此您可以研究他们的源代码来了解他们的行为。他们有时会使用类似mmap(2)的系统调用从内核获取虚拟内存(后来使用munmap(2)将内存释放回内核),但他们通常会尝试重用以前的free
d未来的记忆malloc
s
在实践中,free
可以munmap
你的记忆(特别是大记忆malloc
- 区域) - 然后你就可以了。如果您敢于(稍后)解除引用SIGSEGV
d指针,那么将获得free
,但通常(特别是对于小内存区域),它将在以后设法重用该区域。确切的行为是特定于实现的。通常free
不清除或写入刚刚释放的区域。
您甚至可以通过链接特殊库(例如libtcmalloc)来重新定义(即重新实现)您自己的malloc
和free
,前提是您的实现具有兼容的行为符合C99或C11标准所说的。
在Linux上,禁用内存过量使用并使用valgrind。在调试时与gcc -Wall -Wextra
(可能还有-g
进行编译;您可能会考虑将-fsanitize=address
传递给最近的gcc
或clang
,至少可以捕获一些顽皮的错误。) 。
BTW,有时 Boehm's conservative garbage collector可能有用;您将使用(在整个计划中)GC_MALLOC
代替malloc
,而您不会关心free
- 记忆。
答案 6 :(得分:2)
free()
实际上可以将内存返回给操作系统并使进程变小。通常,它所能做的只是允许稍后调用malloc 来重用空间。在此期间,该空间仍作为malloc
内部使用的自由列表的一部分保留在您的程序中。