Помнится тут недавно была небольшая "разборка" насчет использования явных и неявных конструкторов-деструкторов.
Большинство коллег (в том числе и я), кроме Вадима, сошлись на том, что использование неявных Construct/Destruct для классов не есть криминал а даже наоборот - круто и удобно.
Теперь я хочу поправиться - БЫЛО удобно и надежно до выхода последней версии C60!
Как я уже упоминал в этой рассылке, разработчики в одном из релизов C60 изменили порядок неявной инициализации обьектов.
Чем внесли приличный бардак в ранее стройную систему инициализации.
Они неявно это и сами признали, введя в последних релизах C6 новый ключ проектной системы для компилятора init_priority, позволяющий разработчику в случае необходимости задавать явный порядок инициализации своих модулей. Вплоть до того, что можно инициализировать свои модули вообще в первую очередь.
Можно сказать - удобно! НО! Удобно, если четко знаешь порядок инициализации ВСЕХ обьктов программы! В том числе конструкторов ядра и драйверов. Но об этом, как всегда, разработчики SV забыли нас проинформировать! А не имея такой инфы, вышеупомянутый ключ просто превращается в мертвую опцию, которую рискнет использовать лишь экстремал-экспериментатор!
Но и это не все!
Разработчики так-же ЗАБЫЛИ упомянуть, что вместе с системой инициализации обьектов подверглась "переработке" и система их уничтожения! Причем настолько круто, что я просто пребываю в небольшом шоке! Возможно я чего не понимаю, но тому, что они сделали с шатдауном программы я не могу найти приличного определения!
А именно - при сборке приложения с внешними либами, без использования DLL (режим сборки Local), при завершении приложения сначала уничтожаются все глобальные обьекты библиотеки а уже потом - глобальные статические экземпляры, порожденные классами этой библиотеки!
Т.е., предположим, подключили к программе некую либу, которая содержит реализацию класса MyClass.
Для связи различных экземпляров этого класса или для других каких целей в этой либе обьявлены некие глобальные обьекты.
Конструктор и деструктор класса MyClass этой либы использует каким-то образом эти глобальные обьекты. Никакого криминала нет! Даже используем синхронизацию доступа к этим глобальным обьектам. Вообщем - все по правилам!
Теперь в секции глобальных данных программы пишем:
NewClass MyClass ! или CLASS(MyClass).
Все! В большинстве случаев получаем устойчивый GPF при завершении программы!
Как я уже упомянул выше, при завершении сначала будут уничтожены глобальные обьекты самой либы, в которой реализован класс MyClass, а уже после этого будет вызван деструктор экземпляра NewClass. А он, по условиям, использует те самые глобальные обьекты либы, которые прямо перед этим были уничтожены "заботливым" компилятором!
Особенно "красиво" получается именно с синхронизацией доступа к этим глобальным обьектам. Что-бы эта синхронизация работала так как необходимо, ее управляющий буфер должен быть глобальным по отношению ко всем процессам, которые нуждаются в такой синхронизации. И вот именно этот буфер и будет уничтожен первым, до того, как отработают все деструкторы использующие его для синхронного доступа к другим глобальным обьектам либы.
Вот теперь и думаю - то-ли вообще полностью отказаться от глобальных обьектов в библиотеках и искать другие пути взаимодействия различных экземпляров или "наплевать" на рекомендации SV по синхронизации общего доступа к этим глобальным обьектам. Хотя, с другой стороны, при таком завершении программы уже не имеет никакого смысла работа с уничтожеными глобальными данными! Бред!
А перейти к так любимой Вадимом системе явных Init/Kill, увы, не получается - теряется часть функционала.
Ну, ладно - поплакался и достаточно - пойду посплю!
Может и правда - "утро вечера мудренее" - встану "утром" ближе к обеду:)) - глядишь и проблема сама собой "рассосется"!?
Удачи!
=============================
С уважением, Олег А. Руденко.
Oleg_Rudenko@mail.ru
Oleg_Rudenko@mail333.com
Библиотека DynaLib
http://dynalib.narod.ru
(Добавление)
В догонку к вчерашнему письму.
Описанная в предыдущем письме ситуация с неверным, имхо, порядком уничтожения обьектов при шатдауне программы, если и не приведет к GPF, то вполне может стать источником утечки памяти. Правда, актуально это только на ОС типа W9x, которые не всегда корректно "подчищают" хвосты за програми.
Предположим в либе есть глобальная очередь, в которой регистрируются экземпляры, созданные классами этой либы.
Естественно, что конструктор экземпляра заносит запись о себе в эту очередь, а деструктор убирает эту запись из очереди. При такой ситуации вышеописанна проблема не приведет к GPF при доступе деструктора к глобальной очереди. Но, как я уже писал, для этой очереди уже будет выполнен ее деструктор, который уничтожит все внутренние буфера. А при ЛЮБОМ действии с такой очередью производится повторная инициализация всех этих внутренних буферов.
Вот и получаем:
- сначала, при уничтожении глобальных переменных либы, будет выполнен деструктор и для этой очереди, который освободит всю память, занимаемую внутренними буферами.
- дальше, в деструкторе экземпляра, будет выполнена попытка чтения из этой очереди. Эта попытка вернет ошибку 30 или 35.
Но! Одновременно с этим будет выполнена начальная инициализация внутренних управляющих буферов этой очереди!
- программа завершается. И при этом, естественно, уже повторно не будет вызван деструктор для глобальной очереди.
И если ОС корректно не "подчистит" за такой прогой "хвосты", то получим "высячую" память и, кроме того, еще и выбывшие из оборота дескрипторы памяти, кол-во которых у ОС типа W9x имеет тенденцию очень быстро уменьшаться!
Кстати, надо-бы еще проверить как обстоят дела при завершении трейдов в новой версии. А то, чем черт не шутит, еще и там получим подобный сюрприз, что приведет уже к реальной потери памяти во время работы программы! И, что самое печальное, такие сюрпризы просто так не обнаружишь - в коде-то все чисто!
=============================
С уважением, Олег А. Руденко
(Добавление)
Грустно, однако!
В предыдущем письме я высказал предположение, что описанная в этой теме проблема с порядком инициализации/деинициализации обьектов в последних версиях C60, имеет место быть не только при завершении программы, но и при завершении трейда.
Честно говоря, я очень надеялся что это не так. Увы!
При завершении трейда имеем абсолютно ту-же картину.
Кстати, в первом письме я уже упоминал о ключе компилятора, для явного задания приоритета модулей при инициализации.
Так вот, оказывается этот ключ действует и на приоритет деинициализации модулей. Вот только не так, как ожидалось.
Может я не прав, но я всегда следовал правилу FILO (первым вошел, последним вышел). Т.е., если какой-либо модуль инициализируется первым, то этот-же модуль должен уничтожаться последним. Иначе просто-напросто нарушаются рамки вложения процессов инициализации/деинициализации.
Однако разработчики SV считают по другому - если модуль был инициализирован первым, то и деинициализация его производится так-же первым. Бред, имхо!
Итак, возвращаемся к теме.
Предположим имеем все ту-же либу MyLib, которая линкуется к приложению в режиме Local. В этой либе есть класс MyClass и глобальная очередь регистрации экземплятор этого класса MyClassQueue. Полностью следуем рекомендации SV о том, что желательно глобальные данные делать трейдовыми и обьявляем эту очередь как MyClassQueue QUEUE,THREAD.
В тестовом приложении описываем глобальный экземпляр класса MyClass и делаем его так-же трейдовым:
MyFirstClass MyClass,THREAD
Ну, вот. А теперь кто может мне сказать, что программа написана неверно!? Никаких ошибок и ляпов нет!
Однако, при работе такой программы получим постоянную "утечку" памяти при завершении процессов. Любых!
Как и в предыдущем письме, здесь так-же имеем:
- при завершении трейда первым будет выполнена деинициализация трейдовых обьектов самой либы MyLib.
При этом будет вызван деструктор для очереди MyClassQueue, который освободит память, занятую всеми внутренними управляющими структурами этой очереди.
- следом будет выполнена деинициализация трейдовых обьектов уже программы. При этом будет вызван деструктор экземпляра MyFirstClass.
Мало того, что мы получим в этот момент уже очищенные глобальные трейдовые статические переменные и уничтоженные динамические (т.е. уже имеем потенциальные проблемы, могущие привести как неверной логике последующей работы программы, так и к банальному GPF при обращении к динамическому глобальному трейдовому обьекту.
Мы стабильно получаем повторную иниицализацию очереди MyClassQueue с выделением памяти для ее внутренних управляющих структур.
- полное завершение трейда с потерей памяти, выделенной на предыдущем шаге для внутренних структур очереди MyClassQueue!
Вышеописанных проблем можно избежать ТОЛЬКО в случае обьявления экземпляра MyFirstClass в секции локальных данных процедуры, в которой он используется. В этом случае ВСЕГДА первыми отрабатывают деструкторы локальных обьектов.
Правда, тогда непонятно, что делать, если необходим глобальный экземпляр по отношению к текущему трейду?!
Получаем, как это ни печально, вывод - использование во внешних либах ЛЮБЫХ глобальных обьектов потенциально чревато проблемами при использовании классов этих либ. Вот такой вот неутешительный вывод!
2Сергей Чушкин.
Может ты, все-таки, попробуешь донести до разработчиков SV информацию о данной проблеме. Я как-то пытался им обьяснить, что их новый порядок инициализации обьектов потенциально ведет к нестабильной работе приложения. Они тогда только отмахнулись - "у нас все нормально - у самого кривые руки"!
Правда, их что-то все таки заставило ввести этот ключ init_priority.
И вот теперь еще одна проблема - уже с порядком деинициализации обьектов.
Мне, честно говоря, уже даже и нехочется с ними общаться по этому поводу! Но что-то делать надо-же!
Или пускай так прямо и напишут в доке - "в связи с новой логикой иниицализации/деинициализации обьектов НЕ рекомендуется использовать в готовых библиотеках (LIB-модули) глобальные обьекты, которые будут использоваться конструкторами/деструкторами классов этой библиотеки" !!!
=============================
С уважением, Олег А. Руденко
(Добавление)
ТАМ, в SV работают больные люди, им надо терпеливо и настойчиво указывать на их ошибки. Пости эту инфу в clarion6 на их новостной сервак, с периодичностью раз в месяц. Тогда межет и будет эффект. Подобные проблемы ставят под вопрос готовность C6 вообще, что конечно очень грустно. Думаю "широкой" кларионовской общественности это не очень понравится и SV вынуждены будут принимать меры. Они конечно будут ругаться и посылать тебя в закрытую группу, bugreports или как она там называется. Но ее-то не читают потенциальные клиенты, значит и пинок получится слабым. А им хороший пинок нужен, волшебный такой пендаль.Я как-то пытался им обьяснить, что их новый порядок инициализации обьектов потенциально ведет к нестабильной работе приложения. Они тогда только отмахнулись - "у нас все нормально - у самого кривые руки"!
Угу, а кому сейчас легко.Мне, честно говоря, уже даже и нехочется с ними общаться по этому поводу! Но что-то делать надо-же!
Удачи!
__________________________________
Владимир Якимченко (IСQ 16 993 194)
В том-то и дело, что лики памяти у завершающегося процесса никого не волнуют - процесс умер, вся память отошла системе. А вот в случае потоков действительно грустно получаетсяПри завершении трейда имеем абсолютно ту-же картину.

--
Best regards,
Maxim Yemelyanov,
Enigma Soft Company
phone: +380 572 177977
WEB: http://enigmasoft.com.ua
e-mail: clalist@enigmasoft.com.ua
ICQ: 12253836
Написал: ClaList(2)