Кучи коррупции: что может быть причиной? - PullRequest
18 голосов
/ 01 октября 2009

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

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

Буду признателен, если вы предложите сценарии, которые могут привести к повреждению кучи.

Платформа: Windows XP

Язык: C ++

Компилятор: VC6

Ответы [ 13 ]

37 голосов
/ 01 октября 2009

Общие сценарии включают в себя:

  • Запись вне выделенного пространства массива (char *stuff = new char[10]; stuff[10] = 3;)
  • Приведение к неверному типу
  • Неинициализированные указатели
  • Ошибка опечатки для -> и.
  • Ошибка опечатки при использовании * и & (или нескольких из них)

[РЕДАКТИРОВАТЬ] Из комментариев, еще несколько:

  • Смешивание нового [] и нового с помощью delete [] и delete
  • Отсутствующие или неправильные конструкторы копирования
  • Указатель, указывающий на мусор
  • Вызов удалить несколько раз на одних и тех же данных
  • Полиморфные базовые классы без виртуальных деструкторов
13 голосов
/ 01 октября 2009

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

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

В этом случае вы можете попробовать анализатор, такой как Purify. Они обнаружат почти все, что делает ваш код, но также будут работать ОЧЕНЬ медленно. Такой инструмент обнаружит доступ к свободной памяти, доступ к свободной памяти, попытку дважды освободить один и тот же блок, использовать неправильные распределители / освобождающие блоки и т. Д. Это все виды состояний, которые могут оставаться скрытыми очень долго и только сбой в самый неподходящий момент.

6 голосов
/ 01 октября 2009

Существуют продукты, которые будут наблюдать за распределением и освобождением памяти и создавать отчет об аномалиях. В прошлый раз я использовал один, они не были дешевыми, но я не знаю, какова ситуация сейчас. Однако поиск вещей для VC ++ 6 может быть проблемой.

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

Я бы предложил везде использовать std::tr1::smart_ptr<> вместо необработанных указателей, но я не уверен, что VC ++ 6 это поддержит.

Почему вы до сих пор на VC ++ 6? Будет ли практичным обновить? Инструменты лучше с более свежими версиями, и они полностью поддерживают умные указатели.

6 голосов
/ 01 октября 2009

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

РЕДАКТИРОВАТЬ: Если вы используете контейнеры stl, которые могут перемещать элементы во время изменений (например, vector, deque), убедитесь, что вы не сохраняете ссылки на такие элементы в операциях, которые могут их изменить.

3 голосов
/ 01 октября 2009

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

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

3 голосов
/ 01 октября 2009

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

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

В последних версиях компилятора Microsoft добавлен переключатель компилятора / analysis, который выполняет статический анализ для выявления таких ошибок. В Linux valgrind является очевидным выбором.

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

Если возможно, вам следует перейти на правильный компилятор.

3 голосов
/ 01 октября 2009

Ознакомьтесь с ответами на этот связанный вопрос.

Ответ , который я предложил, предоставляет метод, который может помочь вам вернуться к коду, который фактически вызывает повреждение кучи. Мой ответ описывает технику с использованием gdb, но я уверен, что вы должны быть в состоянии сделать что-то подобное в Windows.

Принцип, по крайней мере, должен быть таким же.

2 голосов
/ 01 октября 2009

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

constQ= new double* [num_equations];
for(int i=0;i<num_equations;i++)
{
constQ[i]=new double[num_equations];
for(int j=0;j<num_equations;j++)
{
constQ[i][j]=0.0;
}
.
.
.

//Deleting/Freeing memory block 
//Here the below only parent memory block is deleted, the child memory block is leaked.

if(constQ!=NULL)
{
delete[] constQ;
constQ=NULL
} 
//Correct way of deleting heap memory..First delet child block memory and then Parent block

if(constQ!=NULL)
{
for(int i=0; i <num_equations;i++)
{
delete[] constQ[i];
delete[] constQ;
constQ=NULL
}
2 голосов
/ 01 октября 2009

Распространенной ошибкой является освобождение () или удаление выделенной памяти более чем одной. Это может помочь вставить что-то вроде * var = NULL после таких вызовов и проверить наличие! = NULL при вызове free. Хотя в C ++ допустимо вызывать delete с переменной NULL, вызов C - free () завершится неудачей.

Также распространенная проблема - путать удаление и удаление [].

Переменные, выделенные с помощью new , должны быть освобождены с помощью delete .

Массив, выделенный с помощью new [] , должен быть освобожден с помощью delete [] .

Также не смешивайте управление памятью в стиле C (malloc, calloc, free) и управление памятью в стиле C ++ (new / delete). В устаревшем коде часто оба смешаны, но вещи, выделенные одним, не могут быть освобождены другим.

Все эти ошибки обычно не распознаются компилятором.

1 голос
/ 01 октября 2009

Самая трудная ошибка, связанная с повреждением памяти, с которой я столкнулся: (1) вызов функции в DLL, которая возвратила std::vector, а затем (2), позволяющая std::vector выпасть из области видимости (что в целом точка std::vector). К сожалению, оказалось, что DLL была связана с одной версией среды выполнения C ++, а программа была связана с другой; это означало, что библиотека вызывала одну версию new[], а я вызывал совершенно другую версию delete[].

Это не то, что здесь происходит, потому что каждый раз это не удавалось, и, согласно одному из ваших комментариев, «ошибка проявляется в аварийном отказе один в миллионный раз». Я предполагаю, что есть оператор if, который принимается один раз в миллион раз и вызывает двойную ошибку delete.

Недавно я использовал ознакомительные версии двух продуктов, которые могут вам помочь: IBM Rational Purify и Intel Parallel Inspector . Я уверен, что есть и другие (Insure ++ часто упоминается). В Linux вы бы использовали Valgrind.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...