2014 dxdy logo

Научный форум dxdy

Математика, Физика, Computer Science, Machine Learning, LaTeX, Механика и Техника, Химия,
Биология и Медицина, Экономика и Финансовая Математика, Гуманитарные науки




Начать новую тему Ответить на тему На страницу 1, 2  След.
 
 освобождение динамически выделенной памяти в С++
Сообщение22.01.2008, 13:54 
Аватара пользователя


02/07/07
163
Харьков
Недавно узнал, что функция delete в С++ не удаляет выделение памяти, а просто обнуляет значения. Например, такой код компилируется и работает, хоть это и нежелательно.
Код:
double **m=new double*[2];

for(int i=0;i<2;i++)
  m[i]=new double [2];
m[1][1]=2;

Edit1->Text=m[1][1];

for(int i=0;i<2;i++)
delete [] m[i];
delete [] m;

Edit2->Text=m[1][1];

В Edit2 выводится число, близкое к нулю.
Мне непонятно, для чего тогда предназначается функция delete.

 Профиль  
                  
 
 
Сообщение22.01.2008, 17:14 
Заслуженный участник
Аватара пользователя


01/08/06
3132
Уфа
"Выделить память", "Освободить память" --- в C это означает просто "пометить блок памяти как выделенный и вернуть указатель на него" и "пометить выделенный блок памяти как свободный", только и всего. Несмотря на то, что блок освобождён, к нему по-прежнему можно обратиться. Правда, содержимое будет, вообще говоря, случайным. То, что у Вас получился 0 или близкое к нулю число --- это случайность.
Asalex писал(а):
для чего тогда предназначается функция delete
Для того, чтобы пометить блок памяти как свободный, чтобы в следующий раз при выделении памяти этот блок мог быть вновь выделен. Примерно так.

 Профиль  
                  
 
 
Сообщение22.01.2008, 17:25 


21/03/06
1545
Москва
Вообще говоря, в терминых ООП и C++, оператор new выделяет память из кучи под экземпляр соответствующего класса (метит как занятую часть памяти, равную совокупному объему всех занимающих память переменных класса), и запускает конструктор этого класса. Оператор delete, соответственно, запускает деструктор данного класса, и возвращает в кучу (метит как свободную) часть памяти объекта.

Экземпляры массивов (да и любых базовые классы) с точки зрения ООП лучше рассамтривать как объекты, для которых просто невозможна перегрузка операторов. Так правильнее и понятнее будет.

 Профиль  
                  
 
 
Сообщение22.01.2008, 22:09 
Заслуженный участник
Аватара пользователя


17/10/05
3709
:evil:
Мой грошик:

Вы, Asalex, узнали не то, что память не удаляется, а то, что операция delete не изменяет значения переменной, хранящей указатель. Что может привести к так называемым «висячим ссылкам», одной из которых Вы и воспользовались для своего примера.

(1) Терминология. Названия операторов вводят в заблуждение, но new — это создать объект, состоящее из выделения памяти и вызова конструктора, а delete — уничтожить объект, т.е. вызвать деструктор и освободить память. Заметьте, выделить и освободить, а не удалить.

(2) (Слегка упрощая: ) операция new возвращает значение–указатель, а операция delete получает указатель как значение. Обратите внимание — значение в обоих случаях. Это, в частности означает, что если Вы храните указатель в переменной, то значение переменной не изменится при уничтожении объекта. Что иногда больно и обидно вспоминать.

(3) После освобождения память содержимое памяти непредсказуемо. В частности, она может быть использована повторно.
Код:
int* a = new int();
*a = 5;
delete a;

int * b = new int();
*b = 3;
printf("a = %d, b = %d", *a, *b);


(4) Ещё одна популярная ошибка, связанная с висячими ссылками, это повторное освобождение памяти. В простых случаях это очевидно:
Код:
int* a = new int();
*a = 5;
delete a;
delete a;
Но есть и более интересные варианты:
Код:
int* a = new int();
int* b = a;
*a = 5;
delete a;

int * c = new int();
*c = 3;

delete b;

int *d = new int();
*d = 7;
printf("c = %d d = %d", *c, *d);
Здесь в глаза не бросается, что c был непреднамеренно удалён, и выглядит так, как будто значение c поменялось само собой.

 Профиль  
                  
 
 
Сообщение22.01.2008, 23:23 


21/03/06
1545
Москва
незваный гость писал(а):
...Это, в частности означает, что если Вы храните указатель в переменной, то значение переменной не изменится при уничтожении объекта. Что иногда больно и обидно вспоминать.

Гы, весьма эмоционально :).

Добавлю еще чуть-чуть для полноты картины: для собственных классов операторы new и delete можно еще и перегрузить. И, в принципе, вот тогда и становится возможным заполнение освобожденной памяти нулями, т.н. "умные" указатели, которые в большинстве случаев умеют сами определять, был ли уже удален объект или нет при попытке удаления и т.д. и т.п, а также, что очень немаловажно, создание собственных схем управления памятью кучи, которые могут быть гораздо более интеллектуальными, чем стандартное выделение/освобождение/запрос системы о новом куске памяти Си++.

 Профиль  
                  
 
 
Сообщение22.01.2008, 23:46 
Заслуженный участник
Аватара пользователя


17/10/05
3709
:evil:
Для случаев тяжёлого ожирения (полноты полноты) картины:

(1) new и delete можно переопределить не только для своих классов, но и глобально. Этим очень часто пользуются во встроенных системах, где стандартные alloc()/free() недоступны или нежелательны (представьте себе АСУ АЭС, которой не хватило памяти!).

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

(2) Заполнять при освобождении лучше не нулями, а чем-либо более экзотическим, например, '\xA5'. В этом при использовании без инициализации произойдёт две ошибки — выравнивания и адреса. А вот 0 обычно вполне законный адрес.

С другой стороны, вся эта чехарда всё равно происходит перед освобождением памяти. Поэтому содержимое памяти может измениться при free().

(3) Все эти «умные» указатели — это переменные, а не значения (smartptr<T> ptr вместо T* ptr). В частности, они автоматически обнуляют хранимое значение при освобождении. И либо считают ссылки, либо не позволяют копировать указатель. Строго говоря, к new/delete это относится косвенно. А платить приходится непосредственно: и в собственно умных указателях, и в организации программы вокруг них.

 Профиль  
                  
 
 Re: освобождение динамически выделенной памяти в С++
Сообщение26.01.2008, 13:50 
Заслуженный участник


14/12/06
881
А я нашёл, что ещё можно добавить.

Asalex писал(а):
Недавно узнал, что функция delete в С++ не удаляет выделение памяти, а просто обнуляет значения.


worm2 писал(а):
Выделить память", "Освободить память" --- в C это означает просто "пометить блок памяти как выделенный и вернуть указатель на него" и "пометить выделенный блок памяти как свободный", только и всего.


А обнулять память нужно по соображениям безопастности, иначе, в принципе, есть возможность одной программе прочитать данные другой (пароль, например).
Современные стандарты требуют, чтобы операции, освобождающие память, обнуляли её.

 Профиль  
                  
 
 
Сообщение26.01.2008, 20:07 


21/03/06
1545
Москва
Цитата:
Современные стандарты требуют, чтобы операции, освобождающие память, обнуляли её.


Совершенно не так...Нет таких операций, которые требуют обнуления памяти. Читайте стандарты, и да зачтется Вам :).

 Профиль  
                  
 
 
Сообщение31.01.2008, 20:35 
Аватара пользователя


02/07/07
163
Харьков
Почему в результате выполнения такого кода
Код:
int *a=new int();
*a=1;
delete a;

int *c=new int();
*c=5;


cout<<*a;
delete c;
выведется 5,
а в результате выполнения такого кода
Код:
int *a=new int();
*a=1;
delete a;

int *c=new int();
*c=5;

delete c;
cout<<*a;
какое-то случайное число? Почему после того, как блок памяти, на котоую ссылается указатель с, пометили как свободный, этот блок меняется? И причем при каждом запуске программы меняется на одно и то же число(-572662307).

 Профиль  
                  
 
 
Сообщение31.01.2008, 20:52 


21/03/06
1545
Москва
Цитата:
Почему после того, как блок памяти, на котоую ссылается указатель с, пометили как свободный, этот блок меняется?

Реализация Вашего компилятора, ОС, архитектуры ПК.
Вы учитывайте, что компилятор очень тесно взаимодействует с ОС, когда динамически выделяет память. Фактически, он может ее просить выделить еще блок памяти для кучи, или возвращать освобожденный блок в систему.

Цитата:
И причем при каждом запуске программы меняется на одно и то же число(-572662307).

Потому, что на Вашей системе Вы каждый раз запускаете программу "с чистого листа", при одинаковом окружении, поведении ОС, ассемблерном коде Вашей программы, в специально выделенном ОС куске памяти под вашу программу. Даже seed из rand() имеет строго определенное значение по умолчанию, что гарантирует постоянную последовательность случайных чисел, если Вы вдруг их используете, и это делается умышленно - для упрощения отладки программ.
Но, в общем случае, это может быть и не так.

Вы поймите одно - разбираться что и как происходит после удаления объекта, на который указывал указатель, это значит расковырять все вплоть, может быть, до внутренней архитектуры и конвейера команд ЦП. Не стоит заморачиваться с этим вопросом, только если Вы не хакер. Программист не должен допускать подобных ситуаций в принципе.

 Профиль  
                  
 
 
Сообщение31.01.2008, 23:46 
Заслуженный участник
Аватара пользователя


17/10/05
3709
Asalex
Поиграйте с печатью указателей printf("a=%p\n", a); и тому подобное. Это поможет Вам понять, что происходит. Обратите внимание на элемент формата.

e2e4 писал(а):
Вы учитывайте, что компилятор очень тесно взаимодействует с ОС, когда динамически выделяет память.

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

Я плохо знаю стандарт С++, и потому не уверен — должен ли по умолчанию new() транслироваться в вызов malloc() или не должен.

 Профиль  
                  
 
 
Сообщение01.02.2008, 19:43 


21/03/06
1545
Москва
незваный гость писал(а):
Я плохо знаю стандарт С++, и потому не уверен ...


Я тоже плохо знаю стандарт и тонкости Си++. Что самое смешное, многие проффесиональные программисты, с кем мы обсуждали этот язык на 2-ой/3-ей фразе признавались в том же.

Просто интересно, кто-нибудь, кроме нескольких избранных людей на земле, реально знает как осмысленно пользоваться всеми заморочками Си++, помнит и применяет их? :)

 Профиль  
                  
 
 
Сообщение01.02.2008, 21:41 
Заслуженный участник
Аватара пользователя


17/10/05
3709
:evil:
e2e4 писал(а):
Просто интересно, кто-нибудь, кроме нескольких избранных людей на земле, реально знает как осмысленно пользоваться всеми заморочками Си++, помнит и применяет их?

Просто знание стандарта — это весьма специфическое знание. Оно, несомненно, нужно разработчику компилятора и библиотеки. А дальше? Не знаю. Знание именно стандарта языка необходимо при написании переносимых программ — там много разных компиляторов. Но в таких областях С++ используется реже. А если писать в гомогенной среде, например, под «окнами», то стандарт становится не нужен. Важнее знание особенностей своего компилятора.

Имеет, конечно, значение и сложность стандарта. Стандарт Algol-60 — 25 страниц, Algol-68 — 533. Как Вы понимаете, первый знало намного больше людей, чем второй.

 Профиль  
                  
 
 
Сообщение03.02.2008, 16:11 


21/03/06
1545
Москва
незваный гость писал(а):
Это очень сильное утверждение. Верное далеко не всегда и далеко не всюду. Во-первых, не компилятор, а библиотека. Во-вторых, библиотека может и не взаимодействовать с осью.

Во-первых, полностью согласен, что malloc - функция библиотечная.
Во-вторых, в моем сообщении есть доля моей аппроксимации, но аппроксимации разумной. Не представляю, как библиотека может не взаимодействовать с современными ОС при работе с памятью. Точнее представляю два варианта:
1. Заранее резервируется некоторый ограниченный объем памяти под кучу, и за пределы этого объема программа вылезти не может - malloc возвратит NULL при запросе памяти большего объема, чем доступен в данный момент в куче.
2. Происходит попытка занять всю доступную память для данной программы. Ну это бред по определению :).

Под современной ОС я подразумеваю ОС, которая не позволяет занимать память авторитарно, без ее, ОС, ведома, т.е. защищенную.

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

незваный гость писал(а):
Я плохо знаю стандарт С++, и потому не уверен — должен ли по умолчанию new() транслироваться в вызов malloc() или не должен.

Я тоже не знаю, но опять таки, исходя из логики, не думаю, что обязан. Ибо new - это оператор, поведение которого (неперегруженного) должно оставаться предсказуемым и идентичным в рамках хотя бы данного компилятора, что не будет осуществляться при использовании иной ф-ии malloc. Вообще, malloc - это Си, new - рекомендуемая ему замена в Си++, при этом new лучше malloc тем, что позволяет осуществить проверку типов, и, в общем-то, вообще отказаться от применения void *.

незваный гость писал(а):
Просто знание стандарта — это весьма специфическое знание. Оно, несомненно, нужно разработчику компилятора и библиотеки. А дальше? Не знаю. Знание именно стандарта языка необходимо при написании переносимых программ — там много разных компиляторов.

Я стараюсь писать изначально переносимый код, даже если в данный момент это не требуется в проекте. Просто в любой момент может взять да и потребоваться :).
Конечно, глубоко стандарты не изучал, многое уже забылось, но все-таки, именно переносимость кода ставлю одним из первых требований. Иначе, мы отказываемся от одного из главных преимуществ Си/Си++ - его реализаций практически на всех платформах. Тогда бы я уж предпочел что-то более красивое - Яву напиример, для спец. задач - python, php, perl и т.д. и т.п.

Но вообще-то я имел ввиду не стандарты, а Си++ вообще. Когда почитал книжку Джеффа Элджера "C++", то понял, что, как недавно сказали на Баше, "я знаю Си++, но не знаю как писать на нем программы" :).
Такого накрутить можно, причем накрутить обоснованно, что дух захватывает. Используется масса тонкостей, которые программисты не применяют почти никогда (во всяком случае, мой опыт говорит об этом).

Поэтому я и задаю вопрос - сколько людей вообще используют ООП/Си++, оперируя тенденциями, которые общество по развитию Си++ (или как там оно называется) ежегодно придумывает и обосновывает?

Не могу не сказать о Borland'цах - они в Builder'e вообще отключили множественное наследование от vcl-классов(!). Также повсеместно нарушается принцип инкапсулировани и т.д.

Такие дела...

 Профиль  
                  
 
 
Сообщение03.02.2008, 21:52 
Заслуженный участник
Аватара пользователя


17/10/05
3709
:evil:
e2e4 писал(а):
2. Происходит попытка занять всю доступную память для данной программы. Ну это бред по определению

Ну отчего же? Это абсолютно нормальная стратегия для встроенных систем. После или до инициализации ядра (в зависимости от используемого ядра), runtime забирает всю память и управляет ей.

e2e4 писал(а):
Ибо new - это оператор, поведение которого (неперегруженного) должно оставаться предсказуемым и идентичным в рамках хотя бы данного компилятора, что не будет осуществляться при использовании иной ф-ии malloc.

Почему? Почему оно станет непредсказуемым?

e2e4 писал(а):
при этом new лучше malloc тем, что позволяет осуществить проверку типов

Это несущественно. Существенно, что new вызывает конструктор, а malloc() — нет. Сравните с placement new (файл-заголовок <new>).

\Sigma: очевидно, что new не может не взаимодействовать с malloc() — употребление их в одной программе вполне законно.

e2e4 писал(а):
Такого накрутить можно, причем накрутить обоснованно, что дух захватывает. Используется масса тонкостей, которые программисты не применяют почти никогда (во всяком случае, мой опыт говорит об этом).

Программист (любой) используя язык (любой) для решения задачи, использует всегда некоторое подмножество языка. Так же как и язык написания статьи отличается от объяснения у пивного ларька отличается от оды Владимиру Владимировичу™.

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

 Профиль  
                  
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 27 ]  На страницу 1, 2  След.

Модераторы: Karan, Toucan, PAV, maxal, Супермодераторы



Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group