Как reallo c () перераспределяет память? - PullRequest
3 голосов
/ 14 января 2020

Как realloc() перераспределяет память, которая была впервые выделена malloc()?

Я знаю, что вам нужно использовать malloc(), прежде чем вы сможете перераспределить память, но я не понимаю, как это на самом деле должно работать. Что делать, если размер динамического c -памяти уменьшается на realloc()? Эта соответствующая часть объекта была просто стерта после вызова realloc()?


Мой вопрос:

  1. Как функция realloc() перераспределяет динамику c объект памяти, созданный malloc()?

Примечание: я сделал эти вопросы и ответы, потому что многие новички, похоже, все еще не понимают проблему перераспределения памяти с использованием realloc(), несмотря на уже существующие здесь вопросы по SO для этой темы c. Они кажутся немного запутанными для любого, кто новичок в топи c и все еще не представляют всего поведения realloc(). Поэтому, и поскольку вопросы, ИМХО, все еще не совсем соответствуют ответу, который я хотел бы дать, я сделал свои собственные вопросы и ответы.

1 Ответ

5 голосов
/ 14 января 2020

Примечание. Все цитаты в следующем ответе приведены в соответствии с действующим стандартом C, ISO / IEC 9899: 2018 (C18), раздел 7.22.3.4.


Во-первых, краткий обзор функции realloc() из ISO / IEC 9899: 2018, раздел 7.22.3:

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

Несмотря на название, функция realloc() ничего не " re выделяет". realloc() - это , а не изменение существующего объекта в памяти. Вместо этого он выполняет своего рода процедуру «создания (нового объекта) и копирования данных».


Если size не равно 0 и ptr либо указывает на объект, который был выделен с помощью одной из функций управления памятью (не только malloc()) или указывает на NULL, realloc() обычно создает новый объект и копирует данные из старого объекта в новый объект.

* Я говорю , обычно , потому что вы не можете предположить, что новый объект в памяти действительно выделен. Вы должны всегда проверять, был ли он выделен, проверяя, указывает ли возвращаемый указатель на NULL.


Если размер нового объекта больше старого объекта, байты нового Объект, размер которого превышает размер старого объекта, имеет неопределенные значения. Если новый объект короче старого объекта, значения внутри разницы между ними отбрасываются. Любое другое значение остается в новом объекте так же, как и в старом.

Содержимое нового объекта должно быть таким же, как и у старого объекта до освобождения, до меньшего из новые и старые размеры. Любые байты в новом объекте, превышающие размер старого объекта, имеют неопределенные значения.


После этого , если :

  • ptr - это , а не указатель на NULL и - это указатель, ранее возвращенный функцией управления памятью, и объект, на который указывает этот указатель, не был освобожден до вызова realloc(),

    Если ptr является нулевым указателем, функция reallo c ведет себя как функция mallo c для указанного размера. В противном случае, если ptr не соответствует указателю, ранее возвращенному функцией управления памятью, или если пространство было освобождено путем вызова функции free или reallo c, поведение не определено.

  • size не 0,

    Если размер равен нулю и память для нового объекта не выделена, это реализация -определено, освобожден ли старый объект. Если старый объект не освобожден, его значение должно быть неизменным.

  • и новый объект действительно может быть размещен, если realloc() не вернул указатель на NULL ,

    Если размер не равен нулю и память для нового объекта не выделена, старый объект не освобождается

и действительно только если все из этих посылок выполнены, realloc() освобождает память старого объекта и возвращает указатель с адресом нового объекта в памяти.

reallo c функция освобождает старый объект, на который указывает ptr, и возвращает указатель на новый объект, размер которого указан как size.

Если realloc() возвращает указатель на NULL, новый объект не создается, и старый объект остается неизменным по его адресу в памяти.


При желании, чтобы сделать поведение "псевдо-перераспределения" почти идеальным, возможно, что новый объект после освобождение старого объект завершен (если это происходит), размещается по тому же адресу в памяти, где был сохранен старый объект.

Функция reallo c возвращает указатель на новый объект (который может имеют то же значение, что и указатель на старый объект), или нулевой указатель, если новый объект не был выделен.

В этом случае логически существует два процесса копирования данных в 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

Таким образом, адрес, сохраненный в ptr1 после использования realloc(), эквивалентен перед вызовом этого.

Дополнительные примечания:

  • realloc() действует как malloc(), когда ptr является указателем NULL:
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 является нулевым указателем, функция reallo c ведет себя как функция mallo c для указанного размера.

Но, по моему личному мнению, вы не должны сначала выделять динамическое c хранилище с помощью realloc(). Я рекомендую всегда использовать malloc() или другую функцию управления памятью. Это может вызвать некоторые трудности у будущих читателей.


  • Не следует использовать realloc(ptr,0) в качестве замены free(ptr) для освобождения динамической памяти c, поскольку она определяется реализацией, независимо от того, старый объект действительно освобожден или нет.

Если размер равен нулю и память для нового объекта не выделена, определяется реализацией, был ли освобожден старый объект. Если старый объект не освобожден, его значение должно быть неизменным.

Всегда использовать free() для освобождения динамически размещенного объекта.

...