Должен ли я принудительно выполнять проверку перераспределения, если размер нового блока меньше исходного?

StackOverflow https://stackoverflow.com/questions/2541215

  •  23-09-2019
  •  | 
  •  

Вопрос

Может ли в этом случае Realloc потерпеть неудачу?

int *a = NULL;

a = calloc(100, sizeof(*a));
printf("1.ptr: %d\n", a);

a = realloc(a, 50 * sizeof(*a));
printf("2.ptr: %d\n", a);

if(a == NULL){
    printf("Is it possible?\n");
}

return (0);

}

Вывод в моем случае:

1.ptr: 4072560
2.ptr: 4072560

Таким образом, «а» указывает на один и тот же адрес.Так следует ли мне применять проверку перераспределения?

Позже отредактировать:

  • Использование компилятора MinGW под Windows XP.
  • Аналогично ли поведение gcc в Linux?

Позже отредактировать 2:Можно ли проверить таким способом?

int *a = NULL, *b = NULL;

a = calloc(100, sizeof(*a));
b = realloc(a, 50 * sizeof(*a));

if(b == NULL){
    return a;
}
a = b;
return a;
Это было полезно?

Решение

Да, вам всегда следует принудительно проверять realloc или любое другое распределение памяти в этом отношении.

Текущее поведение повторного использования одного и того же адреса — это деталь реализации, на которую не следует полагаться.Поступая так, вы просто открываете для себя ошибки, когда либо библиотека меняет свою реализацию, либо вы переходите на новую платформу.

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

void* xrealloc(void* ptr, size_t size) {
  ptr = realloc(ptr, size);
  if ( !ptr ) {
    exit(EXIT_FAILURE);
  }
  return ptr;
}

Другие советы

Было бы удивительно, если бы realloc не удалось передать размер, меньший, чем исходное выделение, но ничто в стандарте C (7.20.3.4) не гарантирует, что это всегда будет успешным:

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

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

Возврат

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

Очень простая соответствующая реализация realloc было бы это:

void *realloc(void *ptr, size_t size)
{
    void *new_ptr= malloc(size);
    if (new_ptr && ptr)
    {
        size_t original_size= _get_malloc_original_size(ptr);
        memcpy(new_ptr, ptr, min(original_size, size));
        free(ptr);
    }

    return new_ptr;
}

При нехватке памяти (или любых условиях, при которых malloc вернусь NULL), это вернет NULL.

Также было бы очень простой оптимизацией возвращать тот же указатель, если размер исходного выделения больше или равен запрошенному размеру.Но ничто в стандарте C не требует этого.

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

Стандарт C99 §7.20.3.4 (realloc) гласит:

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

Если PTR является нулевым указателем, функция Realloc ведет себя как функция Malloc для указанного размера.В противном случае, если PTR не соответствует указателю, ранее возвращаемому функцией Calloc, Malloc или Realloc, или если пространство было решено по вызову свободной или реальной функции, поведение не определен.Если память для нового объекта не может быть распределена, старый объект не имеет съемки, а его значение не изменяется.

Возврат

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

Обратите внимание, что старый объект освобождается;новый объект может случайно указывают на то же место, что и старый.И могут быть проблемы.Это маловероятно, но гораздо проще придерживаться правила «всегда», чем иметь странные исключения.

Обычный контраргумент: «если это не может потерпеть неудачу, это означает, что у меня есть путь ошибки, который я не могу проверить».До определенного момента это правда.Однако возможно, что произошло некоторое перераспределение памяти, поэтому выделение не может быть выполнено, поскольку управляющая информация была повреждена.Скорее всего, вы просто получите дамп ядра, но, возможно, код достаточно надежный, чтобы этого избежать.(Я предполагаю, что жестко закодированные 100 и 50 предназначены для того, чтобы задать вопрос;реальный код не будет выделять слишком много памяти, если знает, сколько ему действительно нужно.)

Там, где два вызова «realloc()» соседствуют, как здесь, очень мало места для того, чтобы что-то пошло не так.Однако в реальном рабочем коде между этими двумя операциями могут быть некоторые промежуточные операции, и этот код может привести к сбою второго «realloc()».

Что касается вашего «Редактировать 2»…

Код лучше написать так:

if (b != NULL)
    a = b;
return a;

Но основная концепция в порядке.Обратите внимание, что стандарт прямо говорит, что исходное распределение безопасно, если новое не может быть создано.

Время, необходимое для проверки, настолько мало по сравнению со временем, затрачиваемым на realloc(), что я даже не понимаю, почему это может быть проблемой.Или вы хотите уменьшить количество строк кода?

realloc() могу вернуться NULL достаточно легко уменьшить размер.

void *ptr = malloc(10);
ptr = realloc(ptr, 0);
if (ptr == NULL) {
  puts("Failure because return value is NULL? - not really");
}

realloc(any_pointer, 0) мог бы вернуться NULL или, может быть, некоторые not-NULL указатель, он определяется реализацией.

Поэтому realloc()/malloc() неудача не должна быть простой проверкой if (ptr == NULL) но

void *ptr = malloc(newsize); // or realloc(..., newsize)
if (ptr == NULL && newsize > 0) {
  exit(0); // Handle OOM;
}

Из-за этой двусмысленности, если код захочет создать realloc() обертка, порекомендуйте что-то вроде:

void *xrealloc(void *ptr, size_t newsize, bool *falure) {
  *failure = 0;
  if (newsize > 0) {
    void *tmp = realloc(ptr, newsize);
    if (tmp == NULL) {
      *failure = 1;
      return ptr;  // old value
    }
    return tmp;  // new value
  } 
  free(ptr);
  return NULL; // new value
  }

Получающий NULL на realloc() с уменьшенным размером, поэтому на самом деле это не отказ и поэтому этот ответ применим лишь косвенно, но вопрос ОП был «...принудительно выполнить проверку перераспределения, если размер нового блока меньше исходного?», а затем использовал менее надежный if (ptr == NULL) парадигма.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top