Управления памятью, повреждения кучи и 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 все правильно, так что это то, что я сделал (или ошибка, но проблемы с выбором нет). Я уверен, что смогу проверить написанный мной код, и вы, очень умные люди, сразу увидите проблему, но я хочу добавить этот вид анализа кода к моей «панели инструментов».

11.08.2008 04:52:50
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 .
22
23.05.2017 10:29:25
+1, хороший список! Тем не менее, я бы оспорил # 8 - хотя он предотвращает «плохой» доступ, на самом деле это запах кода, который скрывает плохую логику или плохое управление временем жизни объектов в моем опыте ...
Roddy 15.03.2010 15:46:40
В наши дни C ++ имеет собственные умные указатели в стандартной библиотеке, поэтому в этом нет необходимости для Boost или Loki.
Sebastian Redl 21.12.2015 15:41:03

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

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

0
11.08.2008 05:26:12

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

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

1
11.08.2008 05:31:27

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

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

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

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

1
23.05.2017 12:26:29

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

Также я исправил проблему с 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.08.2008 06:16:46

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

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

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

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

7
11.08.2008 06:20:21

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

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

1
11.08.2008 06:26:06

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

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

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

3
11.08.2008 06:49:15

Запустите Purify.

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

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

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

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

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

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

1
11.08.2008 07:24:10

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

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

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

1
11.08.2008 08:34:56

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

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

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

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

1
21.08.2008 19:18:44

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

10
16.09.2008 13:47:32
Отличное обсуждение для Linux! Мисс развивается в этой среде. Мне нужно решение для WinDoze ... (тоже VS6.0) ... (не по моей вине! Клиенты используют VS6.0, а клиенты всегда правы :).
Ross Youngblood 31.05.2017 22:25:10