Подтвердить что ты не робот

Действительно ли realloc (p, 0) включает в себя free (p) в glibc?

Я обнаружил, что некоторые люди и ссылки, подобные книгам, заявляют, что если p != NULL и p происходят из предыдущего распределения (например, malloc), то realloc(p, 0) эквивалентно free(p) в GNU/Linux. Чтобы поддержать этот тезис man realloc, указывается именно таким образом (акцент мой вперед):

Функция realloc() изменяет размер блока памяти, на который указывает от ptr до размера байтов. Содержимое будет оставаться неизменным в диапазоне от начало региона до минимума старого и нового размеров. Если новый размер больше старого, добавленная память не будет инициализируется. Если ptr равно NULL, то вызов эквивалентен malloc (размер) для всех значений размера; , если размер равен нулю, и ptr не является NULL, тогда вызов эквивалентен свободному (ptr). Если ptr NULL, он должен быть возвращен предыдущим вызовом malloc(), calloc() или realloc(). Если указанная область была перемещена, свободный (ptr) делается.

Как вы можете найти в этом вопросе, стандарт C не определяет точно, что должно произойти, а фактическое поведение определяется реализацией. Более конкретно:

C11 §7.22.3/p1 Функции управления памятью говорят:

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

и C11 §7.22.3.5 Функция realloc содержит:

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

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

Я написал базовый код, чтобы узнать фактическое поведение с помощью mcheck, средство проверки памяти, которое поставляется с glibc

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

int main(void)
{
    int a = 5;
    int *p, *q;

    mtrace();

    p = malloc(sizeof(int));
    q = &a;

    printf("%p\n", (void *) p);
    printf("%p\n", (void *) q);

    q = realloc(p, 0);

    printf("%p\n", (void *) p);
    printf("%p\n", (void *) q);

    return 0;
}

и результаты:

$ gcc -g check.c 
$ export MALLOC_TRACE=report
$ ./a.out 
0xfd3460
0x7ffffbc955cc
0xfd3460
(nil)
[[email protected] workspace]$ mtrace a.out report 

Memory not freed:
-----------------
           Address     Size     Caller
0x0000000000fd3460      0x4  at /home/grzegorz/workspace/check.c:12

Как вы можете видеть, q был установлен в NULL. Кажется, что free() на самом деле не назывался. На самом деле это не может быть, если моя интерпретация неверна: поскольку realloc возвратил указатель NULL, новый объект не мог быть выделен, что подразумевает, что:

старый объект не освобождается и его значение не изменяется

Правильно ли это?

4b9b3361

Ответ 1

Изменить: ваш glibc, кажется, был до 2.18, в 2.18 ошибка была исправлена ​​в mtrace (см. здесь). На 2.20 glibc ваша тестовая программа сообщает: "Нет утечек памяти".

free вызывается в glibc. Из источников текущего glibc 2.21 (здесь и здесь)

/*
  REALLOC_ZERO_BYTES_FREES should be set if a call to
  realloc with zero bytes should be the same as a call to free.
  This is required by the C standard. Otherwise, since this malloc
  returns a unique pointer for malloc(0), so does realloc(p, 0).
*/

#ifndef REALLOC_ZERO_BYTES_FREES
#define REALLOC_ZERO_BYTES_FREES 1
#endif

void *
__libc_realloc (void *oldmem, size_t bytes)
{
  mstate ar_ptr;
  INTERNAL_SIZE_T nb;         /* padded request size */

  void *newp;             /* chunk to return */

  void *(*hook) (void *, size_t, const void *) =
    atomic_forced_read (__realloc_hook);
  if (__builtin_expect (hook != NULL, 0))
    return (*hook)(oldmem, bytes, RETURN_ADDRESS (0));

#if REALLOC_ZERO_BYTES_FREES
  if (bytes == 0 && oldmem != NULL)
    {
      __libc_free (oldmem); return 0;
    }
#endif

Ответ 2

Хотя моя интуиция "возвращаемого NULL" случая кажется правильной (см. мое правление ниже), glibc разработчики решили чтобы он соответствовал предыдущему стандарту C89 и отклонялся в соответствии с C99/C11:

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

Также индикация mcheck вводила в заблуждение, поскольку другой тестовый пример показал, что память эффективно освобождается realloc:

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

int main(void)
{
    int *p, *q;

    p = malloc(20 * sizeof(int));
    malloc_stats();

    putchar('\n');

    q = realloc(p, 0);
    malloc_stats();

    return 0;
}

Здесь вывод:

$ gcc check.c 
$ ./a.out 
Arena 0:
system bytes     =     135168
in use bytes     =         96
Total (incl. mmap):
system bytes     =     135168
in use bytes     =         96
max mmap regions =          0
max mmap bytes   =          0

Arena 0:
system bytes     =     135168
in use bytes     =          0
Total (incl. mmap):
system bytes     =     135168
in use bytes     =          0
max mmap regions =          0
max mmap bytes   =          0

EDIT:

Как указано в comment, рабочая группа ИСО/МЭК имела некоторое обсуждение, которое было представлено как Отчет об ошибках # 400. Предлагаемые изменения могут, вероятно, разрешить существующую практику glibc в будущем пересмотре стандарта C (или, возможно, в качестве технического исправления 1 для C11).

Что мне действительно нравится в DR # 400, является предложение:

Добавить в подраздел 7.31.12 новый пункт (пункт 2):

Вызов realloc с аргументом размера, равным нулю, является устаревшей функцией.

Ответ 3

Как поведение realloc() при передаче размера 0 определяется имптенентацией...

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

... портативный эквивалент

void * p = malloc(1);
free(p);

должно быть

void * p = malloc(1);
p = realloc(p, 0)
free(p); /* because of the part after the "or" as quoted above.

Баланс памяти должен быть даже после.

Обновить покрытие realloc() "ошибка":

void * p = malloc(1);
{
  void * q = realloc(p, 0);
  p = q ?q :p;
}
free(p); /* because of the part after the "or" as quoted above.