2008-08-11 10 views
16

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

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)

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

ответ

21

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

  1. Держите глаз на мой heap corruption question - Я обновляю с ответами, как они вытряхивают. Первым был баланс new[] и delete[], но вы уже это делаете.
  2. Дать valgrind больше идти; это отличный инструмент, и я только хочу, чтобы он был доступен под Windows. Я только замедляю вашу программу примерно на половину, что довольно хорошо по сравнению с эквивалентами Windows.
  3. Подумайте об использовании Google Performance Tools в качестве замены malloc/new.
  4. Вы очистили все свои объектные файлы и начали? Возможно, ваш файл make ... «suboptimal»
  5. Вы не assert() достаточно в вашем коде. Откуда я знаю, что, не увидев этого? Как нить, ни один assert() s достаточно в их коде. Добавьте функцию проверки для своих объектов и вызовите ее при запуске метода и конце метода.
  6. Вы compiling -wall? Если нет, сделайте это.
  7. Найдите себе инструмент для ворса, как PC-Lint. Маленькое приложение, подобное вашему, может поместиться на странице PC-lint demo, что означает отсутствие покупки для вас!
  8. Убедитесь, что вы удалили указатели после их удаления. Никто не любит свисающий указатель. Тот же концерт с объявленными, но нераспределенными указателями.
  9. Остановить использование массивов. Вместо этого используйте vector.
  10. Не используйте необработанные указатели. Используйте smart pointer. Не используйте auto_ptr! Эта вещь ... удивительна; его семантика очень странная. Вместо этого выберите один из Boost smart pointers или что-то из the Loki library.
+2

+1, хороший список! Тем не менее, я бы оспаривал № 8 - хотя он предотвращает «плохие» обращения, это на самом деле запах кода, который скрывает плохую логику или плохое управление жизненным сроком объекта в моем опыте ... – Roddy 2010-03-15 15:46:40

+0

В наши дни C++ имеет свои собственные интеллектуальные указатели в стандарте библиотеки, поэтому нет необходимости в Boost или Loki для этого. – 2015-12-21 15:41:03

0

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

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

1

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

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

1

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

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

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

Я нашел ответ от this article, чтобы быть хорошим руководством по указателям.

1

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

Кроме того, я фактически исправил проблему 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++ раньше. Кажется, это работает.

7

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

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

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

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

1

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

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

3

Некоторые места, чтобы начать:

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

Here's an article I found which explains a lot of the common causes of memory leaks and corruption.

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

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

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

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

Надежда, что помогает. Повреждение памяти - это достойное место!

1

Запустить очистить.

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

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

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

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

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

(Это довольно дорого, но у них есть бесплатный Eval скачать)

1

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

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

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

1

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

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

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

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

10

У нас когда-то была ошибка, которая ускользала из всех обычных методов, valgrind, очистила и т. Д. Авария только когда-либо случалась на машинах с большим количеством памяти и только на больших наборах входных данных.

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

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 с г ++ и 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 et al. поскольку адреса памяти, выделенные при работе под этими инструментами, никогда не были «> MAX_INT» и поэтому никогда не приводили к отрицательному индексу.