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

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
20
0
14 853
12
Перейти к ответу Данный вопрос помечен как решенный

Ответы 12

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

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

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

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

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

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

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

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

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

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

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

Also, I actually fixed the std::string problem. How? By replacing it with a vector, compiling, then replacing the string again. It was consistently crashing there, and that fixed even though it...couldn't. There's something nasty there, and I'm not sure what.

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

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

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

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

Запустите Purify.

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

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

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

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

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

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

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

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

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

Ответ принят как подходящий

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

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

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

Roddy 15.03.2010 18:46

В наши дни C++ имеет свои собственные интеллектуальные указатели в стандартной библиотеке, поэтому для этого не нужны Boost или Loki.

Sebastian Redl 21.12.2015 18:41

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

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

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

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

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

Отличное обсуждение для Linux! Скучаю по развитию в этой среде. Мне нужно решение для WinDoze ... (VS6.0 тоже) ... (не моя вина! Клиенты используют VS6.0 и клиенты всегда правы :).

Ross Youngblood 01.06.2017 01:25

Другие вопросы по теме