Примечание: если вы плохо или совсем не понимаете, что такое указатели и/или объекты - рекомендую сначала прочитать эту статью.
Хотя любые ошибки в программе - это всегда плохо, но некоторые типы ошибок могут менее болезненно проявлять себя в определённом окружении. Например, ошибки утечки памяти или ресурсов довольно безобидны для клиентских машин (из-за сильно ограниченного времени работы) и довольно опасны на серверах.
Когда новичок загорается идеей поиска утечек памяти (или оптимизации использования памяти), он обычно открывает Диспетчер Задач и смотрит на своё приложение и колонку "Память" напротив него:
И всё бы ничего, но потом он замечает, что колонка эта очень странно себя ведёт даже на простом приложении: память не уменьшается при закрытии форм, но почему-то пропадает при сворачивании приложения и т.п.
Хороший вопрос: а с чего новичок взял, что в этой колонке показывается именно память, выделенная его кодом? Если открыть меню "Вид"/"Выбрать столбцы", то можно увидеть ещё кучу показателей, которые тоже подходят под определение "память" - это и виртуальная память и различные виды пулов.
Так вот, открою вам секрет, что колонка "Память" в Диспетчере задач - это кол-во оперативной памяти, которую занимает ваша программа. Это вовсе не количество выделенной вами памяти (об этом можно было догадаться и самому, как только мы увидели, что у нашей программы пропадает "память" при сворачивании - ведь и ребёнку понятно, что без нашей команды память у нас отнять не могут). Во-первых, ваша программа может занимать меньше ОЗУ, чем вы выделили, т.к. часть данных может быть скинута в файл подкачки. Во-вторых, часть памяти не является эксклюзивно вашей: например, все системные библиотеки хотя и загружаются в каждый процесс, но тем не менее, присутствуют в памяти в единственном экземпляре (впрочем, это не относится к их данным). Это значение также называют "Working Set" или песочницей.
Вот снимок стандартного Диспетчера Задач и более продвинутого Process Explorer со всеми колонками "памяти":
Не правда-ли, большой разброс по значениям?
Поздравляю, вы попались в ловушку очередного мифа о памяти (по ссылке - см. мифы №11 и 13).
Так что давайте выкинем на время Диспетчер Задач и вернёмся к нашему Паскалю. Как в Delphi осуществляется управление памятью? Ранее я уже говорил о наличии кода, который осуществляет централизованное управление памятью. Этот код называется менеджером памяти. Это значит, что искать утечки памяти не очень сложно, т.к. мы можем легко перехватить любые выделения и освобождения памяти, просто подставив свой "менеджер памяти", который будет просто заглушкой. Он не будет реально работать с памятью, перенаправляя запросы старому (настоящему) менеджеру памяти программы, а вместо этого он будет просто вести учёт памяти.
Что значит, что в программе есть утечка памяти? Это значит, что ваш код выделил память и забыл её освободить. Также это значит, что при выходе из программы у вас окажется блок неосвобождённой памяти. Иными словами, будет несоответствие числа вызовов на выделение и освобождение памяти.
Что нам нужно сделать для поиска утечки памяти? Нам нужно запоминать каждое выделение блока, фиксируя, в каком месте оно произошло, а в конце работы пройтись по оставшимся блокам памяти, которые никто не удосужился освободить. Каждый такой блок - это и есть утечка памяти.
Замечу, что сегодня нет смысла писать код диагностики вручную, если только вы не отлаживаете какой-нибудь очень хитрый глюк. В типичных ситуациях вполне сгодятся готовые инструменты, к рассмотрению которых мы сейчас и приступим.
Delphi
Окей, даже в Delphi "из коробки" есть базовая возможность диагностики, так что вы можете как минимум проверить наличие утечек памяти без использования сторонних утилит. Правда, если вы увидите утечку, то чтобы найти источник проблемы, вам всё же придётся воспользоваться чем-то ещё. Ну да ладно.Во-первых, стоит упомянуть о том, что за всю историю Delphi в ней использовалось два менеджера памяти: один - довольно древний и примитивный (я не знаю, имел ли он какое-то название). В новые версии Delphi стали включать модификацию менеджера памяти FastMM - функционально богатого и исключительно быстрого менеджера памяти. Автономная версия FastMM и является одним из инструментов для поиска утечек памяти - его использование мы и расмотрим чуть ниже (кстати, FastMM вполне можно установить и в старые версии Delphi).
Окей, в старом менеджере памяти у нас в распоряжении есть глобальные переменные AllocMemCount и AllocMemSize (кол-во и суммарный размер выделенных блоков). Таким образом, чтобы сделать простейшую проверку на утечки памяти, вам нужно использовать такой простой модуль:
unit Unit2; interface implementation uses Windows; initialization finalization if AllocMemCount <> 0 then MessageBox(0, 'An unexpected memory leak has occurred.', 'Unexpected Memory Leak', MB_OK or MB_ICONERROR or MB_TASKMODAL); end.Не забудьте, что подключать его нужно самым первым в dpr-файле (оба пункта важны принципиально). Если при выполнении программы у вас будет утечка памяти, то при закрытии программы вам будет показано сообщение.
В новых версиях Delphi эти переменные больше не работают. Зато их итоговая функциональность доступна переключением флага
ReportMemoryLeaksOnShutdown
. Всё, что вам нужно сделать в этом случае - установить глобальную переменную ReportMemoryLeaksOnShutdown
в True
при старте программы, например, так:unit Unit1; interface ... { ваша форма } implementation ... { ваш код формы } initialization ReportMemoryLeaksOnShutdown := True; end.Однако, все эти способы просто покажут вам, есть ли у вас утечка памяти или нет. Для того, чтобы диагностировать, кто конкретно тут виноват - вам придётся использовать один из инструментов, перечисленных ниже.
EurekaLog
У EurekaLog тоже есть функциональность по поиску утечек памяти. По-умолчанию она выключена, т.к. её включение не обходится за бесплатно - при её включении слегка замедляется выполнение программы и увеличивается потребляемая память, поскольку теперь надо вести учёт каждому выделению памяти. Кроме того, эта функциональность ограничена: она не доступна в C++ Builder и для приложений, собранных с пакетами, кроме того, она может поймать не более 1024 утечки.В любом случае, для её включения нужно поставить флажок "Catch memory leaks" на вкладке "Advanced options":
Если вы включаете отлов утечек, то при выходе из программы вам сообщат о наличии утечек памяти, если таковые будут:
Как видите, все утечки памяти в программе будут представлены в одном отчёте, который ничем не отличается от обычных отчётов EurekaLog - пользователь точно так же может отправить его вам, как он делает это с обычными отчётами. Единственные два отличия это: отсутствие вкладок CPU и Assembler и пропуск вызовов обработчиков событий. Второе делается по той простой причине, что проверка на утечки делается последним действием в программе, когда никакой служебный код уже не работает. Вызов стандартных обработчиков в этот момент привело бы к плохим вещам.
Несколько под-опций на всё той же вкладке "Advanced options" контролируют поведение проверок утечек памяти.
Опция "Group son leaks with its father" помогает улучшить читабельность отчёта при наличии большого числа утечек. Она скрывает "дочерние" утечки памяти. Например, вы забыли удалить объект. Но в этом объекте есть ссылки на другие объекты. С выключенной опцией "Group son leaks with its father" у вас будет упомянут каждый объект, с включенной - только один: тот самый, корневой, который ссылается на другие объекты. Т.к. при удалении этого объекта пропадут и другие утечки. Вам следует выключать эту опцию, если вы хотите получать более детальный отчёт о проблемах.
Опция "Hide Borland leaks" пытается опознать и скрывать утечки памяти, вызванные недочётами в коде RTL/VCL Delphi. При выключенной опции вы можете получать отчёт об утечке, даже если в вашем коде её нет. Например, код может создать глобальный объект, но никогда не удалять его, в расчёте на то, что память будет освобождена при выходе из приложения. Хотя, строго говоря, это не является программистской ошибкой, это, тем не менее, ошибка проектирования, т.к. она затрудняет анализ приложения на настоящие утечки памяти.
Опция "Free All Memory" говорит сама за себя: надо ли освобождать память для этих самых утечек. В большинстве случаев нет никакой разницы, т.к. при выходе из программы вся память всё равно будет освобождена. В основном эта опция используется для решения проблем совместимости со сторонним кодом.
Об опции "Catch leaks exceptions" мы поговорим в другой раз - это и есть та побочная функциональность диагностики утечек памяти, которая может быть использована для поиска проблем с удалением объектов.
FastMM
FastMM также имеет неплохие возможности по диагностике утечек памяти. К сожалению, встроенная в Delphi версия не обладает полным функционалом, поэтому вам всё равно придётся скачать и установить полную версию FastMM.Собственно, устанавливать ничего и не нужно: достаточно скачать и распаковать архив, после чего добавить модуль
FastMM4.pas
в свой проект (возможно, вам также придётся добавить папку с FastMM в Search Paths проекта). Добавлять модуль, разумеется, нужно самым первым. Если вы используете EurekaLog, то модуль ExceptionLog
должен быть перечислен сразу же за модулем FastMM4. Сразу после подключения FastMM работает в штатном режиме: как отличный менеджер памяти. Различные функции диагностики включаются правкой файла опций. Для этого вы должны открыть файл FastMM4Options.inc
, поменять там опции и сделать Build проекту. Для тех, кто не очень понимает в том, что такое директивы условной компиляции:{.$define AssumeMultiThreaded} - опция выключена {$define AssumeMultiThreaded} - опция включенаИли же вы можете использовать вот эту программку для визуального редактирования опций.
Итак, во-первых, нам понадобится включить опцию
FullDebugMode
, т.к. только с ней становятся доступными возможности диагностики утечек. Включение этой опции означает, что вашей программе теперь будет нужна библиотека FastMM_FullDebugMode.dll
. Скомпилированный вариант можно найти в папке \FullDebugMode DLL\Precompiled\
. Вы также можете скомпилировать свой вариант, если вам нужны другие опции для неё (я скажу об этом чуть ниже). В простейшем случае вам нужно просто скинуть эту библиотеку в папку с программой или в C:\Windows\System32
, чтобы сделать доступной её для всех программ. Последнее удобно сделать на машине для разработки. Замечу, что функциональность по отлову утечек памяти доступна и без включения опции FullDebugMode
, но в этом случае FastMM будет работать так же, как и в варианте с Delphi выше: сообщая только о наличии/отсутствии утечки, но ничего не сообщая о её местонахождении.Из-за этого FastMM в режиме отладки слабо подходит для использования вне окружения для разработки: всё-таки вам придётся таскать за собой отдельную DLL, да плюс ещё замедление работы. Поэтому оптимальным вариантом будет тестирование программы с полным режимом отладки FastMM при разработке, а поставлять программу стоит с выключенной опцией поиска утечек памяти, либо же использовать функциональность EurekaLog. Она выглядит более скромно, чем аналогичная функциональность в FastMM, зато и более пригодна к использованию на машина конечных пользователей.
Итак, кроме включения режима отладки, нам нужно включить ещё функциональность поиска утечек памяти. Это делается опцией
EnableMemoryLeakReporting
. Заметьте, что рядом с FullDebugMode
и EnableMemoryLeakReporting
перечисленны ещё несколько опций, которые контролируют поведение этой функциональности (заметьте, что каждая опция снабжена комментарием в самом файле, так что я приведу только краткое описание):
RawStackTraces
- включает RAW метод трассировки стека. По умолчанию используется стековый метод. Вы можете переключать эту опцию, если стек вызовов вызывает у вас затруднения при чтении. Подробнее об этом я говорю в статье про чтение лог-файлов.LogErrorsToFile
- записывать ошибки (не только утечки) в файл отчёта в папке с программой. Если вы выключаете эту опцию, то вам нужно ловить вывод от FastMM другими способами.LogMemoryLeakDetailToFile
- записывать утечки памяти в файл. Не работает, если выключена опцияLogErrorsToFile
.ClearLogFileOnStartup
- удалять файл-лог при запуске. Если опция выключена, то в один файл будут суммироваться отчёты от нескольких запусков. Может иногда ввести вас в заблуждение: вы можете подумать, что в программе есть утечка, когда файл-отчёт содержит старые записи от предыдущего запуска.DisableLoggingOfMemoryDumps
- отключает запись подробной информации об учетках (дампы памяти). Уменьшает размер отчёта.HideExpectedLeaksRegisteredByPointer
- скрывать утечки памяти, которые были зарегистрированы вызовомRegisterExpectedMemoryLeak
. FastMM не добавляет автоматически утечки памяти в коде самой Delphi, так что если у вас такая ситуация, то вы можете при старте программы вызватьRegisterExpectedMemoryLeak
, передав ей адрес объекта, который утекает (если у вас есть возможность его получить, конечно же).RequireIDEPresenceForLeakReporting
- включение этой опции заставляет программу включать диагностику утечек памяти только при запущенной IDE Delphi.RequireDebuggerPresenceForLeakReporting
- очень похожа на предыдущую опцию, только требует, чтобы программа была запущена именно из IDE Delphi (предыдущая опция требует наличия IDE, программа же может быть запущена и в свободном режиме).RequireDebugInfoForLeakReporting
- а эта опция включает диагностику только, если в программе есть отладочная информация (я об этом скажу ещё чуть ниже).ManualLeakReportingControl
- включение опции позволяет вам использовать глобальную переменнуюReportMemoryLeaksOnShutdown
для ручного включения/выключения контроля утечек памяти.
NoMessageBoxes
- выключает показ сообщений об ошибках.UseOutputDebugString
- включает использованиеOutputDebugString
для вывода сообщений об ошибках. Вывод отOutputDebugString
ловится отладчиком (в Delphi это View/Debug Windows/Events) или программой DebugView при свободном прогоне.
CheckHeapForCorruption
, CatchUseOfFreedInterfaces
и DetectMMOperationsAfterUninstall
мы обсудим в следующий раз, т.к. они напрямую связаны с нашей предыдущей темой.Прежде, чем вы сможете использовать FastMM в своей программе, вам осталось сделать ещё одну вещь: добавить в программу один из вариантов отладочной информации. Если в случае с EurekaLog отладочная информация генерировалась и добавлялась в программу автоматически, а в случае с Delphi она была просто не нужна (т.к. там были простые проверки, не было стеков вызовов), то для FastMM её нужно добавлять вручную. Отладочная информация используется библиотекой
FastMM_FullDebugMode.dll
при построении отчёта для получения информации об адресах памяти в вашей программе. Если отладочная информация будет недоступна, то в отчётах об ошибках вы увидите только безликие адреса, а никаких Button1Click
там не будет.Окей, по-умолчанию,
FastMM_FullDebugMode.dll
скомпилирована с поддержкой JCL, что означает, что DLL может читать отладочную информацию в формате map, TD32 и JDBG. Вы также можете перекомпилировать библиотеку для включения поддержки EurekaLog - в этом случае к уже перечисленным форматам добавится собственный формат EurekaLog. Чтобы сделать это, вам нужно включить опцию EurekaLog
и выключить JCLDebug
в файле FastMM_FullDebugMode.dpr
, после чего перекомпилировать DLL. Тогда для правильной работы FastMM вам нужно просто включить EurekaLog для вашей программы.Если же вы не хотите или не можете перекомпилировать DLL, то вы можете добавить в вашу программу отладочную информацию в виде map-файла, TD32 или JDBG. Первые два варианта поддерживаются Delphi "из коробки": первый включается установкой опции "Map file" в "Detailed" на вкладке "Linker", второй вариант - включением опции "Include TD32 debug info" ("Debug information" в D2009 и выше) на вкладке "Linker" в опциях проекта.
В первом случае будет генерироваться отдельный файл, который вам нужно будет таскать с программой, во втором случае отладочная информация будет включаться в сам исполняемый модуль.
К сожалению, оба эти варианта плохо подходят для распространения приложения, т.к. они значительно увеличивают размер программы. Наиболее компактный вариант - это отладочная информация в формате EurekaLog или JCL. Если с первым я уже всё сказал, то второй вариант предполагает скачивание и установку библиотеки JCL (там есть автоматический установщик) и включение генерации/внедрения отладочной информации:
Скорее всего, вы захотите включить все три опции.
Вот как выглядит одна полная запись об утечке памяти в лог-файле:
--------------------------------2009/5/26 21:52:35--------------------------------
A memory block has been leaked. The size is: 12
This block was allocated by thread 0x4F8C, and the stack trace (return addresses) at the time was:
403206 [System][System.@GetMem]
4CC5B7 [Unit15.pas][Unit15][Unit15.B][35]
4CC5C4 [Unit15.pas][Unit15][Unit15.A][39]
4CC5DC [Unit15.pas][Unit15][Unit15.TForm15.FormCreate][43]
4C0CCB [Forms][Forms.TCustomForm.DoCreate]
4C0913 [Forms][Forms.TCustomForm.AfterConstruction]
4C08E8 [Forms][Forms.TCustomForm.Create]
4CA539 [Forms][Forms.TApplication.CreateForm]
4CD986 [Project14][Project14.Project14][14]
75B94911 [BaseThreadInitThunk]
770FE4B6 [Unknown function at RtlInitializeExceptionChain]
The block is currently used for an object of class: TTest
The allocation number is: 3814
Current memory dump of 256 bytes starting at pointer address 7FF75E50:
A0 C5 4C 00 E8 5E F7 7F 00 00 00 00 F4 20 51 2A 00 00 00 00 E0 4B F7 7F 00 00 00 00 00 00 00 00
FF FF FF FF 00 00 00 00 E7 0E 00 00 06 32 40 00 B7 C5 4C 00 C4 C5 4C 00 DC C5 4C 00 CB 0C 4C 00
13 09 4C 00 E8 08 4C 00 39 A5 4C 00 86 D9 4C 00 11 49 B9 75 B6 E4 0F 77 8C 4F 00 00 22 32 40 00
EF 70 42 00 26 6B 42 00 D9 6A 42 00 5A 9B 42 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 8C 4F 00 00 08 00 00 00 00 00 00 00 81 6F AF 70 0C 11 40 00 00 00 00 00
7E 90 50 8F 80 80 80 80 00 00 00 00 E0 4B F7 7F 00 00 00 00 00 00 00 00 FF FF FF FF 00 00 00 00
E8 0E 00 00 06 32 40 00 DC C5 4C 00 CB 0C 4C 00 13 09 4C 00 E8 08 4C 00 39 A5 4C 00 86 D9 4C 00
11 49 B9 75 B6 E4 0F 77 89 E4 0F 77 00 00 00 00 8C 4F 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Е L . и ^ ч . . . . ф Q * . . . . а K ч . . . . . . . .
я я я я . . . . з . . . . 2 @ . · Е L . Д Е L . Ь Е L . Л . L .
. . L . и . L . 9 Ґ L . † Щ L . . I № u ¶ д . w Њ O . . " 2 @ .
п p B . & k B . Щ j B . Z › B . . . . . . . . . . . . . . . . .
. . . . . . . . Њ O . . . . . . . . . . Ѓ o Ї p . . @ . . . . .
~ ђ P Џ Ђ Ђ Ђ Ђ . . . . а K ч . . . . . . . . я я я я . . . .
и . . . . 2 @ . Ь Е L . Л . L . . . L . и . L . 9 Ґ L . † Щ L .
. I № u ¶ д . w ‰ д . w . . . . Њ O . . . . . . . . . . . . . .
MemProof/AQTime
Эти два инструмента являются профессиональными профайлерами, которые могут диагностировать проблемы не только с утечками памяти, но и с утечками других типов ресурсов. Из всех перечисленных инструментов это - самый функциональный вариант. MemProof - это (бесплатный) предшественник AQTime, который более не поддерживается, но его ещё можно найти на просторах интернета.Я не буду рассматривать использование этих инструментов здесь, т.к. это отдельная большая тема. Интересующихся, отправляю к этой статье.
Замечу, что они позволяют справляться даже с такими проблемами, как неявные утечки памяти. Например, объекты могут не освобождаться вовремя, скапливаясь в каком-либо глобальном списке. Они реально не используются в программе и их число со временем растёт, но, тем не менее, они не считаются за утечку памяти, т.к. список вместе со всеми объектами корректно удаляется при выходе из программы.
Не только утечки...
Как я не раз упоминал тут - утилиты по диагностике учетек памяти зачастую имеют побочную функциональность, которая помогает отловить и другие типы "плохого использования" памяти, в частности, различные проблемы с объектами, которые могут приводить к возникновению Access Violation. Напомню, что Access Violation - это весьма хитрая штука, которая может и не возникать в вашем коде, даже если в нём есть баг. Простейший способ убедиться в этом:var Str: TStringList; begin Str := TStringList.Create; try Str.Add('Test 1'); finally Str.Free; end; Str.Add('Test 2'); // ошибка! Обращение к уже удалённому объекту ShowMessage(Str.Text); end;Хотя этот код содержит глюк, в подавляющем большинстве случае он отлично отработает у вас без единого исключения и даже покажет какой-то результат.
Но об этом мы поговорим в следующий раз.
В любом случае, у вас всегда есть вариант просто переписать код ;)
См. также: как читать отчёты об ошибках.
Читать дальше: ищем утечки памяти, часть 2.
P.S. Ах, да, ещё одно: не важно, какой инструмент вы решили использовать, вы всегда можете улучшить его работу, выбором подходящих опций проекта ;)
Вот тут кажеться ошибка:
ОтветитьУдалитьХороший вопрос:а с чего новичек взял, что в этой колонке [b]показыватся[/b] именно память
Наверное далжно быть [b]показывается[/b]
Спасибо, исправил.
ОтветитьУдалитьВот еще нашел :)
ОтветитьУдалитьFastMM так же имеет неплохие возможности по >диагнотике<
Угу, исправил и это.
ОтветитьУдалитьРаз уш пошла такая пьянка - вот еще пара опечаток :)
ОтветитьУдалитьСообщая только о наличии/отсутствии >учетки<
Итак кроме >включение< режима
Функциональности (>земетьте<, что
Скапливаясь в >како-либо< глобальном
В целом статья отличная :)
Спасибо :)
ОтветитьУдалитьДержи ссылочку в тему:
ОтветитьУдалитьhttp://bishop-it.ru/2009/05/vmmap/
За ссылку на блог спасибо - только не понял, как VMMap может помочь при диагностике утечек памяти.
ОтветитьУдалитьЯ имел в виду не столько поиск утечек, сколько анализ того, на что память расходуется.
ОтветитьУдалитьой статья то какая хорошая
ОтветитьУдалитьсколько ссылок полезных...
5 баллов
по мемчеку ещё пройтись можно было
Кстати, для тех, кто предпочитает "увидеть и пощупать", вместо "скучного" чтения: здесь есть повторы с CodeRage 2. В частности - презентация "Fighting Memory Leaks for Dummies" от Francois Gaillard (ссылка на скачивание - примерно 25 Мб).
ОтветитьУдалитьДа, на английском, но рекомендую к просмотру ;)
Есть хорошая утилита для поиска утечек памяти в C++ - Deleaker - deleaker.ru Мне лично очень нравится.
ОтветитьУдалитьНепонятное какое-то поведение у FastMM. Установил я его в D6, и опробовал на проекте из одной пустой формы. Так он мне заругался на утечки, которых вроде в принципе и быть не может, на всякие THelpManager, Unknown, которых у меня в объекте и нет совсем...
ОтветитьУдалитьчекбокс "Build with runtime packages".. вот где собака порылась %0
ОтветитьУдалитьОказывается, сборка без рантаймовских пакаджей приводит к сообщениям об утечке памяти, причем, неважно, пусть в списке только штатные vcl, rtl пакеты - утечка уже диагностируется.. Что за ерунда..??
мдя... как только убрал галку "Build with runtime packages", избавился в том числе и от сообщений о настоящих утечках памяти... Что за своеобразное поведение у FastMM...
ОтветитьУдалитьспасибо за статью. Руки дойдут надо прикрутить это всё.
ОтветитьУдалитьMemCheck.pas (http://v.mahon.free.fr/pro/freeware/memcheck/) очень симпатичный ловец утечек. Один модуль, представляет собой обёртку вокруг менеджера памяти. По завершении программы выдаёт исключения по каждой утечке, позиционируя отладчик на месте выделения памяти. Заодно пишет лог.
ОтветитьУдалитьГм, второе упоминание о MemCheck :)
ОтветитьУдалитьГоворят, вещь хорошая, но я сам не смотрел, потому что проект уже не развивается - не обновляется с 2007-го года. Т.е. никаких тебе новых Delphi без допиливания напильником.
Не стесняйтесь сделать обзор сами, если хотите, и прилинковать сюда.
Благодарствую за статью, мне помогла :)
ОтветитьУдалитьА вот и неправда ваша по поводу MemCheck. Я тоже долгое время думал, что проект загнулся и больше не развивается. Однако недавно наткнулся вот на это. Так-что проект продолжают развивать и адаптирован он уже вплоть до Delphi2010.
ОтветитьУдалитьПарни,я не программист и не соображаю ничего тут как вы,но у меня вопрос,после закрытия одного приложения у меня выдаёт ошибку о каком то FastMM,на работу это вроде никак не влияет,но хочу спросить что это и как исправить?
ОтветитьУдалитьотличная статья, спасибо!
ОтветитьУдалитьТолько я воспользовался MemCheck-ом :)
Пробовал в Builder C++ 2009 использовать данну переменную (ReportMemoryLeaksOnShutdown), незаработала:( Отключил RTL и Runtime packages, тот же эффект. Может что в настройках еще надо изменить?
ОтветитьУдалитьИнтересно еще, насколько сильно менеджер памяти билдера отличаеться от дельфи?
Вообще свелся к мысли что использование CodeGuard + memprof достаточно, чтобы отловить утечку ресурсов.
RTFM: http://docwiki.embarcadero.com/Libraries/Tokyo/en/System.ReportMemoryLeaksOnShutdown
УдалитьWarning: ReportMemoryLeaksOnShutdown only works on Delphi applications, it has no effect in C++ applications and in packages.
Я не в курсе про C++ Builder.
ОтветитьУдалитьКстати madCollection теперь обзавелась шикарным инструментом по контролю за утечкой ресурсов (в том числе и памяти).
ОтветитьУдалитьНе совсем понятно, что указано в квадратных скобках.
ОтветитьУдалитьНапример, Line 35[1], т.е. что означает 1?
Смещение от начала метода.
ОтветитьУдалитьЗдравствуйте!
ОтветитьУдалитьСобранная для использования с EurekaLog dll-ка FastMM_FullDebugMode.dll выдаёт сообщение: You must activate EurekaLog in the "FastMM_FullDebugMode.dll" module. В коде в ExceptionLog инициализация отменяется вот в этом месте процедуры Init:
...
if not IntoIDE then
begin
{$IFNDEF CBuilder}
Hook_InitializationAndFinalization;
{$ENDIF}
if (not IsEurekaLogModule(HInstance)) then
begin
Initialized := False;
Exit;
end;
...
Может подскажете как решить эту проблему? Ах, да - Delphi 7, EurekaLog 6.0.15.
Спасибо.
А как собирали-то? Сообщение говорит о том, что код EL в DLL есть (читай: добавлены модули), но вот DLL с EL скомпилирована не была (читай: нет данных EL - опций, отладочной информации).
ОтветитьУдалитьЕсли в IDE стоит эксперт EL, то в свойствах проекта EL нужно её включить. Если эксперта не стоит, то нужно руками включить генерацию .map файла, создать .eof файл с настройками EL и пост-процесснуть DLL после компиляции вызовом ecc32.
В IDE всё включаю (EL активна) и для DLL-ки и для тестовой проги, но в DLL-ку в ресурсы почему-то не записывает ресурс ELDATA из которого считывается MagicNumber по которому и определяется что собрано с EL. В экзешник в ресурсы вписывает. Короче похоже это какой-то старый баг EL
ОтветитьУдалитьПохоже дело в этом:
ОтветитьУдалитьTUnhandledModules: set of TModuleType =
{$IFDEF PROFESSIONAL}
[mtUnknown];
{$ELSE}
[mtUnknown, mtPackage, mtLibrary];
{$ENDIF}
В списке необслуживаемых - mtLibrary - поэтому map-файл dll-ки не обрабатывается EL.
Что-то я совсем ничего не понял. Вы ломаный Trial, что-ли, используете?
ОтветитьУдалитьВо-первых, зачем вы пересобираете ecc32 (или IDE-эксперт)?
Во-вторых, директива PROFESSIONAL включена в любых версиях EL, кроме Trial.
[Fatal Error] Alpha1.dpr(8): File not found: 'FastMM4Messages.dcu'
ОтветитьУдалитьТолько начинаю изучать Делфи.В чем может быть проблема?Сделал все как написано.Не могу разобраться что к чему
А вы где FastMM-то брали? Просто если брать где надо, то все необходимые файлы там прямо в корне лежат.
УдалитьАлександр, подскажите, может, я что-то не так делаю, но после локализации приложения встроенными средствами Delphi (Seattle) по его закрытии выводится сообщение "FastMM has detected a FreeMem call after FastMM was uninstalled" и после нажатия OK еще одно "Runtime error 204". Столкнулся с этим на своем проекте и повторил ситуацию на простеньком тестовом с одной формой и одним TLabel. Причем если удалить запись в HKEY_CURRENT_USER\Software\Embarcadero\Locales, относящуюся к моему приложению, то сообщение не появляется.
ОтветитьУдалитьНе понятно совсем, почему это происходит (догадываюсь, что связано с попыткой повторной выгрузки ресурсов) и существует ли какой workaround для этого?
Известные грабли. Не сказать, что совсем баг, скорее "особенности дизайна". Решается использованием опции типа NeverUninstall, но при этом будут регистрироваться ложные утечки памяти.
УдалитьАлександр, подскажите как подключить jcl debug expert. Вроде устанавливаю, а в меню не вижу
ОтветитьУдалитьКак-то неправильно ставите, значит.
УдалитьСамо собой, JCL - библиотека кода и поэтому не нуждается в установке, можно просто указать её папку в путях поиска. Но чтобы в IDE появились примочки - тут установка нужна. Проще всего это делается запуском Install.bat в корне - он скомпилирует установщик (\bin\JediInstaller.exe) и запустит его. Сделать это .bat-ник сможет только при условии, что у вас рабочий dcc32.exe. Например, в редакции Starter он отключен, поэтому JCL автоматически установить не удастся - только вручную.
В установщике JediInstaller будет набор вкладок - по одной на каждую установленную у вас версию Delphi. Если вкладки нет, то либо установщик вашу IDE не поддерживает (скачайте последнюю версию JCL), либо пора делать Repair самой IDE (отсутствует регистрация IDE в реестре).
На вкладке указываются пути, куда складывать BPL и DCP, а также опции установки. Убедитесь, что вы ставите галку JEDI Code Library / Packages / IDE Experts / Debug Extension - эта опция скомпилирует JclDebugExpert. (Лично я включаю все галки, кроме Demos.) Жмёте Install - и установщик скомпилирует и установит пакеты. Следите за логом, не будет ли там ошибок.
Готовые пакеты (BPL/DCP) попадут в папки, указанные на вкладке. Путь может выглядеть как-то так: C:\Users\Public\Documents\RAD Studio\8.0\BPL или так: C:\jcl\jcl\lib\d17\win32\
Пакеты можно скомпилировать и вручную - откройте подходящий \packages\JclPackagesDxxx.groupproj и сделайте Build All. Затем на каждом пакете правой щёлкаете и - Install. А если у вас уже есть скомпилированные BPL, то в самой среде - Components / Install Packages и Add - выбираете BPL. Ну и убедитесь, что галка в списке пакетов напротив JCL экспертов не сброшена.
Да, и всегда скачивайте свежую/последнюю версию JCL.
Спасибо за развернутый и оперативный ответ!В общем вчера качал видимо что-то не то! Скачал отсюда http://wiki.delphi-jedi.org/wiki/JEDI_Code_Library и произвел установку(manual) как описано здесь: http://wiki.delphi-jedi.org/wiki/JCL_Installation#Manual_Installation. И вуаля!
УдалитьЗдравствуйте, Александр!
ОтветитьУдалитьПодскажите пожалуйста почему в лог файле события дублируются? Я вызываю событие единожды, но в лог файле записей больше сотни!
Line 819372: CF1C8F [frxClass.pas][frxClass][frxClass.TfrxReport.Create][11611]
Line 819410: CF1C8F [frxClass.pas][frxClass][frxClass.TfrxReport.Create][11611]
Line 819448: CF1C8F [frxClass.pas][frxClass][frxClass.TfrxReport.Create][11611]
Line 819486: CF1C8F [frxClass.pas][frxClass][frxClass.TfrxReport.Create][11611]
Line 819524: CF1C8F [frxClass.pas][frxClass][frxClass.TfrxReport.Create][11611]
Line 819562: CF1C8F [frxClass.pas][frxClass][frxClass.TfrxReport.Create][11611]
Line 819600: CF1C8F [frxClass.pas][frxClass][frxClass.TfrxReport.Create][11611]
Line 819635: CF1C8F [frxClass.pas][frxClass][frxClass.TfrxReport.Create][11611]
Line 819676: CF1C8F [frxClass.pas][frxClass][frxClass.TfrxReport.Create][11611]
Line 819714: CF1C8F [frxClass.pas][frxClass][frxClass.TfrxReport.Create][11611]
Line 838447: CF1C8F [frxClass.pas][frxClass][frxClass.TfrxReport.Create][11611]
Я не телепат. Я понятия не имею, какой инструмент вы используете для поиска утечек, какое событие вы вызываете (и зачем).
УдалитьМогу предположить, что у вас куча однотипных утечек. Т.е. утечка в одном месте, и это место вызывается множество раз. В этом случае можно расследовать и починить только один (первый) отчёт, все остальные пропадут автоматически.
Подскажите можно ли в исходном коде (через дерективы) включить/отключить генерацию TD32 или Detail map? Это команды для linker, и в документации такой штуки не нашел, но возможно есть какие-то способы?
ОтветитьУдалитьВозможно такое можно сделать с JDBG?
Поясню зачем, есть код, который будет компилироваться под разные версии Delphi, хотелось бы управлять через *.inc файл.
УдалитьНапример: для Debug принудительно включать генерацию отладочной информации, а для Release отключать.
Конечно же нельзя, ведь это не опции для компилятора (compiler), это опции для компоновщика (linker). Компоновщик с исходным кодом не работает. С исходным кодом работает компилятор.
УдалитьНе очень понятно как вы делаете компиляцию. Обычно делают так: настраивают два профиля (Debug и Release). Дают им разные опции. И включают в профиле Debug символ условной компиляции DEBUG. Ну а в исходном коде уже проверяют символ: {$IFDEF DEBUG}. Т.е. идут от профиля к коду, а не наоборот.
В старых Delphi профилей нет. Вместо этого можно использовать два проекта.
Наконец, можно генерировать map/tds всегда. И из релиза их просто удалять. Они не влияют на машинный код, это только сопутствующая информация. (для TD32 в опциях проекта можно указать, что инфу внедрять в .exe не надо, а надо создавать отдельный .tds файл; в старых Delphi такой опции нет, вместо неё можно использовать утилиты вроде StripTDS).
Все это я понимаю. Использую старую версию. Уточнил что директив для linker нет в доке, но возможно есть "хаки".
УдалитьА команды компоновщику через директивы в исходном коде в с++ например через #pragma comment(linker,.. вполне передают, может думаю что-то для Delphi есть, чего не знаю.
debug/release задаю руками в inc файле:
{$IFNDEF RELEASE}{$IFNDEF DEBUG}
{$DEFINE BUILD_TYPE_IDE_NOT_SUPPORT}
{.$DEFINE RELEASE}
{$IFNDEF RELEASE}{$DEFINE DEBUG}{$ENDIF}
{$ENDIF}{$ENDIF}
Потом опции.
За StripTDS спасибо. Вспомнил еще про DDevExtensions.
А еще по поводу "компоновщик/компилятор". Забавная деталь: опция «Использовать Debug DCU» в настройках проекта почему то находиться на вкладке компилятора.
УдалитьТехнически, это опция компоновщика, конечно. Ибо это замамкированная search paths (посмотрите как они меняются при переключении этой опции).
УдалитьНо логически это аналог $D+/-. Видимо, поэтому её рядом и поместили.
Иными словами, это всё равно опция компоновщика и передаётся она компоновщику (search paths).
> debug/release задаю руками в inc файле
УдалитьНу и зачем вы это делаете?
Уже не делаю, в старой версии Delphi установил DDevExtensions.
УдалитьАлександр, а как бы вы посоветовали делать? Посоветуйте ваш способ организации разных build профилей на старых версиях Delphi.
ОтветитьУдалитьЛично я использую три варианта:
Удалить1). Не использовать старые версии Delphi (серьёзно, 15 лет назад всё же).
2). Использую два проекта с одинаковым содержимым, но разными настройками (например, Project_Release.dpr и Project_Debug.dpr). При этом, само собой, DPR не содержит кода, а лишь вызывает основную процедуру из главного модуля (это для ручного управления).
3). Использую один проект и конфигурирую его в DEBUG. Проект для RELEASE собирается как часть сборки релизного билда чем-нибудь вроде FinalBuilder (в самом деле, зачем RELEASE компилировать из IDE?).
Что-то мне подсказывает, что на полную вы используете только первый вариант :)
Удалить1) Не всегда есть возможность купить новую версию.
2) Проходил это, сильно не удобно. Несколько примеров: Есть идея, нужен прототип, запускаем Делфи, (оп, нужно же второй проект создать, иногда лень), через несколько часов разработки оказывается что "релиз" проект сильно устарел и запустить его быстро не получиться, нужно например прописать пути (второй оп), а бывает так, переключился на проект "релиз": а где этот файл? а мы его не добавили в "релиз" проект (3-ий оп). Может еще что забыл, помню что отказался от такого подхода. Не смертельно, но неудобно.
3) Не понятно тогда, зачем все IDE дают возможность создавать build профили. Было бы Debug и команда Deploy сразу на стол заказчику. Ну а если серьезно то этот сценарий хорош например для крупного проекта долгожителя, где один раз настроил конфигурацию сборки и разрабатываешь. Причем предметная область задачи явно не завязана на различие генерируемого debug и release кода. Но сценарии бывают разные. Тот же второй пункт сюда (быстрое прототипирование). А если задача написать например stack tracer. Все же профили сборки в IDE это даже не плюс, а стандарт.
Для старых версий, на мой взгляд, нет ничего удобней DDevExtensions, который дает возможность создавать даже более двух профилей сборки.
По поводу куска кода с defines, который я указал выше. Мне удобно при разработке библиотек под разные версии компилятора или с дополнительными опциями которые задаются через defines. И так как там уже есть свои defines, без них никак, то сюда же удобно перенести переключение Debug/Release. Все в одном месте, удобно. Ну это для старых версий среды, в коде видно что работает он если не заданы ни RELEASE ни DEBUG. Опять же DDevExtensions проблему решает, если он установлен.
Блог еще живой?
ОтветитьУдалитьЯ не знаю, баг это FastMM или он так задуман - он не дает работать с SendMessage и Thread.
Суть.
Unit1: Form с двумя Button и двумя Edit.
const
MESS_1 = WM_USER+1;
MESS_2 = WM_USER+2;
....
procedure GetText(var Msg: TMessage); message MESS_1;
procedure SetText(var Msg: TMessage); message MESS_2;
....
procedure TForm1.GetText(var Msg: TMessage);
begin
Msg.Result:=integer(PChar(TAccessControl(Msg.WParam).Text));
end;
procedure TForm1.SetText(var Msg: TMessage);
begin
TAccessControl(Msg.WParam).Text := PChar(Msg.LParam);
end;
//--------------------
Unit2: Thread, который через SendMessage отправляет и получает данные формы.
function TNew.GetText: string;
var
i: integer;
begin
i:=SendMessage(Form1.Handle, MESS_1, WPARAM(Form1.Edit1), 0);
result := PChar(i);
end;
procedure TNew.SetText(s: string);
begin
SendMessage(Form1.Handle, MESS_2, WPARAM(Form1.Edit2), LPARAM(PChar(source)));
end;
Без FastMM всё работает как надо: берет текст из одного Edit и ставит в другой.
Подключаю FastMM, берем текст. А там какой-то мусор в итоге. Значение PChar не меняется в результате передачи, а вот по адресу какая-то шляпа.
Я сперва подумал, что возможно с Юникодом что-то, перевел строку в набор hex. Даже близко не похожы.
В чем может быть дело? Я уже весь гугл облазил.
Сам задал, сам отвечу. Т.к. SendMessage возвращает Integer, а не string, то я помещал в integer адрес начала строки. Но! "В отладочном режиме (т.е. если включена опция условной компиляции FullDebugMode) FastMM при удалении объекта "портит" память, занимаемую ранее объектом, таким образом, что при следующей попытке вызывать виртуальный метод удаленного объекта FastMM возбуждает исключение." Т.е. строка была ЛОКАЛЬНАЯ для ф-ии, при выходе из ф-ии строка как бы всё. FastMM ее "портит" специально, поэтому всякая туфта была на выходе.
УдалитьВыход: задать строковую переменную глобальную в форме и через нее получать значение едита.
Edit не хранит сам строку текста. Соответственно, при вызове свойства .Text он вынужден создавать новую строку. Которая и удаляется из памяти при выходе из подпрограммы. Если бы он хранил текст самостоятельно (в поле объекта) и возвращал бы значение поля без изменений, то код бы работал - благодаря ссылкам для строк.
УдалитьЕсли честно, я до этого вообще из потока брал просто Form1.Edit1.Text :) Т.к. считал, что обращаюсь к свойству VCL объекта, не изменяю его... просто читаю. А вот изменял через SendMessage и Synchronize. И так делал, пока не связался с Frame. Вот что было: если создать динамически фрейм, где лежит VCL объект, но фрейм я еще не увидел - он в другой вкладке PageControl, то при попытке прочитать св-во объекта из потока, вся форма висла намертво. Но удивительно, что если этот фрейм посмотреть (простой перейти в другую вкладку PageControl) и обратиться напрямую к св-ву объекта из потока, то все работает :)
Удалить