realloc()如何重新分配内存?

6
realloc()如何重新分配由malloc()分配的内存?
我知道在重新分配内存之前需要使用malloc(),但我不明白它如何实际工作。如果通过realloc()减小动态内存对象的大小,那么这个对象的相应部分是否会在调用realloc()后被删除?
我的问题是:
  1. realloc()函数如何重新分配由malloc()创建的动态内存对象?
请注意:我做了这个问答是因为许多初学者似乎仍然对使用realloc()重新分配内存的问题感到困惑,尽管已经有关于该主题的问题存在于SO上。它们似乎对于初学者来说有些混淆,并且仍然不能完全表示realloc()的整个行为。因此,由于我认为现有的问题和回答仍然不能完全解决这个问题,我做了自己的问答。
1个回答

11

注意:以下回答中的所有引用都摘自实际 C 标准 ISO/IEC 9899:2018(C18)第 7.22.3.4 节。


首先,ISO/IEC 9899:2018 第 7.22.3 节中 realloc() 函数的简介:

#include <stdlib.h> 
void *realloc(void *ptr, size_t size);
尽管其名称为realloc(),但该函数并不“重新分配”任何东西。realloc()不会修改内存中的现有对象。相反,它执行某种“创建(新对象)并将数据复制”的例程。
如果size不为0且ptr指向由内存管理函数之一(不仅限于malloc())分配的对象,或者指向NULL,则realloc()通常会创建一个新对象,并将数据从旧对象复制到新对象中。
我之所以说“通常”,是因为您不能假设在内存中真正分配了一个新对象。必须始终检查返回的指针是否指向NULL来确定是否已分配新对象。
如果新对象的大小大于旧对象,则超出旧对象大小的新对象的字节具有不确定的值。如果新对象比旧对象短,则差异中的值会被丢弃。新对象中的每个其他值都与旧对象中的原始值相同。
之后,如果:
- ptr不是指向NULL的指针,并且是先前由内存管理函数返回的指针,该指针指向的对象在调用realloc()之前没有被释放, - size不为0, - 如果realloc()未返回指向NULL的指针,则确实可以分配新对象,
只有当所有这些前提条件都满足时,realloc()才会释放旧对象的内存并返回一个指向在内存中的新对象的地址的指针。

如果realloc()返回一个指向NULL的指针,则不会创建新对象,并且旧对象仍然以其在内存中的地址保持不变。


为了使“准重新分配”行为几乎完美,可选地,在释放旧对象后(如果发生),新对象可能会分配回与旧对象存储在同一内存地址处。

realloc函数返回指向新对象的指针(可能与指向旧对象的指针相同),或者如果未分配新对象,则返回空指针。

在这种情况下,在 realloc()中有逻辑上的两个数据复制过程,一次是进入缓冲区对象,稍后再回到原始旧对象存储的地方。执行realloc()后缓冲区对象将被释放。


用于指向旧对象的ptr指针不应用于返回的指针。如果调用realloc()的语句如下:

ptr = realloc(ptr,size);

如果重新分配内存失败,通常是由于你刚刚用空指针覆盖了旧内存的指针,那么你通常会有一个内存泄漏问题。如果没有其他指向它的指针,就会出现内存泄漏。

因此,通常最好使用以下变体:

void *new_space = realloc(ptr, new_size);
if (new_space == NULL)
{
     /* …handle out of memory condition… */
     /* ptr is still valid and points to the previously allocated data */
     return; /* Or otherwise do not continue to the following code */
}
ptr = new_space;
size = new_size;

请注意,根据我以上所说的,地址可能与调用realloc()之前相同。


为了确保内存管理确实是以这种方式进行的,我们可以尝试这个实验:

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

int main(void)
{
    size_t length1 = 4;
    size_t length2 = 2;

    int *ptr1 = malloc(sizeof(*ptr1) * length1);
    if(ptr1 == NULL)
    {
         printf("The object could not be allocated!\n");
         return 1;
    }  

    printf("value (not address) of ptr1 before realloc(): %p\n", (void *)ptr1);

    ptr1 = realloc(ptr1,length2);

    if(ptr1 == NULL)
    {
         printf("No new object allocated. Old object remains!\n");
         return 1;
    }

    printf("value (not address) of ptr1 after realloc(): %p\n", (void *)ptr1);

    free(ptr1);

    return 0;
}

在我的尝试中,它输出了:

value (not address) of ptr1 before realloc(): 0x1db4010
value (not address) of ptr1 after realloc(): 0x1db4010

所以,在使用 realloc() 后,存储在 ptr1 中的地址与调用它之前相同。

附加说明:

  • ptrNULL 指针时,realloc() 的作用类似于 malloc()
int *ptr = NULL;
size_t length = 4;
ptr = realloc(ptr,sizeof(*ptr) * length);

与……具有同等效力,

int *ptr;
size_t length = 4;
ptr = malloc(sizeof(*ptr) * length);
如果ptr是一个null指针,realloc函数将为指定大小的内存行为类似于malloc函数。但是在我个人的看法中,你不应该首先使用realloc()来分配动态存储。相反,我建议您始终使用malloc()或其他分配内存管理函数。这可能会给未来的读者带来一些困难。
你不应该使用realloc(ptr,0)作为替代free(ptr)来释放动态内存,因为是否真正释放旧对象是实现定义的。
当大小为零并且新对象的内存未被分配时,实现定义了旧对象是否被释放。如果旧对象没有被释放,它的值将不变。请始终使用free()来释放动态分配的对象。

2
“当ptr是空指针时,realloc()可能会像malloc()一样运行[...]你不应该首先通过使用realloc()来分配动态存储。始终使用malloc()。”——好吧,这并不完全正确,正如手册页面所述,当使用NULL参数调用时,realloc()保证与malloc()完全相同。使用ptr = realloc(NULL, 0);来初始化指针是完全有效的,并且非常常见。请参见相关代码:https://code.woboq.org/userspace/glibc/malloc/malloc.c.html#3158。 - Marco Bonelli
@MarcoBonelli 我也思考了很长时间关于那个短语。但至少,我决定这样陈述它,因为它可能会导致某些人在任何代码中遇到这种行为,并且不知道实际上可以使用realloc()来分配对象,尝试寻找具有明显更合适的函数的相对初始化的动态内存对象。因此,我只是为了不引起任何不必要的问题而写下了这个。由于标准说它是可能和允许的,当然任何人都可以这样做。但我只是不建议这样做。 - RobertS supports Monica Cellio
1
是的,不要扔石头,因为你在梳理malloc/realloc的各种细微差别方面做得很好,但使用realloc进行初始分配也是可以的。这样做没有任何问题。当然,我理解你的想法,即最好先使用malloc/calloc进行分配,然后再调用realloc(对于新的C程序员来说可能更容易理解),但是所有这些都不能使使用realloc进行初始分配成为错误或不好的选择。(好的编辑--现在清楚了) - David C. Rankin
好的编辑。我建议将“realloc() may act as malloc()”改为“realloc() acts as malloc()”。 - Marco Bonelli
干得好。不过,我对这行代码有疑问:“在这种情况下,realloc()函数中会发生两个数据复制过程。” 你有任何关于这方面的引用吗?我在标准草案中没有找到相关内容。 - Bob__
显示剩余5条评论

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