2009-12-18 2 views
23

Я написал простую, трудную игру тетриса с каждым блоком как экземпляр класса singleblock.C++ delete - удаляет мои объекты, но я все еще могу получить доступ к данным?

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, сообщая мне, хотя деструктор назывался данными wasn ' t фактически удален из кучи. Многие идентичные испытания показывают, что всегда одни и те же конкретные блоки, которые не удаляются должным образом.

Результаты:

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 

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

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

+1

Самая безопасная политика, чтобы удалить элемент, когда он больше не используется, и никогда не относятся к нему снова. Умные указатели могут помочь, когда более одного указателя ссылается на один и тот же объект в памяти. –

+0

Если вы можете получить доступ к блокам, вы можете повторно удалить их. Плохо. Не делай этого. –

+15

Иногда я думаю, что лучшее ключевое слово, чем 'delete', было бы« забыть »; вы на самом деле не говорите компилятору * удалять * все, что угодно * перестает заботиться о нем (и позволяя кому-то делать то, что они хотят с i) вроде как возвращать книгу в библиотеку, а не записывать ее. –

ответ

69

Возможно ли получить доступ к данным из-за пределов могилы?

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

+9

Кроме того, полезно добавить следствие этого факта ... Если у вас были данные, которые являются «чувствительными», хранящимися в памяти, следует подумать, что это хорошая практика, чтобы полностью перезаписать его перед удалением (чтобы предотвратить другие сегменты кода от доступа к нему). – Romain

+0

Это должно быть обработано до вызова dtor. – dirkgently

+1

... или, по крайней мере, в дторе. – dirkgently

0

Это еще не ноль/изменение памяти ... но в какой-то момент ковер будет вытаскиваться из-под ваших ног.

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

28

Возможно ли получить доступ к данным из-за пределов могилы?

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

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

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

+0

Однако нет никакой гарантии, что 'new' или' malloc' не будут выделять некоторые новые объекты поверх старых. Другой катастрофой может быть сборщик мусора системы. Кроме того, если вашей программе предоставляется память из общесистемного пула памяти, другие программы могут записывать данные призрака. –

+0

Собственно, нет. Успешный доступ к удаленной памяти не ожидается, это неопределенное поведение. Еще одно распределение может также легко переписать память, которую вы только что освободили. –

+4

@Thomas Matthews Я не говорю, что это хорошая идея, пытаясь получить к нему доступ. @Curt Nichols Это играет со словами. В зависимости от того, какой компилятор вы используете, вы можете * ожидать *, что память не обнуляется сразу при вызове delete. Вы, очевидно, не можете быть уверены в этом. – Martin

1

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

9

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

2

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

1

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

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

0

Да, этого можно ожидать время от времени. В то время как new резервирует место для данных, delete просто делает недействительным указатель, созданный с помощью new, позволяя записывать данные в ранее зарезервированных местах; он не обязательно удаляет данные. Однако вы не должны полагаться на это поведение, поскольку данные в этих местах могут меняться в любое время, что может привести к неправильной работе вашей программы. Поэтому после того, как вы указали delete на указатель (или delete[] на массив, выделенный с помощью new[]), вам следует назначить ему NULL, чтобы вы не могли манипулировать недействительным указателем, если вы не будете выделять память с помощью new или new[] перед повторным использованием этого указателя.

+3

В стандарте языка C++ нет ничего, что препятствовало бы стиранию памяти, удаленной или заполняемой странным значением,' delete'. Это реализация определена. –

8

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

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

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

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

Обратите внимание, что это нормально, можно позвонить по телефону delete NULL;.

+0

Даже если вы не используете макрос, рекомендуется установить указатель на NULL сразу после его освобождения. Это хорошая привычка проникать, предотвращая подобные недоразумения. –

+1

@Kornel Любая библиотека C++, которая использовала такой макрос, была бы чрезвычайно подозрительной, ИМХО. В самом лизинге это должна быть встроенная функция шаблона. – 2009-12-18 20:43:25

+8

@Mark Настройка указателей на NULL после удаления не является универсальной хорошей практикой в ​​C++. Бывают моменты, когда это хорошо, и времена, когда это бессмысленно и может скрыть ошибки. – 2009-12-18 20:44:46

1

Это приведет к неопределенному поведению и удалению освобождает память, но не инициализирует его нулем.

Если вы хотите, чтобы сделать его обнулить, то сделать:

SingleBlock::~SingleBlock() 

{ x = y = 0 ; } 
3

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

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

+1

Если компилятор может определить, что код неизбежно собирается получить доступ (даже посмотреть) к части доски, которую он не имеет, такое определение освободит компилятор от законов времени и причинности; некоторые компиляторы используют это так, что это было бы абсурдно десять лет назад (многие из которых до сих пор абсурдны, ИМХО). Я мог понять, что если две части кода не зависят друг от друга, компилятор может чередовать свою обработку любым способом, даже если это заставляет UB ударить «рано», но как только UB становится неизбежным, все правила вылетают из окна. – supercat

1

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

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

1

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

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

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

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

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

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

Delete example memory

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

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