让我们考虑一下这段非常短的代码:
#include <stdlib.h>
int main()
{
char* a = malloc(20000);
char* b = realloc(a, 5);
free(b);
return 0;
}
在阅读了realloc的手册页后,我不完全确定第二行是否会释放19995个额外的字节。引用手册页:The realloc() function changes the size of the memory block pointed to by ptr to size bytes.
,但从这个定义来看,我能确定其余部分会被释放吗?
我的意思是,b
指向的块肯定包含5个空闲字节,那么对于一个懒惰的分配器来说,不为realloc行做任何事情就足够了吗?
注意:我使用的分配器似乎释放了19 995个额外的字节,如valgrind在注释free(b)
行时所示:
==4457== HEAP SUMMARY:
==4457== in use at exit: 5 bytes in 1 blocks
==4457== total heap usage: 2 allocs, 1 frees, 20,005 bytes allocated
是的,如果可以分配新对象,则由C标准保证。
(C99,7.20.3.4p2)"realloc函数取消分配ptr指向的旧对象,并返回一个指向大小由大小指定的新对象的指针。"
是—如果成功的话。
您的代码片段显示了一个众所周知的邪恶错误:
char* b = (char*) realloc(a, 5);
如果成功,则先前分配给a
的存储器将被释放,并且b
将指向可能与原始块重叠或不重叠的5个字节的存储器。
但是,如果调用失败,b
将是null
,但a
仍将指向其原始内存,该内存仍然有效。在这种情况下,您需要free(a)
才能释放内存。
如果你使用常见的(危险的)习语:,情况会更糟
a = realloc(a, NEW_SIZE); // Don't do this!
如果对realloc
的调用失败,则a将成为null
,并且其原始内存将成为孤立内存,从而使其无法修复地丢失,直到程序退出。
这取决于您的libc实现。以下所有行为均符合要求:
- 什么也不做,即让数据保持原样并返回旧块,可能会重新使用现在未使用的字节进行进一步的分配
- 将数据复制到新块,并将旧块释放回操作系统
- 将数据复制到新块并保留旧块以供进一步分配
也是可能的
- 如果无法分配新块,则返回空指针
在这种情况下,旧数据也将保留在原来的位置,这可能导致内存泄漏,例如,如果realloc()
的返回值覆盖指向该块的指针的唯一副本。
一个合理的libc实现将使用一些启发式方法来确定哪种解决方案最有效。
还要记住,这个描述是在实现级别:从语义上讲,只要分配不失败,realloc()
总是释放对象。
似乎不太可能释放19995个字节。更有可能的是,realloc用另一个5字节的块替换了20000字节的块,也就是说,20000字节的块被释放,并且新的5字节块被分配。
realloc函数具有以下契约:
void*result=realloc(ptr,new_size)
- 若结果为NULL,则ptr仍然有效且未更改
- 若结果不是NULL,那个么ptr现在是无效的(就像它已经被释放一样),并且不能再使用了。结果现在是一个指针,正好指向newsize字节的数据
引擎盖下发生的事情的确切细节是特定于实现的——例如,result可能等于ptr(但不能再触摸new_size之外的额外空间),realloc可以调用free,也可以进行自己的内部free表示。关键是,作为一名开发人员,如果realloc返回非null,你就不再对ptr负责,如果reallock返回null,你仍然对它负责。