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

Итак, мне нужна помощь. Я работаю над проектом на C ++. Однако, я думаю, мне удалось каким-то образом развратить мою кучу. Это основано на том, что я добавил std::string в class и присвоил ему значение из другой 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 , rm -rf ProjectDir в C #: D)

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

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

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

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

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

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

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

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

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

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

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

    На моем предыдущем рабочем месте мы использовали 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). Это полезный чит-код для использования gdb.

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

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

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

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

    Давайте будем гением компьютера.