【问题标题】:C Programming - How often should realloc be used?C 编程 - realloc 应该多久使用一次?
【发布时间】:2018-05-17 10:55:34
【问题描述】:

我有一个关于动态内存分配的问题。

上下文:我正在编写一个程序,它读取一个单词的文本文件并计算每个单词出现的频率(每行一个单词)。

这个特定的函数读取文件,计算行数和字符数,然后动态地为字符串指针数组分配内存,该数组存储每行的字符数和字符串本身。 (其他部分与我的问题不太直接相关)。

问题:如果空间不足,我应该多久重新分配一次内存?我设置了一个常量(“memstart”)来设置初始内存分配值。在下面的代码 sn-p 中,我为每一行重新分配了“memstart”的值。如果 a 重新分配更大的内存块而不是每次将内存空间增加 1 个“变量类型”,程序会处理得更快吗?

这样的最佳做法是什么?

代码片段:

int read_alloc(FILE* fin, FILE *tmp, char **wdp, int *sz){
    int line_cnt= 0, chr, let=1;
    do{
        chr=getc(fin);
        let++;          
        //count characters

        if(chr!=EOF){
            chr=tolower(chr);
            fputc(chr, tmp);
        }               
        //convert to lcase and write to temp file

        if ('\n' == chr || chr==EOF){
            sz[(line_cnt)]=((let)*sizeof(char));            //save size needed to store string in array
            *(wdp+(line_cnt))=malloc((let)*sizeof(char));   //allocate space for the string
            if ((line_cnt-1) >= memstart){
                realloc(wdp, (sizeof(wdp)*(memstart+line_cnt)));    //if more space needed increase size
                realloc(sz, (sizeof(sz)*(memstart+line_cnt)));
            }
            line_cnt++;         
            let=1;
        }
    } while (EOF != chr);

    return (line_cnt);
}

【问题讨论】:

  • 通常容器会以某个常数因子增长。说1.5。也就是说,每次您需要调整它的大小时 - 您都将其容量设为 1.5x 的当前容量。
  • 在增加分配时,一个好的一般策略是从合理大小的分配开始,并根据需要加倍。完成增长后,可以通过最终调用 realloc() 将分配修剪到最终大小。
  • 你应该重新分配log的次数。
  • realloc(大小比以前大)的使用次数应至少与您打算踩过前一个大小的最后一个字节的次数一样多。再少一点,你就有麻烦了。

标签: c dynamic-memory-allocation realloc


【解决方案1】:

虽然问题是关于应该多久调用一次realloc,但看看 OP 的代码,我认为最好从安全应该如何完成开始。

C11 标准规定(n1570 草案,§ 7.22.3.5realloc 函数,强调我的):

概要

#include <stdlib.h>
void *realloc(void *ptr, size_t size);

说明
realloc函数释放ptr指向的旧对象,并返回一个指向大小由size指定的新对象的指针。新对象的内容应与旧对象的内容相同在重新分配之前,直到新旧尺寸中的较小者。新对象中超出旧对象大小的任何字节都具有不确定的值。
如果 ptr 是空指针,则 realloc 函数的行为类似于指定大小的 malloc 函数。 (...)。 如果无法为新对象分配内存,则不会释放旧对象并且其值不变
返回
realloc 函数返回一个指向新对象的指针(它可能与指向旧对象的指针具有相同的值),如果无法分配新对象,则返回一个空指针

现在让我们从问题中考虑这个 sn-p,其中 sz 被声明为 int* sz;

realloc(sz, (sizeof(sz)*(memstart+line_cnt)));

返回值丢失了,所以我们无法知道调用是否成功,如果成功了,sz 就失效了。此外,sizeof(sz) 是指针的大小,而不是指向类型 (int)。

更安全(和正确)的模式是:

size_t new_size = /* Whatever, let's say */ size + SOME_COSTANT + size / 2;
void *tmp = realloc(ptr, new_size * sizeof *ptr);
if ( tmp == NULL ) {
    /* Deal with the error, e.g. log a message with perror, return NULL
       (if this is in a function) or just give up, but remeber that
       realloc doesn't invalidate nor free 'ptr' on failure */
    exit(EXIT_FAILURE);
}
ptr = tmp; // <- on success, realloc invalidated ptr
size = new_size;   

现在,回答这个问题,应该只在需要时调用realloc,因为它涉及可能扩展系统调用。所以要么提前分配一个大块,要么选择一个不断增长的策略,比如每次将大小加倍(或 1.5 倍)。

值得注意的是,如果可能,操作系统可以在不复制原始数组的任何元素的情况下执行重新分配。

【讨论】:

  • 只有几尼特。 realloc 返回 void *,所以只需要 void *tmp = realloc (..if ( tmp == NULL ) 不要释放指针您之前的所有数据仍然由ptr 指向,只是不要分配ptr = tmp;。最后一点,'*'variable 一起使用,而不是 type。 (例如 int* a, b, c; 肯定不会产生 bc 指针)
  • @DavidC.Rankin 1) 好点,我错过了。 2)我释放正是因为数据仍然指向,因为在我退出的sn-p中,通常我在一个函数中执行它并返回(并检查,以处理其他要释放的资源)NULL,但我得到你的观点。 3)我知道;)这是我使用(并且经常看到)的一种风格,因为通常我尽量不在语句中声明一个以上的变量,但同样,它可能会产生误导,我明白你的意思。
  • 谢谢。这非常有益。
【解决方案2】:

经典答案是每次加倍,but a factor of 1.5 might be better。重要的是每次将数组大小乘以某个因子,而不是每次添加额外的空间。

每次重新分配都可能需要将前一个数组复制到一个新数组中。我们希望尽量减少这些副本。如果我们要添加n 项目,并且我们从大小为a 的数组开始,每次重新分配增加r 的因子,以n 的值结束,则(re -) 分配将是 a, ar, ar^2, ar^3, ..., n。该序列的总和为 (nr-a)/(r-1)。因此总空间为 O(n) 阶。

假设我们从 a 开始,这次每次都添加 r。序列是 a, a+r, a+2r, a+3r, ..., n。该序列的总和将为 0.5*((n^2-a^2)/r + a + n)。在这种情况下,总空间为 O(n^2)。更糟糕!

当因子为 2 时,在最坏的情况下,数组将是 1/2 为空的。那可能没问题。完成并知道最终大小后,您始终可以缩小分配。

正如另一个答案中所指出的,您调用realloc() 的方式存在几个错误,但这不是问题所在。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-01-20
    • 2010-11-06
    • 1970-01-01
    • 2012-08-11
    • 2011-02-02
    • 2013-02-17
    • 2011-05-12
    • 1970-01-01
    相关资源
    最近更新 更多