realloc在常见实现中真的会缩小缓冲区吗?

11 投票
3 回答
874 浏览
提问于 2025-04-17 06:31

在常见的系统实现中,比如Linux/Glibc、Windows/MSVC和BSD/Mac OS X,

void *p = malloc(N + M);  // assume this doesn't fail
p = realloc(p, N);        // nor this

对于NM > 0的情况,调用realloc时,实际上会缩小malloc返回的缓冲区,也就是说,最多可以将M字节返回给空闲列表吗?更重要的是,有可能会重新分配这个缓冲区吗?

我想知道这个问题,因为我刚刚在numpy.ndarray的基础上实现了动态数组,现在我在进行resize操作,这个操作会调用realloc,以确保最终的大小是正确的。我可能可以跳过最后一次resize,作为一种优化(虽然这样会导致永久性过度分配),我想知道这样做是否值得尝试。

3 个回答

0

是的,它们确实会这样做。不过标准并没有强制要求这一点。所以你需要根据你使用的 libc 来检查这一点。你可以通过查看代码(如果有的话)或者写一个测试程序来进行检查。这个测试程序的思路是这样的:你先分配一个比较大的内存块(比如说10K),然后尝试用 realloc 把它缩小一半,再用 malloc 分配一些小的内存。如果新返回的地址在你第一次分配的范围内,那么说明你的 realloc 确实缩小了;否则就说明没有缩小。

4

是否值得这样做,主要看这个对象会存在多久,以及减少它占用的内存对应用程序有多重要。没有一个通用的标准答案。

一般的内存分配器通常会假设调用者知道之前这个内存块的大小,只有在他们确实想要缩小这个内存块时才会调用 realloc。我最近看过的一个内存分配器,如果这个内存块超过128字节,并且重新分配后能释放至少1KB的内存,或者释放的字节数至少是当前分配大小的1/4,它就会愿意缩小这个内存块。这个分配器是为高流量的服务器应用设计的,因为在这种情况下,对象通常不会存在太久,同时它还提供了一种特别的“合适大小”操作,适用于那些知道会存在很长时间的对象。

16

我可以说说关于Linux和glibc的事情。在它的源代码中,有这样的注释:

如果 n 的字节数比 p 当前持有的字节数少,那么多出来的空间会被去掉,并且如果可能的话会被释放掉。

如果你查看glibc的代码,会看到这样的行:

remainder_size = newsize - nb;

if (remainder_size < MINSIZE) { /* not enough extra to split off */
  set_head_size(newp, newsize | (av != &main_arena ? NON_MAIN_ARENA : 0));
  set_inuse_bit_at_offset(newp, newsize);
}
else { /* split remainder */
  remainder = chunk_at_offset(newp, nb);
  set_head_size(newp, nb | (av != &main_arena ? NON_MAIN_ARENA : 0));
  set_head(remainder, remainder_size | PREV_INUSE |
       (av != &main_arena ? NON_MAIN_ARENA : 0));
  /* Mark remainder as inuse so free() won't complain */
  set_inuse_bit_at_offset(remainder, remainder_size);
 #ifdef ATOMIC_FASTBINS
  _int_free(av, remainder, 1);
 #else
  _int_free(av, remainder);
 #endif
}

nb 是你想要的字节数,而这里的 newsize 应该叫 oldsize。所以它会尽量释放多余的内存。

关于Mac OSX,更准确地说是关于 magazine_malloc,这是苹果当前的 malloc 实现。想了解更多细节,可以查看这个链接:http://cocoawithlove.com/2010/05/look-at-how-malloc-works-on-mac.html

realloc 调用的是区域重新分配的方法,目前的实现是 szone_realloc。对于不同的分配大小,有不同的代码,但算法总是一样的:

if (new_good_size <= (old_size >> 1)) {
            /*
             * Serious shrinkage (more than half). free() the excess.
             */
            return tiny_try_shrink_in_place(szone, ptr, old_size, new_good_size);
} else if (new_good_size <= old_size) {
            /* 
             * new_good_size smaller than old_size but not by much (less than half).
             * Avoid thrashing at the expense of some wasted storage.
             */
             return ptr;
}

所以你可以看到,它的实现会检查 new_size <= old_size / 2,如果是这样,就会释放内存;如果不是,就什么也不做。

撰写回答