Использование указателя после free()
Во время моих тестов я обнаружил, что можно использовать указатель после free(). У меня есть следующий код:
typedef struct{
int module_id;
int adc_id;
struct config_line * pnext;
} config_line;
config_line * create_list()
{
config_line * phead = (config_line *) malloc(sizeof(config_line));
phead->pnext=NULL;
phead->module_id = 1;
phead->adc_id = 2;
printf("module_id=%d adc_id=%d\n",phead->module_id, phead->adc_id);
free(phead);
printf("module_id=%d adc_id=%d\n",phead->module_id, phead->adc_id);
phead->module_id = 2;
phead->adc_id = 5;
printf("module_id=%d adc_id=%d\n",phead->module_id, phead->adc_id);
}
Выход этого кода:
module_id=1 adc_id=2
module_id=0 adc_id=2
module_id=2 adc_id=5
Почему после свободного (phead) я могу получить доступ (чтение и запись) к указателю? Почему нет ошибки сегментации?
6 ответов
Потому что использование неверного указателя вызывает неопределенное поведение. А это значит, что поведение... ну... не определено. Это не обязано падать.
Когда вы звоните free(phead)
, память phead
указывает на освобождение, но ценность phead
остается нетронутым, делая phead
свисающий указатель. Доступ к памяти, которая уже была освобождена, приводит к неопределенному поведению.
Это хорошая практика для назначения NULL
на указатель, когда вы освободите память, он указывает на:
free(phead);
phead = NULL;
Потому что память все еще отображается в вашем процессе, но не выделяется. В программе на C существует два уровня управления памятью: во-первых, ядро предоставляет вам страницы, на которые вы можете писать. Если процессу требуется больше памяти, которую он отобразил, он должен запросить больше у ядра (sbrk). Однако это происходит большими кусками, поэтому malloc нарезает их на куски для вас, запрашивая больше страниц по мере необходимости, но используя, где это возможно, память, уже выделенную для программы. free не может вернуть страницу, пока все ресурсы, которые ее использовали, не будут освобождены.
Итак, нарушения доступа к памяти, которые дает вам ядро (SIGSEGV), довольно грубые и не могут выявлять большинство ошибок памяти, только то, что фатально с точки зрения ядра. Вы можете очистить данные отслеживания вашего malloc, прочитать после окончания большинства выделений и после многих освобождений и так далее.
Не допускайте ошибок памяти. Запустите ваше приложение с помощью valgrind, который использует очень строгую виртуальную машину, чтобы проверить все ошибки и немедленно устранить их.
Почему курица может продолжать бегать и прыгать, несмотря на то, что ее голову отрубают? Обратите внимание, что это не всегда происходит; некоторые цыплята могут немедленно перестать двигаться, в то время как другие могут бегать месяцами. Когда это происходит, это происходит потому, что это происходит. Значит ли это, что мы должны отрезать головы нашим питомцам?
Подобно тому, как отрубать головы нашим питомцам, использование освобожденной памяти - плохая идея. Это не значит, что это приведет к отрицательным результатам; ваша программа может продолжать работать, как и ожидалось, на вашем компьютере, в то время как if (fubar)
(где fubar - ваш неверный указатель) может быть достаточно, чтобы ваша программа работала со сбоями вдругих реализациях.
Эта идея свободы реализации для определения (или нет) поведения формально известна как неопределенное поведение; поведение не определено, потому что стандарт C, набор документов, определяющих поведение реализаций C, не определяет поведение. Поэтому, как и отрубать головы нашим питомцам, мы должны избегать неопределенного поведения.
Потому что освобождение памяти (указатель) просто возвращает эту память в пул свободной памяти. Память не просто исчезает, и ее содержимое не очищается до тех пор, пока эта память не будет снова выделена и записана.
Таким образом, очень возможно получить доступ к памяти после ее освобождения. Просто не очень хорошая идея, как вы можете себе представить, потому что она может быть выделена в любое время и изменена.
Данные освобождаются во время вызова. Доступ к указателю после вызова дает так называемый «висячий указатель», это неопределенное поведение, и все может случиться. Что такое неопределенное поведение и как оно работает?В частности, это UB из-за последнего предложения C17 7.22.3.3:
void free(void *ptr);
Функция приводит к тому, что пространство, на которое указывает , освобождается, то есть становится доступным для дальнейшего выделения. Если
ptr
является нулевым указателем, никаких действий не происходит. В противном случае, если аргумент не соответствует указателю, ранее возвращенному функцией управления памятью, или если пространство было освобождено вызовомfree
или жеrealloc
, поведение не определено.
В общем, в оперативной памяти компьютеров нет волшебного «стертого состояния». Значения будут сохраняться в памяти до тех пор, пока эта ячейка не будет использована для чего-то другого. «Стирание» чего-либо, просто отказ от владения этими ячейками памяти.
Хорошая практика писать
NULL
на указатели после вызова
free()
по этой причине, чтобы пометить указатель как не указывающий ни на что действительное.