在常见的实现中,realloc是否会缩小缓冲区?

11

在常见的实现中,例如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
你想知道如何测试内部实现是否缩小了你所拥有的内存大小? - Seth Carnegie
在某些实现中,当您调用mallocrealloc时,甚至没有分配内存... - Joe
看看指针的值是否真的改变会很有趣。比较操作前后 p 的值。如果它确实改变了,那么你就知道 某些事情 发生了。 - Joe
2
@Joe 是的,但也要记住,如果它不改变,你就不知道有什么事情没有发生。 - Seth Carnegie
5
在这种情况下,你不能假定realloc()不会重新分配缓冲区。其中一个原因是realloc()可能会为了最小化碎片化而选择这样做,这取决于实现方式。最好查看realloc()在这些平台上的官方接口文档。为了实现最大的可移植性,建议不要假定文档提供的内容之外的任何东西“按合同”存在。 - Dan Aloni
显示剩余5条评论
3个回答

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。 更精确地说,是有关来自Apple的malloc当前实现的magazine_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,如果是,则释放内存,否则不执行任何操作。


main_arena是什么? - jrwren
1
@jrwren 你可以查看glibc的源代码 https://www.gnu.org/software/libc/ 来找到你问题的答案。 - fghj

4
是否值得这样做取决于对象的存活时间和减少内存占用对应用的重要性。没有通用的正确答案。
通用的内存分配器通常假设调用者知道块的先前大小,并且只有在调用者确实知道他们想缩小块时才会调用 realloc 函数。我看过的最后一个内存分配器,如果块已经超过了 128 字节,并且重新分配至少释放了 1KB 或当前分配大小的四分之一个字节数,那么它就会将块缩小。它被调整为高吞吐量服务器应用程序,其中对象通常不能长时间保留,并且为长期存在的对象提供了特殊的“正确大小”操作。

0

是的,它们确实可以。但标准并没有强制要求这样做。因此,您必须使用您所针对的libc进行检查。您可以通过查看代码(如果可用)或编写测试程序来实现。测试程序的思路是这样的 - 您分配相对较大的块(比如10K),然后尝试使用realloc缩小一半,并使用malloc分配一些小的东西。如果新返回的地址位于您第一次分配的范围内,则您的realloc会缩小,否则不会。


1
如果新返回的地址在您第一次分配的范围内,则realloc会缩小,否则它可能会或可能不会。即使先前已经缩小了该区域,第二个malloc选择其他位置进行分配也是有可能的原因。 - Keith Thompson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接