如果我们需要`tmp buffer`

时间:2016-08-19 16:12:25

标签: c malloc realloc

至于我担心如果realloc失败,我们会丢失信息并realloc将缓冲区(指针)设置为NULL

考虑遵循以下计划:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main(void){
    char *ptr = malloc(256);

    if (!ptr){
        printf("Error, malloc\n");
        exit(1);
    }

    strcpy(ptr, "Michi");

    ptr = realloc (ptr, 1024 * 102400000uL); /* I ask for a big chunk here to make realloc to fail */

    if (!ptr){
        printf("Houston we have a Problem\n");
    }

    printf("PTR = %s\n", ptr);

    if (ptr){
        free(ptr);
        ptr = NULL;
    }
}

当然输出结果是:

Houston we have a Problem
PTR = (null)

我刚丢失了ptr内的信息。

现在要解决这个问题,我们之前应该使用一个临时缓冲区(指针),看看我们是否得到了那块内存,如果我们得到它,我们可以使用它,如果不是,我们仍然可以使主缓冲区(指针)安全。 / p>

现在请考虑以下程序,而不是调用realloc我在临时缓冲区(指针)上调用malloc

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main(void){
    char *ptr = malloc(256);
    char *tmpPTR = NULL;

    if (!ptr){
        printf("Error, malloc\n");
        exit(1);
    }

    strcpy(ptr, "Michi");

    tmpPTR = malloc (1024 * 102400000uL);
    if (tmpPTR){
        strcpy(tmpPTR, ptr);
        strcat(tmpPTR, " - Aloha");

        if (ptr){
            free(ptr);
            ptr = NULL;
        }
    }else{
        printf("Malloc failed on tmpPTR\n\n");
    }


    if (ptr){
        printf("PTR = %s\n", ptr);

        free(ptr);
        ptr = NULL;
    }else if (tmpPTR){
        printf("tmpPTR = %s\n", tmpPTR);

        free(tmpPTR);
        ptr = NULL;
    }
}

输出是:

Malloc failed on tmpPTR

PTR = Michi

现在我为什么要使用realloc? 根据此上下文使用realloc而不是malloc有什么好处吗?

6 个答案:

答案 0 :(得分:3)

您的问题在于如何使用realloc。您不必将realloc的结果分配给您重新分配的同一指针。正如你所指出的,如果realloc失败,它甚至会出现问题。如果您立即将结果分配给ptr,那么当出现问题时,您确实会丢失先前的缓冲区。但是,如果您将realloc的结果分配给tmpPTR,那么即使ptr失败,realloc仍然可以。使用realloc,如下所示:

char * ptr = malloc(256);
if(!ptr){
    return 1;
}

char * tmpPTR = realloc(ptr, 512);
if(!tmpPTR){
    printf("Houston, we have a problem");
    // ptr is fine
}else{
    ptr = tmpPTR;
}

// ptr is realloc()ed

在上面的代码中,tmpPTR不是一个新的(临时)缓冲区,而只是一个(临时)指针。如果realloc成功,它指向同一个缓冲区(但可能位于不同的位置),如果失败则为NULLrealloc并不总是需要分配新缓冲区,但可以更改现有缓冲区以适应新大小。但如果失败,原始缓冲区将不会更改。

如果您将malloc与临时缓冲区一起使用,那么(对于此示例)您至少需要256 + 512 = 768字节,并且始终需要复制旧数据。 realloc可以重新使用旧缓冲区,因此不需要复制,也不会使用比请求更多的内存。

您可以使用malloc方法,但realloc几乎总是更有效率。

答案 1 :(得分:1)

realloc计划很简单。您无需单独调用malloc。例如,如果您最初为256分配了ptr个字节,只需使用一个计数器(或索引,下面的i)来跟踪分配给{的块中的内存量。已使用{1}},并且当计数器达到限制时(如果您使用ptr作为字符串<1,则小于基于0的索引的最大值,或者小于最大值的2; ),ptr

下面显示的方案是,每次达到分配限制时,您只需向realloc添加256个附加字节:

ptr

注意: 您的int i = 0, max = 256; char *ptr = malloc(max); /* do whatever until i reaches 255 */ if (i + 1 >= max) { void *tmp = realloc (ptr, max + 256); if (!tmp) { fprintf (stderr, "error: realloc - memory exhausted.\n") /* handle error */ } ptr = tmp; max += 256; } 可以退出您所在的循环,以保留handle error中的现有数据。你不需要在那时退出。

答案 2 :(得分:1)

  

现在要解决这个问题,我们之前应该使用一个临时缓冲区(指针),看看我们是否得到了那块内存,如果我们得到它,我们可以使用它,如果不是,我们仍然可以使主缓冲区(指针)安全。 / p>

这不仅没有帮助,而且还会使事情变得更糟,因为现在您不再拥有指向您尝试重新分配的块的指针。那你怎么能ptr呢?

所以:

  1. 浪费记忆。
  2. 需要额外的分配,复制和免费。
  3. 因{1}而使free更有可能失败。
  4. 因为指向您尝试重新分配的块的指针丢失而导致内存泄漏。
  5. 所以不,这不是处理realloc返回NULL的好方法。在致电realloc时保存原始指针,以便您可以妥善处理故障。 realloc可以帮助您免于管理两份数据,并避免在可能的情况下制作这些数据。因此,只要有可能,realloc就可以为您完成这项工作。

答案 3 :(得分:1)

realloc优于malloc的优势在于它可以扩展原始动态内存区域,因此无需复制所有以前的元素; 不能使用malloc 1 执行此操作。无论这种优化是否可用,都不会对您起作用。

假设你有一个先前分配的指针:

char *some_string = malloc(size); // assume non-NULL

然后

if (realloc_needed) {
      char *tmp = realloc(some_string, new_size);

     if ( tmp == NULL ) 
          // handle error
     else
         some_string = tmp; // (1)

在(1)处,用新的指针更新旧指针。可能会发生两件事:地址已经有效地改变了(并且自动复制了元素)或者它没有 - 你真的不在乎。无论哪种方式,您的数据现在都在some_string

只有实际的实现(OS / libc)知道是否可以放大块:你不能看到它,它是一个实现细节。但是,您可以查看您的实施代码,看看它是如何实施的。功能

答案 4 :(得分:0)

技术上malloc(size)是不必要的,因为realloc(NULL, size)执行完全相同的任务。

我经常阅读不确定长度的输入。如下面的函数示例所示,我很少使用malloc(),而是广泛使用realloc()

#include <stdlib.h>
#include <errno.h>

struct record {
    /* fields in each record */
};

struct table {
    size_t         size;   /* Number of records allocated */
    size_t         used;   /* Number of records in table */
    struct record  item[]; /* C99 flexible array member */
};

#define MAX_ITEMS_PER_READ 1

struct table *read_table(FILE *source)
{
    struct table  *result = NULL, *temp;
    size_t         size = 0;
    size_t         used = 0, n;
    int            err = 0;

    /* Read loop */
    while (1) {

        if (used + MAX_ITEMS_PER_READ > size) {
            /* Array size growth policy.
             * Some suggest doubling the size,
             * or using a constant factor.
             * Here, the minimum is
             *     size = used + MAX_ITEMS_PER_READ;
            */
            const size_t  newsize = 2*MAX_ITEMS_PER_READ + used + used / 2;

            temp = realloc(result, sizeof (struct table) + 
                                   newsize * sizeof (result->item[0]));
            if (!temp) {
                err = ENOMEM;
                break;
            }

            result = temp;
            size = newsize;
        }

        /* Read a record to result->item[used],
         * or up to (size-used) records starting at result->item + used.
         * If there are no more records, break.
         * If an error occurs, set err = errno, and break.
         *
         * Increment used by the number of records read: */            
        used++;
    }

    if (err) {
        free(result); /* NOTE: free(NULL) is safe. */
        errno = err;
        return NULL;
    }

    if (!used) {
        free(result);
        errno = ENODATA; /* POSIX.1 error code, not C89/C99/C11 */
        return NULL;
    }

    /* Optional: optimize table size. */
    if (used < size) {
        /* We don't mind even if realloc were to fail here. */
        temp = realloc(result, sizeof (struct table) + 
                               used * sizeof table->item[0]);
        if (temp) {
            result = temp;
            size = used;
        }
    }

    result->size = size;
    result->used = used;

    errno = 0; /* Not normally zeroed; just my style. */
    return result;
}

我自己的实际重新分配政策往往非常保守,将规模增加限制在兆字节左右。这有一个非常实际的原因。

在大多数32位系统上,用户空间应用程序限制为2到4千兆字节的虚拟地址空间。我在许多不同的x86系统(32位)上编写并运行了仿真系统,所有系统都有2到4 GB的内存。通常,单个数据集需要大部分内存,从磁盘读取并在适当的位置进行操作。当数据不是最终形式时,它不能直接从磁盘进行内存映射,因为需要翻译 - 通常是从文本到二进制。

当您使用realloc()增长动态分配的数组来存储如此庞大(在32位)的数据集时,您只受可用虚拟地址空间的限制(假设有足够的可用内存)。 (这尤其适用于64位系统上的32位应用程序。)

相反,如果您使用malloc() - 也就是说,当您注意到动态分配的数组不够大时,您malloc()新数组,复制数据并丢弃旧数据 - ,您的最终数据集大小限制为较小的,差异取决于您确切的数组大小增长策略。如果在调整大小策略时使用典型的 double,则最终数据集将限制为大约一半(可用虚拟地址空间或可用内存,以较小者为准)。

在具有大量内存的64位系统上,realloc()仍然很重要,但更多的是性能问题,而不是32位,其中malloc()是一个限制因素。您可以看到,当您使用malloc()分配一个全新的数组,并将旧数据复制到新数组时,常驻设置大小 - 应用程序所需的实际物理RAM量 - 更大;与使用realloc()时相比,使用50%以上的物理RAM来读取数据。您还会执行大量内存到内存的大型复制(在读取大型数据集时),这些副本仅限于物理RAM带宽,并且确实会降低应用程序的速度(尽管如果您正在从旋转磁盘读取,那就是无论如何都是实际的瓶颈,所以无关紧要。)

最可怕的影响,也是最难以确定的影响,是间接影响。大多数操作系统使用“免费”RAM来缓存最近访问过的尚未修改的文件,这确实减少了大多数工作负载使用的挂钟时间。 (特别是,如果存储介质很慢(即旋转磁盘,而不是SSD),缓存典型库和可执行文件可能会从大型应用程序套件的启动时间中删除。)内存浪费malloc() - 只有方法吞噬了比实际需要更多的实际物理RAM,从内存中驱逐缓存的,通常有用的文件!

您可以对程序进行基准测试,并注意使用我上面显示的malloc() - 仅方法和realloc()方法之间的运行时间没有实际差异。但是,如果它适用于大型数据集,则用户会注意到使用malloc() - 仅程序realloc() - 使用程序更慢地减慢其他程序相同的数据!

因此,尽管在使用malloc()的大量RAM的64位系统上,基本上只是一种低效的处理方式,但在32位系统上,当最终大小未知时,它会限制动态分配数组的大小预先。只有使用realloc()才能获得最大可能的数据集大小。

答案 5 :(得分:0)

你的假设是错误的。请注意指针不是缓冲区。当函数realloc()成功时,它会释放旧指针(释放原始缓冲区)并返回一个指向新分配(缓冲区)的新指针,但是当它失败时,它会保留旧缓冲区并返回NULL。 / p>

因此,您不需要临时缓冲区。你需要一个临时指针。我将借用kninnug的例子,这是你需要做的:

char * ptr = malloc(256);
if (!ptr) {
    return 1;
}

char * tmpPTR = realloc(ptr, 512);
if (!tmpPTR) {
    printf("Houston, we have a problem");
    // ptr is fine
}
else {
    ptr = tmpPTR;
}

// ptr is realloc()ed