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

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

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

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

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

(до rm -rf ProjectDir, do снова в C#: D)

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

12 ответов


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

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

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

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

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

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

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

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

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

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

Continuing.
Hardware watchpoint 2: *

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 " и поэтому никогда не приводил к отрицательному индексу.


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

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

  1. устраивайтесь поудобнее в отладчике.
  2. начать прогулки вокруг в отладчике, чтобы увидеть, если вы можете найти все, что выглядит подозрительно. Особенно проверьте, чтобы увидеть, что происходит во время exampleString = hello; линии.
  3. проверьте, чтобы убедиться, что это на самом деле сбой на exampleString = hello; line, а не при выходе из какого-либо ограждающего блока (что может привести к срабатыванию деструкторов).
  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

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


Это может быть повреждение кучи, но это также может быть повреждение стека. Джим прав. Нам действительно нужно немного больше контекста. Эти две линии источника мало что говорят нам в изоляции. Может быть любое количество вещей, вызывающих это (что является настоящей радостью 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. Как? Заменяя его вектором, компилируя, а затем снова заменяя строку. Он постоянно разбивался там, и это исправлялось, хотя и это...не смог. Там что-то мерзкое, и я не знаю, что именно.

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


Выполнить Очищение.

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

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

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

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

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

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


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

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

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


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

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

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

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


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

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

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

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


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

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