C ++ delete - удаляет мои объекты, но я все еще могу получить доступ к данным? - PullRequest
29 голосов
/ 18 декабря 2009

Я написал простую, работающую игру в тетрис с каждым блоком в качестве экземпляра класса SingleBlock.

class SingleBlock
{
    public:
    SingleBlock(int, int);
    ~SingleBlock();

    int x;
    int y;
    SingleBlock *next;
};

class MultiBlock
{
    public:
    MultiBlock(int, int);

    SingleBlock *c, *d, *e, *f;
};

SingleBlock::SingleBlock(int a, int b)
{
    x = a;
    y = b;
}

SingleBlock::~SingleBlock()
{
    x = 222;
}

MultiBlock::MultiBlock(int a, int b)
{
    c = new SingleBlock (a,b);
    d = c->next = new SingleBlock (a+10,b);
    e = d->next = new SingleBlock (a+20,b);
    f = e->next = new SingleBlock (a+30,b);
}

У меня есть функция, которая просматривает полную строку и просматривает связанный список блоков, удаляя соответствующие и переназначая -> следующие указатели.

SingleBlock *deleteBlock;
SingleBlock *tempBlock;

tempBlock = deleteBlock->next;
delete deleteBlock;

Игра работает, блоки удаляются корректно, и все работает как положено. Однако при проверке я все еще могу получить доступ к случайным битам удаленных данных.

Если я печатаю каждое из удаленных значений «x» одного блока после их удаления, некоторые из них возвращают случайный мусор (подтверждая удаление), а некоторые возвращают 222, сообщая мне, что деструктор был назван данными на самом деле удаляется из кучи. Многие идентичные испытания показывают, что это всегда одни и те же конкретные блоки, которые не удаляются должным образом.

Результаты:

Existing Blocks:
Block: 00E927A8
Block: 00E94290
Block: 00E942B0
Block: 00E942D0
Block: 00E942F0
Block: 00E94500
Block: 00E94520
Block: 00E94540
Block: 00E94560
Block: 00E945B0
Block: 00E945D0
Block: 00E945F0
Block: 00E94610
Block: 00E94660
Block: 00E94680
Block: 00E946A0

Deleting Blocks:
Deleting ... 00E942B0, X = 15288000
Deleting ... 00E942D0, X = 15286960
Deleting ... 00E94520, X = 15286992
Deleting ... 00E94540, X = 15270296
Deleting ... 00E94560, X = 222
Deleting ... 00E945D0, X = 15270296
Deleting ... 00E945F0, X = 222
Deleting ... 00E94610, X = 222
Deleting ... 00E94660, X = 15270296
Deleting ... 00E94680, X = 222

Может ли быть доступ к данным за пределами ожидаемой могилы?

Извините, если это немного затянуто.

Ответы [ 13 ]

77 голосов
/ 18 декабря 2009

Может ли быть доступ к данным, выходящим за пределы ожидаемой могилы?

Это технически известно как неопределенное поведение. Не удивляйтесь, если вам предложат и банку пива.

31 голосов
/ 18 декабря 2009

Может ли быть доступ к данным, выходящим за пределы ожидаемой могилы?

В большинстве случаев да. Вызов delete не обнуляет память.

Обратите внимание, что поведение не определено. Используя определенные компиляторы, память может быть обнулена. Когда вы вызываете delete, происходит то, что память помечается как доступная, поэтому в следующий раз, когда кто-то сделает new , память может быть использована.

Если вы подумаете об этом, это логично - когда вы сообщаете компилятору, что больше не интересуетесь памятью (используя delete ), зачем компьютеру тратить время на обнуление?

9 голосов
/ 18 декабря 2009

Это то, что С ++ называет неопределенным поведением - вы можете получить доступ к данным, а можете и нет. В любом случае это неправильно.

8 голосов
/ 18 декабря 2009

Удалить ничего не удаляет - просто помечает память как «свободную для повторного использования». Пока какое-то другое распределение не вызовет резервирование и не заполнит это пространство, у него будут старые данные. Однако полагаться на это - большое «нет-нет», в основном, если вы что-то удаляете, забудьте об этом.

Одной из практик в этом отношении, которая часто встречается в библиотеках, является функция Delete:

template< class T > void Delete( T*& pointer )
{
    delete pointer;
    pointer = NULL;
}

Это предотвращает случайный доступ к недействительной памяти.

Обратите внимание, что звонить delete NULL;.

вполне нормально.
3 голосов
/ 19 декабря 2009

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

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

2 голосов
/ 18 декабря 2009

Система не очищает память, когда вы отпускаете ее через delete(). Поэтому содержимое по-прежнему доступно, пока память не будет отведена для повторного использования и перезаписана.

1 голос
/ 18 декабря 2009

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

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

1 голос
/ 18 декабря 2009

delete освобождает память, но не изменяет и не обнуляет ее. Тем не менее, вам не следует обращаться к освобожденной памяти.

0 голосов
/ 06 марта 2017

Ну, я тоже довольно долго об этом думал, и я попытался провести несколько тестов, чтобы лучше понять, что происходит под капотом. Стандартный ответ таков: после вызова delete вам не следует ожидать ничего хорошего от доступа к этой области памяти. Однако этого мне показалось недостаточно. Что на самом деле происходит при вызове delete (ptr) ? Вот что я нашел. Я использую g ++ в Ubuntu 16.04, так что это может сыграть свою роль в результатах.

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

Память, освобожденная с помощью delete , по-видимому, по-прежнему выделена для программы, в которой она сначала выделена с помощью new . Я пытался, и после вызова delete не происходит уменьшения использования памяти. У меня было программное обеспечение, которое обрабатывало около 30 МБ списков с помощью новых вызовов, а затем освобождало их с последующими delete вызовами. Случилось так, что, глядя на системный монитор во время работы программы, даже после долгого сна после вызовов delete , потребление памяти моей программой было таким же. Нет уменьшения! Это означает, что delete не освобождает память для системы.

На самом деле, похоже, что память, выделенная программой, его навсегда! Тем не менее, дело в том, что в случае освобождения память может снова использоваться той же самой программой без необходимости ее выделения. Я попытался выделить 15 МБ, освободить их, а затем выделить еще 15 МБ данных, и программа никогда не использовала 30 МБ. Системный монитор всегда показывал около 15 МБ. Что касается предыдущего теста, я просто изменил порядок, в котором происходили события: половина выделения, половина освобождения, другая половина распределения.

Итак, очевидно, память, используемая программой, может увеличиваться, но никогда не уменьшаться . Я думал, что, возможно, память действительно будет освобождена для других процессов в критических ситуациях, например, когда памяти больше не будет. В конце концов, какой смысл в том, чтобы программа сохраняла свою память навсегда, когда другие процессы просят об этом? Поэтому я снова выделил 30 МБ, и , освобождая их , я запустил memtester с таким большим количеством физической памяти, какой только мог. Я ожидал увидеть, как моя программа раздает свою память меместеру. Но угадайте, этого не произошло!

Я сделал короткий скринкаст, в котором показывается вещь в действии:

Delete example memory

Чтобы быть на 100% честным, была ситуация, в которой что-то произошло. Когда я пытался использовать memtester с большей доступной физической памятью в середине процесса удаления моей программы, объем памяти, используемой моей программой, уменьшился примерно до 3 МБ. Процесс memtester был автоматически уничтожен, а то, что произошло, было еще более удивительным! Использование памяти моей программы увеличивалось с каждым вызовом удаления! Это было так, как будто Ubuntu восстанавливал всю свою память обратно после инцидента с меместером.

Взято из http://www.thecrowned.org/c-delete-operator-really-frees-memory

0 голосов
/ 31 августа 2016

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

Я рекомендую вам, если вы пишете код с new / delete и необработанными указателями (а не std::make_shared() и аналогичными), чтобы вы проводили свои модульные тесты под Valgrind, чтобы, по крайней мере, иметь шанс обнаружить такие ошибки.

...