29 марта 2010 г.

Проблемы с памятью – заключительная статья

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

Этот цикл про проблемы с памятью – дополнение к моей статье про исключения и ошибки в Delphi-программах на DelphiKingdom (осторожно: большой объём; вот вариант статьи в PDF). Если вы – начинающий программист, то я рекомендую вам выделить время на ознакомление с указанной выше статьёй (можете читать выборочно только интересующие вас разделы). Например, в той статье рассказывается как использовать отладчик (первый пункт в части 2) – весьма необходимый навык, который не раз вам будет нужен при использовании материала ниже.

Итак, возвращаясь к этому циклу – вот его

Оглавление

1. Вступительная статья про указатели (а также отдельная статья про строки). Ну, это не моя статья, но она весьма хороша, поэтому я просто использую ссылку на неё. Здесь рассказывается об указателях, типах данных, связанных с ними – и вообще о многих вещах, о которых вам лучше бы иметь представление, когда вы встречаетесь с проблемами с памятью в своих программах.

2. Подготовительная часть. Инструменты для диагностики различных проблем в ваших программах требуют определённого окружения и выполнения некоторых условий. Вы можете улучшить (или ухудшить) их работу, устанавливая различные опции проекта. Поэтому, прежде чем двигаться дальше, в этой статье рассматриваются опции ваших проектов, имеющие отношение к диагностике проблем, и их влияние на ваши проекты. Вам лучше бы установить их до выпуска вашего продукта на волю (и до того, как вы начнёте получать отчёты об ошибках). Замечу, что в статье также приводятся примеры типичных установок на различные случаи (всё это – только рекомендации, а не истина в последней инстанции). В принципе, это опциональная часть, если вы уже встретили проблему, но не испытываете проблем с детальностью своих отчётов о проблемах.

3. Исключение EAccessViolation. Как наиболее частое исключение в Delphi, у этого исключения есть множество самых различных причин, но все они объединяются тем, что всё это – проблемы с памятью. В статье рассказывается, что это за исключение, приводятся примеры причин его возникновения и рассказывается, как его диагностировать и исправлять. Здесь рассматриваются как ручные методы – прямая работа с отладчиком, так и использование сторонних инструментов для диагностики исключений (тут они впервые рассматриваются). Как становится ясно к концу статьи, возникновение исключения EAccessViolation – это благо, потому что позволяет вам немедленно увидеть проблему. В статье дан пример того, как один и тот же код, в зависимости от внешних условий, может приводить либо к возбуждению исключения (“хорошо”), либо к вылету программы и порче данных (“плохо”), либо даже к нормальной работе, хотя в коде есть баг (“отвратительно”). В статье рассматривался только первый случай – явное исключение, а остальные случаи я оставил на потом (эту статью).

4. Поиск утечек памяти. Хотя в этой статье я говорю о том, как вы можете искать утечки памяти в своих программах, моя действительная цель – познакомить вас с менеджерами памяти и их отладочными режимами. Я упоминаю об этом в самом начале статьи, что тема поиска утечек памяти также тесно связана с проблемами памяти (не включая утечки). В статье я сосредоточился на утечках, вынося обзор дополнительного функционала по диагностике проблем с памятью вне статьи (откладывая его на эту статью).

5. (offtopic 1) Как читать отчёты об ошибках. Эта статья не имеет отношения к циклу статей о проблемах памяти, но я счёл нужным добавить её. Как оказалось, у многих начинающих программистов возникают проблемы с трактовкой отчётов об ошибках, которые получаются методами и инструментами, описанными в предыдущих статьях. Поэтому эта статья направлена на пояснения и указание типичных проблем с пониманием отчётов.

6. (offtopic 2) Дополнение к статье о поиске утечек памяти. Ну, многие люди стали использовать мою статью об утечках памяти по её прямому назначению – поиску утечек памяти ;) Как оказалось, статья написана недостаточно подробно, потому что у новичков постоянно возникали проблемы с тем, как же, собственно говоря, искать и исправлять эти самые утечки памяти. Ну, это не удивительно, поскольку основной прицел оригинальной статьи был направлен на ознакомление с отладочными менеджерами памяти. Поэтому в этом дополнении к статье об утечках я сфокусировался на опущенных в первый раз моментах.

7. Эта статья. В предыдущих статьях я только наметил самые страшные проблемы с памятью, оставив их “на потом” и разобрав только простые случаи. В этой статья я добью оставшийся материал, завершив, таким образом, цикл. В общем, если вы встречаете проблему с пустым/бесполезным отчётом (и это не тривиальное отсутствие отладочной информации), либо же вы получаете загадочный вылет или зависание программы, то эта статья – для вас (примечание: причиной зависания тоже может быть порча памяти: например).

Итак, сделав обзор цикла, переходим к собственно статье:

Когда EurekaLog не может вам помочь (или: о чём идёт речь)

Примечание: я говорю “EurekaLog”, но на самом деле я имею ввиду любой другой аналогичный инструмент, типа JclHookExcept, madExcept, WER и т.п.

EurekaLog – это инструмент для отладки ваших приложений. Это не волшебное средство для решения всех ваших проблем. Как любой инструмент, EurekaLog имеет свои цели, область применения и ограничения. Поэтому, могут быть случаи, когда EurekaLog не сможет вам помочь.

Проблема в том, что любой программный инструмент требует некоторый базис (основу) для своей работы. Если вы разрушаете этот базис – вы ломаете инструмент, и он ничего не сможет с этим сделать. Обычно, сделать это весьма непросто. Но только если вы не трогаете память и указатели. В предыдущих статьях я уже показывал примеры, когда довольно простой код может выбить вашу программу, так что никто даже не заикнётся о проблеме (см. например, конец раздела “Ищем причину возникновения Access Violation анализом кода” в этой статье).

Итак, все эти страшные проблемы, которые делают беспомощными инструменты отладки – это проблемы с памятью. А именно: проблемы порчи памяти (memory corruption). Любой инструмент должен где-то хранить свои данные. Если баг в вашем коде затирает их – игра окончена. Более того, ваш код может испортить данные не только прикладного инструмента (типа EurekaLog), но и некоторые критические данные для вашей программы (типа обработчиков исключений или адресов возврата). Как вы может уже заметили, большинство страшных проблем приходятся на порчу стека (stack corruption). В основном потому, что самые критические данные (из доступных вашему коду) хранятся именно в программном стеке.

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

InternalError

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

Итак, EurekaLog предлагает вам отправить отчёт разработчикам EurekaLog. Если вы согласитесь, то она откроет ваш e-mail клиент с примерно таким отчётом:
Version   : 6.0.23
Date      : Sat, 27 Mar 2010 13:09:00 +0300
OS        : Microsoft Windows 7 (64 bit)
RAD       : BDS 7.0
Dump      : $89 $10 $8B $45 $94 $8B $40 $08 $48 $85 $C0 $7C $7B $40 $89 $85 $30 $FF $FF $FF $C7 $85 $38 $FF $FF $FF $00 $00 $00 $00 $8B $95
Section   : 16
LastExcept: Exception
Address   : $004A7FE6 - [00400000] Project38.exe - ExceptionLog.pas -  - InternalExceptNotify - 14540[484]
Exception : EAccessViolation
Message   : Access violation at address 004A7FE6 in module 'Project38.exe'. Write of address 00000000
Call Stack: 00 $00419534 - [00400000] Project38.exe - SysUtils.pas - Exception - Create - 17419[0]
           01 $00515E63 - [00400000] Project38.exe - Unit39.pas - TForm39 - Button1Click - 29[1]
           02 $004A6E6C - [00400000] Project38.exe - ExceptionLog.pas -  - InternalExceptNotify - 14056[0]
           03 $004A879A - [00400000] Project38.exe - ExceptionLog.pas - TExceptionThread - Execute - 14689[2]
           04 $0043E7DA - [00400000] Project38.exe - Classes.pas -  - ThreadProc - 11018[8]
           05 $0040667C - [00400000] Project38.exe - System.pas -  - ThreadWrapper - 13579[33]
           06 $76EF3675 - [76EE0000] kernel32.dll
           07 $77C89D70 - [77C50000] ntdll.dll
           08 $77C89D4B - [77C50000] ntdll.dll
           09 $77C89D40 - [77C50000] ntdll.dll 
Это короткая версия отчёта с фатальной проблемой. Конечно, вам следует по возможности отправлять такие отчёты разработчикам – однако, если вы видите, что здесь упоминаются исключения EAccessViolation или Invalid Pointer – то это означает, что EurekaLog только что встретила проблему порчи памяти, источником которой может быть и ваш код. Что означает, что вам лучше бы начать внимательно проверять свою программу. Да, иногда такой отчёт может быть признаком бага в самой EurekaLog, но такие ситуации довольно редки. Большинство приходящих нам отчётов связанны с проблемами стороннего кода. В любом случае, если вы считаете, что это именно ошибка EurekaLog, то вам следует быть готовым к долгому разговору с разработчиками (я говорю сейчас только о проблемах с памятью). Потому что, как мы увидим ниже, отчёт о проблеме с памятью даст вам понять, что у вас есть проблема, но никак не может помочь в её исправлении, поэтому для исправления проблемы вам скорее всего придётся работать в тесном сотрудничестве с разработчиками EurekaLog.

Замечу также, что EurekaLog по-умолчанию встраивает себя в IDE Delphi, перехватывая и её ошибки тоже. Т.е. EurekaLog ловит ошибки не только ваших проектов, но и ошибки среды. Это полезно в старых версиях Delphi, которые не имели аналогичного механизма. В новых Delphi появились автоматические отчёты – поэтому вы, возможно, захотите отключить перехват исключений в IDE, чтобы не мешать штатному механизму. В любом случае, включить или выключить его вы можете в меню EurekaLog/EurekaLog IDE Options:

EurekaLog IDE Options

Галочка “IDE Integration” отвечает за перехват исключений в IDE. Вот пример такой проблемы.

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

Итак, возвращаясь к нашему приложению. Отчёт о внутренней проблеме EurekaLog – это лишь одно из возможных проявлений проблем с памятью (причём самое безобидное). Как мы уже говорили, более плохие варианты проблем с памятью могут приводить к вылету или зависанию программы. В этом случае вы вообще не получите какого-либо вразумительного сообщения.

Как диагностировать и исправлять проблемы с памятью

Если у вас есть проблема порчи памяти и вы получили отчёт о ней – это будет просто уведомлением о том, что у вас есть проблема. Вы не сможете исправить проблему, имея на руках только отчёт о ней. Почему? Потому что любой такой отчёт – это заключение о том, что где-то ранее произошла проблема. Во многом это похоже на утечки памяти. Мы уже обсуждали это тут. Дело в том, что никто (ни софт, ни железо) не имеет возможности проверять каждую машинную инструкцию: а уж не собирается ли она испортить память? Поэтому все проверки производятся время от времени, в определённых контрольных точках. Например, в случае утечек памяти, такими контрольными точками являются вызовы функций выделения или освобождения памяти. Но даже в этом случае менеджер памяти не проверяет всю память на проблемы, а проверяет только блок в контексте операции. Это компромисс между функциональностью и производительностью.

Итак, имея на руках отчёт, вы знаете что у вас возникла проблема. Но вы не знаете где. Ну, в случае утечек памяти у вас ещё есть шанс идентифицировать проблему, но не в случае порчи памяти. Потому что в первом случае у вас есть хоть какая-то привязка к коду. В случае же порчи памяти проблемный код может сидеть совершенно в другом месте, чем то место, где проблема была обнаружена. Поэтому, первое, что вам нужно сделать (вне зависимости, есть у вас отчёт или же ваша программа просто вылетела/зависла): попробовать воспроизвести проблему. Иногда сделать это бывает просто; иногда – возможно, но сложно; но часто – попросту невозможно.

Если вам удалось воспроизвести проблему, то тут всё относительно просто – просто отлаживайте свою программу до посинения, пока не найдёте причину проблемы. Самым главным вашим оружием будут являться точки останова на память. Я рассматривал их в своей статье про исключения (вариант в PDF) в разделе 2, когда обсуждал возможности отладчика. Общая тактика проста: вы ищете момент, когда память ещё не испорчена, ставите на неё точку остановка и запускаете программу в свободный полёт. Как только точка останова сработала – вуаля, вы нашли виновника. Смотрите стек, анализируйте переменные – в общем, делайте что хотите, ситуация полностью у вас на ладони.

Таким образом, основной вопрос при условии воспроизводимости проблемы – максимально локализовать проблему. Техники, которые вы можете применять при этом, описаны ниже. Некоторые из них вы можете применять всегда – и в отладочной и в коробочной версии программы, а некоторые – только в отладочном окружении.

Если же решить проблему не удаётся (либо вы не можете воспроизвести её, либо же её не удаётся локализовать) – то вам остаётся только применение пассивных техник. Т.е. вещей, которые не направлены на решение конкретной проблемы, но направлены на улучшение вашего кода, так что после переработки проблему можно будет либо диагностировать, либо же она уйдёт сама собой. Если ваш код представляет собой совершенно хаотическое нагромождение каких-то малопонятных действий, то вы можете потратить полгода на поиск причины вылета программы. Или вы можете потратить два месяца, чтобы привести код к красивому и читабельному виду и потратить два дня на устранение не только этой, но и других проблем, которые вы сможете увидеть в коде из-за улучшения его читабельности. На это мы также посмотрим чуть ниже, через раздел. Применение таких пассивных техник особенно важно, если у вас в коде есть баг, но он никак не проявляет себя. Потому что никак иначе вы его не выявите (почему вообще важно избавляться от багов, которые никак себя не проявляют? Потому что нет гарантии, что так будет всегда: меняются условия и ранее не замеченный баг может всплыть и сделать кучу плохих вещей).

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

Локализация проблемы (активные техники)

Во-первых, вам нужно определить в чём примерно может быть проблема. Основные ситуации это либо это порча памяти в куче (динамической памяти, heap), либо порча стека (stack). В зависимости от этого вы должны применять техники, направленные на диагностику проблем с кучей или со стеком. Например, использование отладочного менеджера памяти может помочь вам при проблемах с кучей, но бесполезно при порче стека. Впрочем, если вы не уверены или не можете определиться – просто используйте всё подряд. В техниках ниже я пометил в скобочках, к чему они относятся.

1. Подключение отладочного менеджера памяти (куча). Под отладочным менеджером памяти имеется ввиду менеджер памяти, который обладает дополнительными возможностями по диагностике проблем памяти. Мы знакомились с ними в статье про утечки памяти. Дело в том, что в обоих случаях используется очень похожий метод, только для утечек памяти мы контролируем совпадение выделений/освобождений, а для порчи памяти мы контролируем целостность блоков памяти. В случае EurekaLog дополнительный контроль включается опцией “Catch leaks exceptions” (в новой версии EurekaLog 7 ожидается больше опций). В FastMM за это отвечают опции CheckHeapForCorruption, CatchUseOfFreedInterfaces (совместно с FullDebugMode). Другие опции тоже могут косвенно влиять на результат, но это – основные, включающие или выключающие проверки вообще. Мы подробно разбирали это в статье про утечки памяти, поэтому я не буду повторяться: с проблемами порчи памяти всё аналогично – вы включаете отладочный режим и нужные опции и гоняете программу, пока менеджер памяти не поймает проблему.

Помимо этих менеджеров памяти, я хотел бы ещё упомянуть SafeMM – это тоже отладочный менеджер памяти, но стоящий несколько в стороне. Взять его можно тут. А тут есть даже пример использования (видео) – первая часть рассказывает про профайлинг, а про проблемы с памятью начинается с (примерно) 22-ой минуты. Основное отличие SafeMM от предыдущих менеджеров в том, что EurekaLog и FastMM (даже в режиме отладки) можно запускать на постоянной основе в отладочном окружении или даже в публичных бета-версиях (а вот в релизных версиях это будет не так разумно), SafeMM же пригоден только для отладки тестовых сценариев. По сути, этот менеджер является аналогом отсутствия менеджера памяти в приложении как такового. Напомню, что работа с памятью осуществляется через функции WinAPI VirtualAlloc и VirtualFree. Менеджер памяти, по сути – просто кэш. Он буферизирует несколько запросов выделения памяти в один блок. Именно отсюда лезет множество проблем с памятью, когда память вы фактически освободили, но менеджер памяти её не отпускает, потому что в этом же блоке есть ещё выделенная память. SafeMM решает эту проблему убиранием этой прослойки, стараясь напрямую перенаправлять запросы на выделение/освобождение памяти ОС напрямую. Это означает, что вы в некоторых случаях вы получаете немедленное уведомление о проблеме, а не отложенное, как у EurekaLog или FastMM. Кроме того, этот способ дополнительно позволяет вам отслеживать попытки неверного чтения памяти, от которых EurekaLog и FastMM зачастую вообще не могут защитить. С другой стороны, от проблем порчи доступной памяти этот менеджер памяти не слишком помогает. Также отрицательной стороной является замедление производительности, что делает невозможным его применение на постоянной основе. В общем, это узкоспециализированное, но зато железобетонное решение для некоторых ситуаций: любые попытки работы со свободной памятью – как невыделенной, так и уже освобождённой памятью. Примеры ситуаций, в которых применим SafeMM, показаны в видео-демонстрации по ссылке выше (да, она на английском, но основной смысл понятен и без слов).

2. Включение отладочных опций (стек и куча). Это мы тоже уже разбирали ранее. В первую очередь это опция “Range check errors”, помогающая отловить проблемы выхода индекса за границы диапазонов (имейте ввиду, что в старых Delphi она работает с ошибками). Кроме этой весьма важной функции у нас есть несколько менее важных опций: типа отключения встраивания функций (inline) и выключения оптимизации. Последнее направлено на упрощение отладки и избавления от глюков оптимизатора (например). К сожалению, в Delphi отсутствует аналог опции “Range check errors”, присутствующий в других компиляторах, и направленный на диагностику проблем со стеком не только с массивами (опция вида "Stack check errors"). Я создал запрос на новую возможность – можете проголосовать за него. Если бы эта возможность была бы реализована, это чрезвычайно упростила бы отладку проблем со стеком. Пока этой опции нет – приходится использовать обходные пути, т.к. сейчас нет надёжного способа определить такие проблемы.

3. Форсированные контрольные точки (стек и куча). Как я уже говорил, почти любой отчёт о проблеме с памятью говорит о моменте, когда проблема обнаружена, а не когда она возникла. Вы должны локализовать проблему. Как это сделать? Очевидно, выделив момент, когда проблемы точно нет, и момент, когда проблема точно есть. Тогда проблема будет сидеть где-то в коде между двумя моментами. Каждый из таких моментов будет контрольной точкой. Сдвигая (или создавая) эти контрольные точки всё ближе и ближе друг к другу, вы сможете сузить диапазон поиска и в итоге локализовать код. В некоторых случаях, такие контрольные точки создаются автоматически – так, например, отладочный менеджер памяти проверяет, не повреждён ли блок памяти, прежде чем освободить его или изменить его размер. Для стека это может быть выход из метода. Если вы корректно вышли из метода, то это значит, что стек внутри её не был повреждён (или хотя бы был не слишком повреждён), потому что адрес возврата не был затёрт. Можно привести и другие примеры, но сейчас суть не в этом. А в том, что если таких контрольных точек будет недостаточно (или они не создаются вовсе) – то вам нужно создавать их руками.

Идеальным вариантом весьма плотной расстановки контрольных точек для стека была бы опция, предложенная мной в предыдущем пункте. Ну а пока её нет – придётся делать руками. В частности, сейчас для стека у вас есть только вариант отладочных проверок – см. следующий пункт.

Для кучи у вас также имеется вариант форсировать проверку памяти. Для EurekaLog вы можете вызвать функцию CheckMemoryOverrun, а для FastMM – ScanMemoryPoolForCorruptions. Вызывая эти процедуры, вы заставляете соответствующий менеджер памяти проверить всю область памяти на повреждения (понятно, что сканируется только целостность служебной информации). Расставляя вызовы одной из этих функций по коду, вы расставляете контрольные точки. Начните с периодического вызова этих функций у себя в коде. Поймав проблему между двумя контрольными точками, сдвигайте их вызовы ближе друг к другу, пока не локализуете проблему. Кроме этого, FastMM дополнительно позволяет вам включить такое сканирование на любую операцию с менеджером памяти. Для этого вам необходимо установить глобальную переменную FullDebugModeScanMemoryPoolBeforeEveryOperation в True. Однако, надо понимать, что эта опция снижает вашу производительность до нуля. Никогда не включайте её на длительное время. Сначала попытайтесь максимально локализовать проблему с помощью вызовов ScanMemoryPoolForCorruptions. И только, когда дальше сузить область не получается – включайте FullDebugModeScanMemoryPoolBeforeEveryOperation, но только для проблемного участка и не забудьте выключить после его прохождения. Возможно, в некоторых случаях более удачным вариантом станет использование SafeMM.

4. Отладочные проверки (стек и куча). Частично это следствие предыдущего пункта. Не всегда возможно применение контрольных точек в виде, как это обсуждалось в предыдущем пункте. Например, ошибочную перезапись любого буфера без выхода за его границы вы таким образом не отследите. Потому что проверки менеджера памяти могут проверять только целостность служебной информации – читай: выходы за пределы буфера. Поэтому, вам может понадобится контролировать целостность информации и в других ситуациях. Делается это просто: расстановкой явных проверок. Расставляйте по коду Assert-ы, в которых вы проверяете корректность данных. Поймав проблему между двумя Assert-ами, сдвигайте их друг к другу, сужая область кода и локализуя проблему. Как для памяти, так и для стека вы можете использовать “сторожей”: добавьте к своим структурам данных или переменным два поля: HeadGuard и FooterGuard типа Cardinal. Перед прогоном кода запишите туда любое “волшебное число”. После прогона – проверьте, не попорчено ли оно. Этот метод поможет найти выход за некоторую область памяти, если вы не смогли сделать это другими средствами. В общем, используйте фантазию и проверяйте всё, что только можно. Как только вы сузите область поиска достаточно, чтобы определить конкретные адреса памяти, где происходит порча памяти – просто поставьте на эту память точку остановка (см. также ниже).

5. Избегание локальных переменных (стек). Ну, вы можете объявить две спец-переменные HeadGuard до блока локальных переменных и FooterGuard после, чтобы проверять выходы за локальные переменные (ручной аналог уже предлагаемой мной новой опции компилятора). Или же вы можете избегать локальных переменных вовсе: попробуйте заменить их на глобальные или (что лучше) преобразовать их в запись (record) и размещать её динамически. Это позволит перенести проблемы со стека в другую область, где у вас уже есть инструмент для диагностики (менеджер памяти).

6. Потоки и их проблемы (куча). Многопоточность обычно не вызывает проблем со стеком, зато очень часто становится проблемой с глобальными переменными и кучей (вернее, не многопоточность, а проблемы синхронизации доступа к данным). Отладка многопоточных приложений – это очень сложная тема и я не буду её сейчас рассматривать. Скажу только, что вы также можете применять принцип контрольных точек и к потокам. Только вместо проверок используется заключение работы с ресурсами в критические секции. Ну, поскольку многопоточность не вносит проблем в работу с локальными переменными, то вы можете использовать подход, противоположный предыдущему пункту: вместо использования разделяемых данных, скопируйте их себе и работайте с ними локально. Кстати, иногда, встречаются и такие перлы. На самом деле, использовать такой трюк во время отладки – вполне нормально, но выпускать такой код, конечно, перебор.

7. Точки останова на память (стек и куча). Как только вы сумеете сузить область поиска достаточно, чтобы вам удалось узнать адрес конкретного блока памяти, где происходит порча памяти – всё становится очень простым. Вы можете поставить точку останова на этот блок памяти и пустить программу дальше, пока она не остановится на этом бряке. Код, который вызвал останов, и будет виновником порчи памяти. Остальное уже совсем просто. Я уже упоминал, что работа с точками останова на память описана в моей статье про исключения в разделе описания отладчика.

Итак, если вы не смогли решить проблему с применением указанных выше методов, то вам остаётся только

Профилактика проблем с памятью (пассивные техники)

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

2. Использование обёрток. Частично мы уже говорили об этом в контексте низкоуровневого кода и “прочих ресурсов” (не памяти). Выделяйте такой код в отдельный класс, который вы можете проверить как единое целое – этим вы сузите область для поиска проблем, локализовав потенциально опасный код в одном месте. В контексте поиска утечек, сюда же относится замена объектов на интерфейсы.

3. Разбор кода другим разработчиком. Хорошо известно, что глаза видят только то, что хочет видеть ваш мозг. Поэтому бывает хорошо дать почитать ваш код коллеге за соседним столом (в соседнем офисе – для хорошо устроившихся программистов) – чужая пара глаз иногда вполне способна высмотреть ошибку, над которой вы бьётесь уже несколько дней.

4. Чтение книжек и написание “грамотного” кода. На самом деле, этот раздел можно продолжать бесконечно. Поэтому, возьмите любую книжку по тому, как надо писать код – все они полны советов и методов, которые изложены гораздо полнее, чем у меня есть возможность сделать это тут. Я советую прочитать минимум по одной книжке по ООП (например: Буч), шаблонам (например: GoF) и рефакторингу (например: Фаулер), а заодно – и про качественный код в целом (например: Макконнел). Хорошо при этом также иметь представление (и использовать, конечно же) о разработке с помощью тестов (я не знаю подходящей книги, но знаю неплохой блог на эту тему). Прочитав столько всего полезного, вы сможете писать более чистый код, который легко читать и легко отлаживать. Многие проблемы уйдут сами собой.

В общем: читайте, читайте и ещё раз читайте (и меня в том числе :) ).

5 комментариев :

  1. Спасибо за работу, очень полезный материал!

    ОтветитьУдалить
  2. Только что твой совет помог. FastMM хоть и не показал пальцем где собака порылась, но навел на проблему и она себя выдала! )

    Опция FastMM называется CheckHeapForCorruption.

    ОтветитьУдалить
  3. спасибо! нихрена ничего не понял кроме большой кучи умных слов, у вас утечка памяти шеф!

    ОтветитьУдалить
  4. Позволю себе исправить неточность:
    "SafeMM решает эту проблему убиранием этой прослойки, стараясь напрямую перенаправлять запросы на выделение/освобождение памяти ОС напрямую."
    SafeMM решает эту проблему применением функции VirtualProtect. А чтобы она работала максимально эффективно, он старается вообще не возвращать операционке память.

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

    ОтветитьУдалить

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и (опционально) ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

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