2016-08-19 103 views
-3

据我所关注的,如果realloc失败,我们失去设置缓冲区(指针)的信息和对reallocNULL我们为什么要使用`realloc`如果我们需要一个'TMP buffer`

去考虑下面的程序:

#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的信息。

现在要解决这个问题,我们应该先使用一个临时缓冲区(指针)来查看我们是否获得了这块内存,如果我们能够得到它,我们可以使用它,如果没有,我们仍然有主缓冲区(指针)安全。

现在请考虑下面的程序,在那里,而不是调用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有什么好处吗?

+4

你的假设是错误的。如果['realloc()'](http://man7.org/linux/man-pages/man3/realloc.3.html)失败,它会返回NULL,但缓冲区不变(仍然完全有效使用)。你只是做错了。改用临时指针。 –

+0

是的,我的意思是直接分配,但我仍然需要一个tmpBuffer。那么为什么我应该使用Realloc?这是问题。 – Michi

+0

下载选民请解释一下? – Michi

回答

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几乎总是更有效。

+0

您错过了'else ptr = tmp;'。这是至关重要的,因为在成功使用'realloc'之后,旧的缓冲区可能不再有效。 –

+0

那就是整个问题。要使用'realloc',我需要一个临时缓冲区。那么为什么我应该使用'realloc'而不是malloc?这是我的问题。请再次阅读我的问题 – Michi

+0

@MatteoItalia修复,谢谢 – Kninnug

1

realloc方案很简单。您不需要单独致电malloc。例如,如果您最初有256字节分配给ptr,只需使用一个计数器(或指数,低于i)跟踪的多少分配给ptr块内的内存已被使用,并在计数器达到极限(比最大为0基于的索引,或大于max 2以下1以下,如果您使用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可以退出你是什么环路保护ptr中的现有数据。你不需要退出。

1

现在要解决这个问题,我们应该先使用一个临时缓冲区(指针)来查看我们是否获得了这块内存,如果我们得到它,我们可以使用它,如果没有,我们仍然有主缓冲区(指针)安全。

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

所以:

  1. 浪费内存。
  2. 需要额外的分配,复制和空闲。
  3. 使得realloc更可能因为1而失败。
  4. 由于指向您尝试重新分配的块的指针丢失,因此泄漏内存。

所以不,这不是一个好方法来处理返回NULL的realloc。当您拨打realloc时保存原始指针,以便您可以正常处理故障。要点realloc可以帮助您避免管理数据的两个副本,甚至在可能的情况下避免使用它们。所以让realloc为你做这项工作,只要你可以。

1

realloc优于malloc的优点是它可以扩展原始的动态存储区域,因此无需复制所有以前的元素;你不能做到这一点与malloc 。无论这种优化是否可用,您都无需付出代价。


让我们假设你有一个以前分配的指针:

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中)知道是否有可能放大块:你不会看到它,这是一个实现细节。然而,你可以检查你的实现代码,看看它是如何实现的。

+0

'......你不能用malloc做到这一点。 '请举个例子。 – Michi

+0

@Michi:让它更清晰一点。这有帮助吗? – edmz

+0

你的意思是[这样的事情](http://ideone.com/NYp1os)? 'realloc'有什么不同?可能'realloc'使用'memcpy',但这是另一个故事 – Michi

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位)的数据集时,您只受可用虚拟地址空间的限制(假设有足够的可用内存)。 (这尤其适用于32位应用程序在64位系统。)

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

在具有大量内存的64位系统上,realloc()仍然很重要,但更多的是性能问题,而不是32位,其中malloc()是一个限制因素。您会发现,当您使用malloc()分配全新阵列并将旧数据复制到新阵列时,常驻集大小(应用程序所需的实际物理RAM量)较大;与使用realloc()时相比,使用50%的物理RAM来读取数据。你也做了很多大的内存到内存的拷贝(当读取一个巨大的数据集时),这些内存限制在物理内存带宽上,而且确实会减慢你的应用程序的速度(尽管如果你从一个旋转磁盘读取数据,那就是无论如何都是实际的瓶颈,所以它并不重要)。

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

您可以对您的程序进行基准测试,并注意在使用您的malloc()-仅限方法和realloc()方法之间的运行时间之间没有实际差异。但是,如果它适用于大型数据集,用户将注意到使用malloc()-仅使用程序会减慢其他程序比使用程序的realloc()速度要快得多,使用相同的数据!

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

0

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

所以,你不需要临时缓冲区。你需要一个临时指针。我要借用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 
+0

只要指针指向存储某些信息的某个位置,指针就是一个缓冲区,因为它不仅保存/指向该内存位置,而且还具有对其值[s]的访问权限。无论如何,你是这里的老师。 – Michi

+0

@Michi我认为Kninnug的答案是完美的,并完全解决您的问题。 –

+0

我放弃了。我在这里超过一年。我知道什么时候有人谈论这种语言或其个人意见。我为了乐趣学习C,相信我不是每个人的语言。 – Michi