Что будет делать realloc со старым указателем
У меня есть вопрос по поводу функции realloc. Будет ли изменено содержимое старого указателя после применения функции realloc? Код
main () {
int *a, *b, i;
a = calloc(5, sizeof(int));
for (i = 0; i < 5; i++)
a[i] = 1;
for (i = 0; i < 5; i++)
printf("%d", a[i]);
printf("\n%p\n", a);
b = realloc(a, 200000 * sizeof(int));
if(b == NULL)
printf("error\n");
for (i = 0; i < 5; i++)
printf("%d", a[i]);
printf("\n");
for (i = 0; i < 10; i++)
printf("%d", b[i]);
printf("\n%p %p\n", a, b);
}
Выход
11111
0x2558010
00111
1111100000
0x2558010 0x7f29627e6010
Указатель еще указывает на тот же адрес, но содержимое меняется.
6 ответов
Указатель еще указывает на тот же адрес, но содержимое меняется.
Это потому что realloc()
может сначала попытаться увеличить размер блока, который a
указывает на. Однако вместо этого он может выделить новый блок, скопировать данные (или столько данных, сколько поместится) в новый блок и освободить старый блок. Вы действительно не должны использовать a
после звонка b = realloc(a, 200000 * sizeof(int))
так как realloc
вызов может переместить блок на новое место, оставив a
указывая на память, которая больше не выделяется. использование b
вместо.
Значение, возвращаемое realloc
говорит вам, удалось это или не удалось.
b = realloc(a, 200000 * sizeof(int));
Если это не удается, он возвращает нулевой указатель, и a
все еще указывает на оригинальный неизмененный кусок памяти (и, конечно, b
нулевой указатель).
Если это удастся, то b
указывает на (возможно, недавно выделенный) кусок памяти, а также значение a
является неопределенным. Если он смог разместить новый блок в том же месте, что и старый (путем увеличения или уменьшения фрагмента на месте), то b
будет равен a
- но проверяя это, или даже ссылаясь на значение a
, имеет неопределенное поведение. Если он должен переместить кусок, то realloc
будет сделано эквивалент free(a)
после копирования данных. В любом случае, вероятно, лучше установить a
в NULL
чтобы избежать случайного обращения к его (теперь неопределенному) значению.
Обратите внимание, что realloc
можно переместить чанк, даже если новый размер меньше.
Просто realloc
Реализация должна ответить на ваши вопросы:
void * realloc(void * ptr, size_t desired_size) {
size_t allocated_size = _allocated_size_of(ptr);
if (allocated_size < desired_size) {
void * new_ptr = malloc(desired_size);
memcpy(new_ptr, ptr, allocated_size);
free(ptr);
ptr = new_ptr;
}
return ptr;
}
malloc
и связанные функции не всегда выделяют точно желаемый размер. Очень часто они выделяют больше желаемого размера. Есть некоторые скрытые данные, сохраняемые с помощью функций выделения памяти, которые позволяют указатель, который был выделен malloc
или связанные функции, которые будут использоваться для поиска размера блока памяти, который был выделен. Не нужно понимать, как с этим справляться, но некоторые очень простые реализации просто сохраняют размер в пространстве непосредственно перед возвращением указателя. *(((size_t)ptr)-1)
,
Если "a" указывает на допустимый блок памяти (из предыдущего malloc/realloc/calloc), то вызов realloc попытается предоставить блок памяти с новым размером, который вы запросили
Вызов realloc должен иметь вид *tmp = realloc (a ...
Возвращаемое значение из realloc должно быть проверено
Если это NULL, realloc не смог выделить запрошенную память, и это оставляет "a" в качестве допустимого указателя
Затем вы несете ответственность за обработку любых данных, на которые указывает "а" (сохраните их / отбросьте их), и вы несете ответственность за free
память, на которую указывает "а"
Если вызов realloc был успешным, сделайте b = tmp
и теперь "b" является новым указателем на блок памяти - не имеет значения, является ли начальная позиция такой же, как "a" или другой. "a" больше не является действительным указателем выделения памяти, хотя дальнейшие ошибки будут зависеть от того, указывает "a" на память, принадлежащую вашей программе, или нет - в основном, если a == b, "a" можно получить без явных ошибок.
После действительного *tmp = realloc(a ...
& b = tmp;
:
1) Если начальное местоположение перераспределенной памяти не изменилось: (a == b)
он выделит запрошенную память
но запустите его под valgrind, и вы увидите сообщения об ошибках:
Недопустимо free() / delete / delete[] / realloc()
Адрес 0x51fc040 составляет 0 байтов внутри блока размером 256 free'd
В этом случае realloc не может освободить память, на которую указывает "a"
и снова в этом случае можно получить доступ к "а", так как это указатель на память, выделенную вашей программе
2) Если начальное местоположение перераспределенной памяти было изменено: (a!= B)
это потерпит неудачу, и Valgrind покажет вывод, как это
адрес: 0x1e89010
адрес b: 0x7f2c5893c010
после перераспределения: 0x1e89010
Ошибка в `./test15': realloc(): недопустимый старый размер: 0x0000000001e89010
и попытка получить доступ к "a" не удастся - даже попытка напечатать его значение, так как указатель потерпит неудачу, возможно потому, что он больше не указывает на память, принадлежащую программе
Другими словами, используя "а" после b = realloc(a ...
является неопределенным поведением.
Приведенный выше комментарий основан на использовании следующего кода:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *a = NULL, *b = NULL, *c = NULL;
/* initial allocation */
a = malloc(256);
if( a == NULL) return (1);
printf("address of a: %p\n", a);
/* reallocation 'b' MAY be same as 'a' - try much larger allocations */
void *tmp = realloc(a, 512);
if ( !tmp ) {
free(a);
return (1);
} else {
b = tmp;
}
printf("address of b: %p\n", b);
/* see what 'a' is now - this MAY crash the program*/
printf("a after realloc: %p\n", a);
/* 'a' may not be a valid pointer - try using it for another realloc */
c = realloc(a, 256);
/* Valgrind shows that memory could not be free'd or 'a' was not valid allocated memory */
printf("return value of c: %p\n", c);
if (c != NULL) {
free(c);
printf("'c' allocated\n");
} else {
free(b);
printf("'c' not allocated\n");
}
return 0;
}
Чтение man-страницы является ключевым моментом здесь, но TLDR - если недостаточно памяти для увеличения в задней части предыдущего блока, он получит новый блок памяти, скопирует в него старые данные и вернет адрес нового блока. Старый адрес не должен использоваться, и наиболее типичный оператор realloc выглядит следующим образом
a = realloc(a, 200000 * sizeof(int));
Таким образом, вы не сможете случайно использовать возможно неправильное старое значение.
Он не может изменить адрес в указателе, так как он передается по значению, поэтому изменение его в функции - это только изменение локальной копии.
РЕДАКТИРОВАТЬ: Per Weather Vane абсолютно правильный комментарий, более безопасный маршрут будет
void * b = realloc(a, 200000 * sizeof(int));
if ( b ) {
a = b;
} else {
;;; /* error handler here */
}
Если realloc()
возвращает указатель, отличный от того, который вы передали (как это будет происходить большую часть времени), затем указатель, который вы передали, больше не принадлежит вам, и вы не имеете никакого дела, зная или заботясь о том, что с ним происходит. Это может изменить его содержимое, а может и нет. Но вам больше не разрешен доступ к нему, так что это может вас не беспокоить.