Управления памятью, повреждения кучи и C ++ - PullRequest
17 голосов
/ 11 августа 2008

Итак, мне нужна помощь. Я работаю над проектом на 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 был недействительным free. Я должен признать, что термин «повреждение кучи» используется в поиске Google; любые статьи общего назначения о подобных вещах также приветствуются.

(до rm -rf ProjectDir, повторить в C #: D)

EDIT: Я не дал понять, но я спрашиваю, каким образом можно диагностировать подобные проблемы с памятью. Я знаю, что с std :: string все правильно, так что это то, что я сделал (или ошибка, но проблемы с выбором нет). Я уверен, что смогу проверить написанный мной код, и вы, очень умные люди, сразу увидите проблему, но я хочу добавить этот вид анализа кода к моей «панели инструментов».

Ответы [ 12 ]

21 голосов
/ 11 августа 2008

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

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

Однажды у нас была ошибка, которая не позволяла использовать обычные методы: 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" и поэтому никогда не приводили к отрицательному индексу.

7 голосов
/ 11 августа 2008

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

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

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

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

3 голосов
/ 11 августа 2008

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

Если вы работаете в 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

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

1 голос
/ 21 августа 2008

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

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

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

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

1 голос
/ 11 августа 2008

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

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

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

1 голос
/ 11 августа 2008

Run Purify.

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

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

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

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

http://www -306.ibm.com / Программное обеспечение / awdtools / очистить / Unix /

(это довольно дорого, но их можно бесплатно скачать)

1 голос
/ 11 августа 2008

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

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

1 голос
/ 11 августа 2008

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

Также я исправил проблему с 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 ++ раньше. Вроде работает.

1 голос
/ 11 августа 2008

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

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

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

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

...