Управления памятью, повреждения кучи и C++

Итак, мне нужна помощь. Я работаю над проектом на C++. Тем не менее, я думаю, что мне как-то удалось испортить мою кучу. Это основано на том, что я добавил std::string к классу и присваивая ему значение из другого std::string:

std::string hello = "Hello, world.\n";
/* exampleString = "Hello, world.\n" would work fine. */
exampleString = hello;

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

Тем не менее, я нахожусь у меня над головой с такими вещами, поэтому я подумал, что я выброшу это там. Я в системе Linux и ковыряюсь с valgrindи, не зная полностью, что я делаю, он сообщил, что std::stringДеструктор был недействительным свободным. Я должен признать, что термин "повреждение кучи" используется в поиске Google; Любые статьи общего назначения о подобных вещах также приветствуются.

(Прежде, чем rm -rf ProjectDirповтори еще раз в C#:D)

РЕДАКТИРОВАТЬ: Я не дал понять, но я прошу, чтобы советы о диагностике такого рода проблем с памятью. Я знаю, что с std::string все правильно, так что это то, что я сделал (или ошибка, но проблемы с выбором нет). Я уверен, что смогу проверить написанный мной код, и вы, очень умные люди, сразу же увидите проблему, но я хочу добавить этот вид анализа кода к моей "панели инструментов".

12 ответов

Решение

Это относительно дешевые механизмы для возможного решения проблемы:

  1. Следите за моим вопросом кучи коррупции - я обновляюсь с ответами, как они встряхивают. Первый был балансировки new[] а также delete[], но ты уже делаешь это.
  2. Дайте Вальгринду больше опыта; Это отличный инструмент, и я бы только хотел, чтобы он был доступен под Windows. Я только замедляю вашу программу примерно вдвое, что довольно неплохо по сравнению с аналогами Windows.
  3. Подумайте об использовании Google Performance Tools в качестве замены malloc / new.
  4. Вы очистили все свои объектные файлы и начали заново? Возможно, ваш файл make... "неоптимальный"
  5. Вы не assert() достаточно в вашем коде. Как я узнаю это, не увидев этого? Как зубная нить, никто assert() достаточно в их коде. Добавьте функцию проверки для ваших объектов и вызовите ее в начале и в конце метода.
  6. Вы компилируете -wall? Если нет, сделайте это.
  7. Найдите себе инструмент для ворса, такой как PC-Lint. Маленькое приложение, подобное вашему, может поместиться на демонстрационной странице PC-lint, что означает, что вам не придется покупать!
  8. Проверьте, что вы НЕ УКАЗЫВАЕТЕ указатели после их удаления. Никто не любит висящий указатель. Тот же концерт с объявленными, но нераспределенными указателями.
  9. Прекратите использовать массивы. Вместо этого используйте вектор.
  10. Не используйте сырые указатели. Используйте умный указатель. Не использовать auto_ptr! Эта вещь... удивительна; его семантика очень странная. Вместо этого выберите один из интеллектуальных указателей Boost или что-нибудь из библиотеки Loki.

Когда-то у нас была ошибка, которая исключала все обычные методы, valgrind, cleany и т. Д. Сбой произошел только на машинах с большим объемом памяти и только на больших наборах входных данных.

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

1) Найти причину сбоя. Из вашего примера кода видно, что память для "exampleString" повреждена и поэтому не может быть записана. Давайте продолжим с этим предположением.

2) Установите точку останова в последнем известном месте, где "exampleString" используется или изменяется без каких-либо проблем.

3) Добавьте точку наблюдения к элементу данных "exampleString". В моей версии g++ строка хранится в _M_dataplus._M_p, Мы хотим знать, когда этот элемент данных изменяется. Техника GDB для этого:

(gdb) p &exampleString._M_dataplus._M_p
$3 = (char **) 0xbfccc2d8
(gdb)  watch *$3
Hardware watchpoint 1: *$3

Я, очевидно, использую Linux с G ++ и GDB, но я считаю, что точки наблюдения за памятью доступны большинству отладчиков.

4) Продолжайте, пока не сработает точка наблюдения:

Continuing.
Hardware watchpoint 2: *$3

Old value = 0xb7ec2604 ""
New value = 0x804a014 ""
0xb7e70a1c in std::string::_M_mutate () from /usr/lib/libstdc++.so.6
(gdb) where

GDB where Команда выдаст обратную трассировку, показывающую, что привело к модификации. Это либо совершенно легальная модификация, в этом случае просто продолжите - или, если вам повезет, это будет модификация из-за повреждения памяти. В последнем случае вы теперь сможете просмотреть код, который действительно вызывает проблему, и, надеюсь, исправить ее.

Причиной нашей ошибки был доступ к массиву с отрицательным индексом. Индекс был результатом приведения указателя к типу int по модулю размера массива. Ошибка была пропущена Valgrind и соавт. поскольку адреса памяти, выделенные при работе под этими инструментами, никогда не были> MAX_INTи так никогда не привел к отрицательному показателю.

О, если вы хотите знать, как решить проблему, это просто. Сначала возьми мертвую курицу. Затем начните трясти его.

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

  1. Почувствуйте себя комфортно в отладчике.
  2. Начните бродить в отладчике, чтобы увидеть, можете ли вы найти что-нибудь подозрительное. Проверьте особенно, чтобы увидеть, что происходит во время exampleString = hello; линия.
  3. Убедитесь, что на самом деле происходит сбой exampleString = hello; линии, а не при выходе из какого-либо ограждающего блока (что может привести к срабатыванию деструкторов).
  4. Проверьте любую магию указателя, которую вы можете делать. Указатель арифметики, литья и т. Д.
  5. Проверьте все ваши выделения и освобождения, чтобы убедиться, что они совпадают (без двойного освобождения).
  6. Убедитесь, что вы не возвращаете никаких ссылок или указателей на объекты в стеке.

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

Некоторые места для начала:

Если вы работаете в Windows и используете Visual C++6 (надеюсь, что никто не использует его в наши дни), это означает, что std::string не является поточно-ориентированным и может привести к подобным вещам.

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

На моем предыдущем рабочем месте мы использовали Compuware Boundschecker, чтобы помочь с этим. Это коммерческий и очень дорогой, поэтому не может быть вариант.

Вот пара бесплатных библиотек, которые могут быть полезны

http://www.codeguru.com/cpp/misc/misc/memory/article.php/c3745/

http://www.codeproject.com/KB/cpp/MemLeakDetect.aspx

Надеюсь, это поможет. Повреждение памяти - плохое место!

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

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

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

Помимо таких инструментов, как Boundschecker или Purify, ваша лучшая ставка в решении подобных проблем - просто научиться хорошо читать код и знакомиться с кодом, над которым вы работаете.

Повреждение памяти - это одна из самых сложных проблем, и обычно эти типы проблем решаются путем проведения часов / дней в отладчике и замечая что-то вроде "эй, указатель X используется после того, как он был удален!".

Если это помогает кому-то, это то, что вы становитесь лучше по мере приобретения опыта.

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

Запустите Purify.

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

Он работает на уровне машинного кода, поэтому вам даже не нужно иметь исходный код.

Один из самых приятных конференц-звонков вендора, на котором я когда-либо был, был, когда Purify обнаружил утечку памяти в их коде, и мы смогли спросить: "Возможно, вы не освобождаете память в своей функции foo()" и услышали удивление в их голосах.

Они думали, что мы отлаживаем богов, но затем мы раскрыли их секрет, чтобы они могли запустить Purify, прежде чем нам пришлось использовать их код.:-)

http://www-306.ibm.com/software/awdtools/purify/unix/

(Это довольно дорого, но у них есть бесплатная загрузка)

Ваш код, как я вижу, не имеет ошибок. Как уже было сказано, нужно больше контекста.

Если вы еще не пробовали, установите gdb (отладчик gcc) и скомпилируйте программу с помощью -g. Это скомпилирует символы отладки, которые может использовать GDB. После того, как вы установили GDB, запустите его вместе с программой (GDB). Это полезный чит для использования GDB.

Установите точку останова для функции, которая производит ошибку, и посмотрите, каково значение exampleString. Также сделайте то же самое для любого параметра, который вы передаете exampleString. Это должно как минимум сказать вам, если std::strings действительны.

Я нашел ответ из этой статьи хорошим руководством по указателям.

Это может быть повреждение кучи, но это также может быть повреждение стека. Джим прав. Нам действительно нужно немного больше контекста. Эти две строки источника не говорят нам много в отдельности. Это может вызывать любое количество вещей (что является настоящей радостью C/C++).

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

Код был просто примером того, где моя программа терпела неудачу (она была размещена в стеке, Джим). Я на самом деле не ищу "что я сделал неправильно", а скорее "как мне диагностировать то, что я сделал неправильно". Научи человека ловить рыбу и все такое. Хотя, глядя на вопрос, я не достаточно ясно дал понять. Слава Богу за функцию редактирования.:')

Также я исправил проблему с std::string. Как? Заменив его вектором, скомпилировав, затем заменив строку снова. Он там постоянно падал, и это было исправлено, хотя... он не мог. Там что-то противное, и я не уверен, что. Я действительно хотел проверить один раз, когда я вручную выделяю память в куче, хотя:

 this->map = new Area*[largestY + 1];
 for (int i = 0; i < largestY + 1; i++) {
     this->map[i] = new Area[largestX + 1];
 }

и удалив его:

for (int i = 0; i < largestY + 1; i++) {
    delete [] this->map[i];
}
delete [] this->map;

Я не выделил 2d массив с C++ раньше. Вроде работает.

Также я исправил проблему с std::string. Как? Заменив его вектором, скомпилировав, затем заменив строку снова. Он там постоянно падал, и это было исправлено, хотя... он не мог. Там что-то противное, и я не уверен, что.

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

Насколько я могу сказать, ваш код правильный. Предполагая, что exampleString является std::string с областью действия класса, как вы описали, вы должны иметь возможность инициализировать / назначить ее таким образом. Возможно, есть какая-то другая проблема? Может быть, фрагмент реального кода поможет поместить его в контекст.

Вопрос: является ли exampleString указателем на строковый объект, созданный с помощью new?

Другие вопросы по тегам