Удаляет ли оператор free() адрес из динамической переменной?

Рассмотрим ниже программу:

int main ()
{
    int *p, *r;
    p = (int*)malloc(sizeof(int));

    cout<<"Addr of p  = "<<p <<endl;
    cout<<"Value of p = "<<*p <<endl;

    free(p);
    cout<<"After free(p)"<<endl;

    r = (int*)malloc(sizeof(int));
    cout<<"Addr of r  = "<<r <<endl;
    cout<<"Value of r = "<<*r <<endl;

    *p = 100;
    cout<<"Value of p = "<<*p <<endl;
    cout<<"Value of r = "<<*r <<endl;
    return 0;
}

Вывод:

Addr of p  = 0x2f7630
Value of p = 3111728
free(p)
Addr of r  = 0x2f7630
Value of r = 3111728
*p = 100
Value of p = 100
Value of r = 100

В приведенном выше коде p и r создаются динамически. p создается и освобождается. r создается после освобождения p. При изменении значения в p значение r также изменяется. Но я уже освободил память p, тогда почему при изменении значения p значение r также изменяется с тем же значение как у p?

Я пришел к следующему выводу. Прокомментируйте, прав ли я?

Объяснение. Переменные-указатели p и q объявляются динамически. Значения мусора сохраняются изначально. Переменная-указатель p освобождается/удаляется. Объявляется еще одна переменная-указатель r. Адреса, выделенные для r, такие же, как и для p (p по-прежнему указывает на старый адрес). Теперь, если значение p изменяется, значение r также изменяется на то же значение, что и p (поскольку обе переменные указывая на тот же адрес). Оператор free() только освобождает адрес памяти из переменной-указателя и возвращает адрес в операционную систему для повторного использования, но переменная-указатель (в данном случае p ) по-прежнему указывает на тот же старый адрес.


person pkthapa    schedule 13.08.2016    source источник
comment
К вашему сведению, free() - это не оператор, это функция. Это не влияет на ваше объяснение или ответ, но иногда в C++ стоит понять разницу.   -  person Steve Jessop    schedule 14.08.2016


Ответы (4)


Функция free() и оператор delete не изменяют содержимое указателя, так как указатель передается по значению.

Однако содержимое в месте, на которое указывает указатель, может быть недоступно после использования free() или delete.

Итак, если у нас есть адрес памяти 0x1000:

       +-----------------+  
0x1000 |                 |  
       | stuff in memory |  
       |                 |  
       +-----------------+  

Предположим, что переменная-указатель p содержит 0x1000, или указывает на ячейку памяти 0x1000.

После вызова free(p) операционной системе разрешается повторно использовать память по адресу 0x1000. Он может не использовать ее сразу или выделить память другому процессу, задаче или программе.

Однако переменная p не изменилась, поэтому она по-прежнему указывает на область памяти. В этом случае переменная p по-прежнему имеет значение, но вы не должны разыменовывать (использовать память), поскольку вы больше не владеете памятью.

person Thomas Matthews    schedule 14.08.2016

Ваш анализ внешне близок в некоторых отношениях, но неверен.

p и r определены как указатели в первом выражении main(). Они не создаются динамически. Они определены как переменные продолжительности автоматического хранения с main(), поэтому они перестают существовать, когда (фактически if, в случае вашей программы) возвращается main().

Это не p создается и освобождается. malloc() динамически выделяет память и, в случае успеха, возвращает указатель, который идентифицирует эту динамически выделенную память (или указатель NULL, если динамическое выделение не удается), но не инициализирует ее. Значение, возвращаемое malloc(), (после преобразования в указатель на int, который требуется в C++) присваивается p.

Затем ваш код печатает значение p.

(Я выделил следующий абзац курсивом, так как вернусь к нему ниже).

Следующий оператор выводит значение *p. Это означает доступ к значению по адресу, на который указывает p. Однако эта память не инициализирована, поэтому результатом обращения к *p является неопределенное поведение. С вашей реализацией (компилятором и библиотекой) в настоящее время это приводит к «значению мусора», которое затем печатается. Однако такое поведение не гарантируется — на самом деле оно может сделать что угодно. Различные реализации могут давать разные результаты, такие как ненормальное завершение работы (сбой вашей программы), переформатирование жесткого диска или [на практике гораздо менее вероятно] воспроизведение песни "Crash" примитивов через громкоговорители вашего компьютера.

После вызова free(p) ваш код проходит аналогичную последовательность с указателем r.

Присваивание *p = 100 имеет неопределенное поведение, так как p содержит значение, возвращенное первым вызовом malloc(), но оно было передано free(). Итак, что касается вашей программы, существование этой памяти больше не гарантируется.

Первый оператор cout после этого обращается к *p. Поскольку p больше не существует (будучи переданным free()), это дает неопределенное поведение.

Второй оператор cout после этого обращается к *r. Эта операция имеет неопределенное поведение по той же причине, что я описал в выделенном курсивом абзаце выше (для p, как это было тогда).

Обратите внимание, однако, что в вашем коде было пять случаев неопределенного поведения. Когда происходит хотя бы один случай неопределенного поведения, все ставки на возможность предсказать поведение вашей программы прекращаются. В вашей реализации результаты выводятся на печать p и r с одинаковым значением (поскольку malloc() возвращает одно и то же значение 0x2f7630 в обоих случаях), в обоих случаях печатается значение мусора, а затем (после оператора *p = 100) печатается значение 100 при печати *p и *r.

Однако ни один из этих результатов не гарантирован. Причина отсутствия гарантии заключается в том, что значение «неопределенного поведения» в стандарте C++ заключается в том, что стандарт не описывает ограничений на то, что разрешено, поэтому реализация может делать что угодно. Ваш анализ может быть правильным для вашей конкретной реализации, в конкретное время, когда вы скомпилировали, скомпоновали и запустили свой код. Это может быть даже правильно на следующей неделе, но неверно через месяц после обновления вашей стандартной библиотеки (например, применения исправлений ошибок). Вероятно, это неверно для других реализаций.

Напоследок пара незначительных моментов.

Во-первых, ваш код неполный и даже не скомпилируется в том виде, в котором вы его описали. В обсуждении выше я предположил, что вашему коду на самом деле предшествует

#include <iostream>
#include <cstdlib>

using namespace std;

Во-вторых, malloc() и free() — это функции из стандартной библиотеки. Они не операторы.

person Peter    schedule 14.08.2016

Ваш анализ того, что на самом деле произошло, верен; однако программа не гарантирует не надежное поведение. Каждое использование p после free(p) "провоцирует неопределенное поведение". (Это также происходит, когда вы обращаетесь к *p и *r, ничего не записав туда предварительно.) Неопределенное поведение хуже, чем просто получение непредсказуемого результата, и хуже, чем просто потенциальное причинение краш программы, поскольку компилятору явно разрешено предполагать, что код, вызывающий неопределенное поведение, никогда не будет выполняться. Например, для компилятора было бы правильно рассматривать вашу программу как идентичную

int main() {}

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

person zwol    schedule 13.08.2016
comment
Вы не можете спровоцировать неопределенное поведение, но оно может быть у программы. - person Lightness Races in Orbit; 14.08.2016

free() освобождает память кучи для повторного использования ОС. Но содержимое, присутствующее в адресе памяти, не стирается/удаляется.

person pkthapa    schedule 29.11.2017