在调用之前确定realloc()行为

时间:2008-10-19 13:26:53

标签: c memory-management

据我了解,当被要求保留更大的内存块时,realloc()函数将执行以下三种操作之一:

if free contiguous block exists
    grow current block
else if sufficient memory
    allocate new memory
    copy old memory to new
    free old memory
else
    return null

增加当前块是一个非常便宜的操作,所以这是我想要利用的行为。但是,如果我正在重新分配内存,因为我想(例如)在现有字符串的开头插入一个char,我不希望realloc()复制内存。我最终会用realloc()复制整个字符串,然后再手动复制它以释放第一个数组元素。

是否可以确定realloc()将执行哪些操作?如果是这样,是否有可能以跨平台的方式实现?

8 个答案:

答案 0 :(得分:7)

realloc()的行为可能取决于其具体实施。并且基于这个代码将是一个可怕的黑客,至少可以说,它违反了封装。

针对您的具体示例的更好解决方案是:

  1. 查找当前缓冲区的大小
    • 分配一个新的缓冲区(malloc()),大于前一个缓冲区
    • 将您想要的前缀复制到新缓冲区
    • 将前一个缓冲区中的字符串复制到新缓冲区,从前缀
    • 开始
    • 释放上一个缓冲区

答案 1 :(得分:2)

如评论中所述,问题中的案例3(无记忆)是错误的;如果没有可用的内存,realloc()将返回NULL [问题现在已修复]。

“Code Complete”中的Steve McConnell指出,如果在realloc()失败时将realloc()的返回值保存在原始指针的唯一副本中,那么您只是泄漏了内存。那就是:

void *ptr = malloc(1024);
...
if ((ptr = realloc(ptr, 2048)) == 0)
{
    /* Oops - cannot free original memory allocation any more! */
}

realloc()的不同实现将表现不同。唯一安全的假设是总是移动数据 - 当你realloc()内存时,你总是得到一个新地址。

正如其他人所指出的那样,如果您对此感到担心,也许是时候查看您的算法了。

答案 2 :(得分:1)

向后存储字符串会有帮助吗?

...否则 只需要malloc()比你需要的空间更多,当你用完房间时,复制到一个新的缓冲区。一种简单的技术是每次加倍空间;这很好用,因为字符串越大(即复制到新缓冲区所需的时间越多),它发生的频率就越低。

使用此方法,您还可以在缓冲区中对字符串进行右对齐,因此可以轻松地在开头添加字符。

答案 3 :(得分:0)

没有 - 如果你考虑一下,它就行不通。在检查它将要执行的操作和实际操作之间,另一个进程可以分配内存。 在多线程应用程序中,这不起作用。在你检查它将要做什么以及实际做什么之间,另一个线程可以分配内存。

如果您担心这类事情,可能是时候查看您正在使用的数据结构,看看您是否可以解决问题。根据这些字符串的构造方式,您可以使用设计良好的缓冲区高效地完成这些工作。

答案 4 :(得分:0)

我认为这不可能以跨平台的方式进行。 Here是ulibc实现的代码,它可能会让你知道如何以平台相关的方式做到这一点,实际上最好找到glibc源,但这个是谷歌搜索之上:)

答案 5 :(得分:0)

如果obstacks与您的内存分配需求相匹配,则可以使用其fast growing功能。 Obstacks是glibc的一个特性,但它们也可以在libiberty库中使用,它是相当便携的。

答案 6 :(得分:0)

为什么不在字符串的左边保留一些空的缓冲区空间,如下所示:

char* buf = malloc(1024);
char* start = buf + 1024 - 3;
start[0]='t';
start[1]='o';
start[2]='\0';

将“on”添加到字符串的开头,使其“打开\ 0”:

start-=2;
if(start < buf) 
  DO_MEMORY_STUFF(start, buf);//time to reallocate!
start[0]='o';
start[1]='n';

这样,您不必在每次想要在开头插入时都继续复制缓冲区。

如果必须在开头和结尾都进行插入,只需在两端分配一些空间;显然,中间的插入仍然需要你将元素改组。

答案 7 :(得分:0)

更好的方法是使用链表。让每个数据对象在页面上分配,并从上一页或索引页面分配另一个页面并链接到该页面。通过这种方式,您可以了解下一次分配失败的时间,并且您永远不需要复制内存。