在 glibc 中 realloc(p, 0) 真的涉及 free(p) 吗?
Does realloc(p, 0) really involves free(p) in glibc?
我发现一些人和书籍等参考资料指出,如果 p != NULL
和 p
源自先前的分配(例如 malloc
),那么 realloc(p, 0)
是等价的在 GNU/Linux 上 free(p)
。为了支持这篇论文 man realloc
正是以这种方式陈述(强调我的前进):
The realloc() function changes the size of the memory block pointed to
by ptr to size bytes. The contents will be unchanged in the range from
the start of the region up to the minimum of the old and new sizes. If
the new size is larger than the old size, the added memory will not be
initialized. If ptr is NULL, then the call is equivalent to
malloc(size), for all values of size; if size is equal to zero, and
ptr is not NULL, then the call is equivalent to free(ptr). Unless ptr
is NULL, it must have been returned by an earlier call to malloc(),
calloc() or realloc(). If the area pointed to was moved, a free(ptr)
is done.
您可能会在 this question 中发现,C 标准并未准确定义应该发生什么,实际行为是实现定义的。更具体地说:
C11 §7.22.3/p1 内存管理函数 说:
If the size of the space requested is zero, the behavior is
implementation-defined: either a null pointer is returned, or the
behavior is as if the size were some nonzero value, except that the
returned pointer shall not be used to access an object.
和 C11 §7.22.3.5 realloc 函数 包含:
3) (...) If memory for the new object cannot be allocated, the old
object is not deallocated and its value is unchanged.
4) The realloc
function returns a pointer to the new object (which may
have the same value as a pointer to the old object), or a null pointer
if the new object could not be allocated.
我在 mcheck
的帮助下编写了一些基本代码来找出实际行为,内存检查器随 glibc
:
一起提供
#include <mcheck.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int a = 5;
int *p, *q;
mtrace();
p = malloc(sizeof(int));
q = &a;
printf("%p\n", (void *) p);
printf("%p\n", (void *) q);
q = realloc(p, 0);
printf("%p\n", (void *) p);
printf("%p\n", (void *) q);
return 0;
}
结果是:
$ gcc -g check.c
$ export MALLOC_TRACE=report
$ ./a.out
0xfd3460
0x7ffffbc955cc
0xfd3460
(nil)
[grzegorz@centos workspace]$ mtrace a.out report
Memory not freed:
-----------------
Address Size Caller
0x0000000000fd3460 0x4 at /home/grzegorz/workspace/check.c:12
如您所见,q
已设置为 NULL
。 free()
好像并没有真正被调用。事实上不可能,除非我的解释不正确:因为 realloc
返回了 NULL
指针,新对象不可能被分配,这意味着:
the old object is not deallocated and its value is unchanged
这是正确的吗?
由于 realloc()
传递大小为 0 时的行为是实现定义的...
If the size of the space requested is zero, the behavior is implementation-defined: either a null pointer is returned, or the behavior is as if the size were some nonzero value, except that the returned pointer shall not be used to access an object.
...相当于
的便携版
void * p = malloc(1);
free(p);
需要
void * p = malloc(1);
p = realloc(p, 0)
free(p); /* because of the part after the "or" as quoted above.
之后内存平衡应该是均匀的
更新 realloc()
的 "error" 案例:
void * p = malloc(1);
{
void * q = realloc(p, 0);
p = q ?q :p;
}
free(p); /* because of the part after the "or" as quoted above.
查看 glibc
中的 realloc
代码:http://code.woboq.org/userspace/glibc/malloc/memusage.c.html#realloc。您将在第 434 行看到当大小为 0 时调用 free
。
编辑:您的 glibc 似乎是 2.18 之前的版本,在 2.18 中修复了 mtrace 中的错误(参见 here)。在 2.20 glibc 上,您的测试程序报告:"No memory leaks."
free
在 glibc 中被调用。来自当前 glibc 2.21 (here and here) 的来源:
/*
REALLOC_ZERO_BYTES_FREES should be set if a call to
realloc with zero bytes should be the same as a call to free.
This is required by the C standard. Otherwise, since this malloc
returns a unique pointer for malloc(0), so does realloc(p, 0).
*/
#ifndef REALLOC_ZERO_BYTES_FREES
#define REALLOC_ZERO_BYTES_FREES 1
#endif
void *
__libc_realloc (void *oldmem, size_t bytes)
{
mstate ar_ptr;
INTERNAL_SIZE_T nb; /* padded request size */
void *newp; /* chunk to return */
void *(*hook) (void *, size_t, const void *) =
atomic_forced_read (__realloc_hook);
if (__builtin_expect (hook != NULL, 0))
return (*hook)(oldmem, bytes, RETURN_ADDRESS (0));
#if REALLOC_ZERO_BYTES_FREES
if (bytes == 0 && oldmem != NULL)
{
__libc_free (oldmem); return 0;
}
#endif
尽管我对 "returned NULL
" 案例的解释似乎是正确的(请参阅下面我的编辑),glibc
开发人员 decided 使其与以前的 C89 标准保持一致并拒绝符合C99/C11:
There is no way this will be changed. This is how it has been
implemented forever. C should document existing practice. Changing
it would mean introducing memory leaks.
另外 mcheck
指示具有误导性,因为其他测试用例表明内存 被 有效释放 realloc
:
#include <malloc.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *p, *q;
p = malloc(20 * sizeof(int));
malloc_stats();
putchar('\n');
q = realloc(p, 0);
malloc_stats();
return 0;
}
这里,输出是:
$ gcc check.c
$ ./a.out
Arena 0:
system bytes = 135168
in use bytes = 96
Total (incl. mmap):
system bytes = 135168
in use bytes = 96
max mmap regions = 0
max mmap bytes = 0
Arena 0:
system bytes = 135168
in use bytes = 0
Total (incl. mmap):
system bytes = 135168
in use bytes = 0
max mmap regions = 0
max mmap bytes = 0
编辑:
正如 hvd 在 , the ISO/IEC Working Group had some discussion, materialized as Defect Report #400 中指出的那样。拟议的更改可能会在 C 标准的未来修订版中允许 glibc
的现有做法(或可能作为 C11 的技术勘误 1)。
我真正喜欢 DR #400 的地方是:
Add to subsection 7.31.12 a new paragraph (paragraph 2):
Invoking realloc with a size argument equal to zero is an obsolescent feature.
我发现一些人和书籍等参考资料指出,如果 p != NULL
和 p
源自先前的分配(例如 malloc
),那么 realloc(p, 0)
是等价的在 GNU/Linux 上 free(p)
。为了支持这篇论文 man realloc
正是以这种方式陈述(强调我的前进):
The realloc() function changes the size of the memory block pointed to by ptr to size bytes. The contents will be unchanged in the range from the start of the region up to the minimum of the old and new sizes. If the new size is larger than the old size, the added memory will not be initialized. If ptr is NULL, then the call is equivalent to malloc(size), for all values of size; if size is equal to zero, and ptr is not NULL, then the call is equivalent to free(ptr). Unless ptr is NULL, it must have been returned by an earlier call to malloc(), calloc() or realloc(). If the area pointed to was moved, a free(ptr) is done.
您可能会在 this question 中发现,C 标准并未准确定义应该发生什么,实际行为是实现定义的。更具体地说:
C11 §7.22.3/p1 内存管理函数 说:
If the size of the space requested is zero, the behavior is implementation-defined: either a null pointer is returned, or the behavior is as if the size were some nonzero value, except that the returned pointer shall not be used to access an object.
和 C11 §7.22.3.5 realloc 函数 包含:
3) (...) If memory for the new object cannot be allocated, the old object is not deallocated and its value is unchanged.
4) The
realloc
function returns a pointer to the new object (which may have the same value as a pointer to the old object), or a null pointer if the new object could not be allocated.
我在 mcheck
的帮助下编写了一些基本代码来找出实际行为,内存检查器随 glibc
:
#include <mcheck.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int a = 5;
int *p, *q;
mtrace();
p = malloc(sizeof(int));
q = &a;
printf("%p\n", (void *) p);
printf("%p\n", (void *) q);
q = realloc(p, 0);
printf("%p\n", (void *) p);
printf("%p\n", (void *) q);
return 0;
}
结果是:
$ gcc -g check.c
$ export MALLOC_TRACE=report
$ ./a.out
0xfd3460
0x7ffffbc955cc
0xfd3460
(nil)
[grzegorz@centos workspace]$ mtrace a.out report
Memory not freed:
-----------------
Address Size Caller
0x0000000000fd3460 0x4 at /home/grzegorz/workspace/check.c:12
如您所见,q
已设置为 NULL
。 free()
好像并没有真正被调用。事实上不可能,除非我的解释不正确:因为 realloc
返回了 NULL
指针,新对象不可能被分配,这意味着:
the old object is not deallocated and its value is unchanged
这是正确的吗?
由于 realloc()
传递大小为 0 时的行为是实现定义的...
If the size of the space requested is zero, the behavior is implementation-defined: either a null pointer is returned, or the behavior is as if the size were some nonzero value, except that the returned pointer shall not be used to access an object.
...相当于
的便携版void * p = malloc(1);
free(p);
需要
void * p = malloc(1);
p = realloc(p, 0)
free(p); /* because of the part after the "or" as quoted above.
之后内存平衡应该是均匀的
更新 realloc()
的 "error" 案例:
void * p = malloc(1);
{
void * q = realloc(p, 0);
p = q ?q :p;
}
free(p); /* because of the part after the "or" as quoted above.
查看 glibc
中的 realloc
代码:http://code.woboq.org/userspace/glibc/malloc/memusage.c.html#realloc。您将在第 434 行看到当大小为 0 时调用 free
。
编辑:您的 glibc 似乎是 2.18 之前的版本,在 2.18 中修复了 mtrace 中的错误(参见 here)。在 2.20 glibc 上,您的测试程序报告:"No memory leaks."
free
在 glibc 中被调用。来自当前 glibc 2.21 (here and here) 的来源:
/*
REALLOC_ZERO_BYTES_FREES should be set if a call to
realloc with zero bytes should be the same as a call to free.
This is required by the C standard. Otherwise, since this malloc
returns a unique pointer for malloc(0), so does realloc(p, 0).
*/
#ifndef REALLOC_ZERO_BYTES_FREES
#define REALLOC_ZERO_BYTES_FREES 1
#endif
void *
__libc_realloc (void *oldmem, size_t bytes)
{
mstate ar_ptr;
INTERNAL_SIZE_T nb; /* padded request size */
void *newp; /* chunk to return */
void *(*hook) (void *, size_t, const void *) =
atomic_forced_read (__realloc_hook);
if (__builtin_expect (hook != NULL, 0))
return (*hook)(oldmem, bytes, RETURN_ADDRESS (0));
#if REALLOC_ZERO_BYTES_FREES
if (bytes == 0 && oldmem != NULL)
{
__libc_free (oldmem); return 0;
}
#endif
尽管我对 "returned NULL
" 案例的解释似乎是正确的(请参阅下面我的编辑),glibc
开发人员 decided 使其与以前的 C89 标准保持一致并拒绝符合C99/C11:
There is no way this will be changed. This is how it has been implemented forever. C should document existing practice. Changing it would mean introducing memory leaks.
另外 mcheck
指示具有误导性,因为其他测试用例表明内存 被 有效释放 realloc
:
#include <malloc.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *p, *q;
p = malloc(20 * sizeof(int));
malloc_stats();
putchar('\n');
q = realloc(p, 0);
malloc_stats();
return 0;
}
这里,输出是:
$ gcc check.c
$ ./a.out
Arena 0:
system bytes = 135168
in use bytes = 96
Total (incl. mmap):
system bytes = 135168
in use bytes = 96
max mmap regions = 0
max mmap bytes = 0
Arena 0:
system bytes = 135168
in use bytes = 0
Total (incl. mmap):
system bytes = 135168
in use bytes = 0
max mmap regions = 0
max mmap bytes = 0
编辑:
正如 hvd 在 glibc
的现有做法(或可能作为 C11 的技术勘误 1)。
我真正喜欢 DR #400 的地方是:
Add to subsection 7.31.12 a new paragraph (paragraph 2):
Invoking realloc with a size argument equal to zero is an obsolescent feature.