В сети есть статьи об использовании MSVC без CRT. Например, by Matt Pietrek, на RSDN и UINC. Как правило пишут про 6ю (12ю) версию компилятора и Си. Провожу некоторые опыты с .NET 7.1 (13.1) и C++ и наблюдаю странные явления Следующий код без проблем компилируется intel'овским компилером (8.1) Код (ASM): #pragma comment(linker, "/nodefaultlib:libc.lib") class foo_class { ~foo_class() { some_cleanup(); } }; static foo_class * global_bar; long __stdcall filter(EXCEPTION_POINTERS * p) { global_bar->~foo_class(); return EXCEPTION_EXECUTE_HANDLER; } int main() { SetUnhandledExceptionFilter(filter); foo_class bar; global_bar = &bar; // return 0; } MSVC начинает плакать Код (Text): error LNK2019: unresolved external symbol "void __cdecl operator delete(void *)" (??3@YAXPAX@Z) referenced in function "public: void * __thiscall foo_class::`scalar deleting destructor'(unsigned int)" это он на Код (Text): global_bar->~foo_class(); строка Код (Text): void __cdecl operator delete(void *){}; проблему "решает". Однако, это создаст кучу новых проблем, когда delete действительно потребуется Другой вариант - использовать вместо деструктора отдельный метод с такой же функциональностью и вызывать его явно. То есть, такой же кривой, как и предыдущий Собственно, вопросы: - использование delete - баг компилятора или я что-то (как всегда) не понял? - кто-нибудь таким занимался? с какими ещё проблемами можно\придётся столкнуться?
В случае MSVC по-моему если реализовать new и delete как в той статье на RSDN (реализация там взята из atlinit.cpp) придется делать еще следуещее (добавить в код): Код (C): extern "C" HANDLE _crtheap; ... _crtheap = HeapCreate(...) // с нужными параметрами // В случае наличия чисто виртуальных (и наверное просто виртуальных) классов функций и т.п. int __cdecl _purecall() { return 0; }; Вполне возможно что я где то намудрил... В общем посмотрите файлы purevirt.c atlinit.cpp heapinit.c (они где то в папке Vc7 в Visual Studio)
Используй автоматические переменные а не указатели. И не понадобится Delete. НО автоматические переменные должны быть объявлены не в global scope. Т.е. они должны быть объявлены внутри функции. В противном случае они не будут работать. по той причине, что инициализацией глобальных переменных занимается опять же CRT.
В том-то и дело, что у меня автоматическая (локальная для main() ) переменная, и new\delete для неё совершенно не требуется (свою реализацию написать можно, но они не нужны в этом контексте). Если убрать фильтр исключений - всё работает на ура. Какого икса MSVC пытается влепить delete при обращении через глобальный указатель - непонятно :-( А без него тоже не понятно как обойтись - требуется в фильтре вызывать деструктор. ЗЫ: где-то в MSDN видел про инициализацию глобальный переманных в CRT, а сейчас найти не могу
S_T_A_S_, больше похоже на бред. Я совсем недавно с эксепшенами игрался - не требовалось там delete. У меня все компилится следующим образом: Код (C): #pragma comment(linker, "/ENTRY:WinMain") #pragma comment(linker, "/SUBSYSTEM:WINDOWS") #pragma comment(linker, "/NODEFAULTLIB:LIBC") #pragma comment(linker, "/NODEFAULTLIB:LIBCP") Хотя.. Классов у меня там нет. Но ты можешь поступить следующим образом: Код (C): void operator delete( void * p ) { free( p ); } Это код оператора delete из CRT. Кто тебе мешает просто такую заглушку вставить? У меня возникает подозрение, что этот при использовании эксепшена происходит раскрутка стека, и соответсвенно вызов всех дестркуторов. НО если я не ошибаюсь - это фича С++, а не WinAPI. Соответственно не могу понять - причем тут дестркуторы. Если бы ты try/catch использовал - там вполне понятно. Но в этом случае - не понятно. В общем сделай реализацию delete, как я выше написал и не парься
S_T_A_S_ Дело в том, что foo_class::`scalar deleting destructor' используется компилятором для уничтожения как статических так и динамических экземпляров foo_class (тип уничтожения определяется параметром `scalar deleting destructor'). Т.е. в данном случае operator delete в `scalar deleting destructor' никогда не будет вызван, но из-за того что компилятор не инлайнит эту ф-цию, возникает этот unresolved. Можно так: Код (C): class foo_class { ~foo_class() { some_cleanup(); } void operator delete(void*){} };
rst > Дык и вставляю Не пойму, почему delete вообще там нужно > Полностью согласен, что раскрутки стэка не должно быть в моём случае. Я как раз и пытаюсь "сэмулировать" некоторые вещи CRT и вызываю деструктор вручную (для этого и замуты с глобальным поинтером). Вот почему MSVC в деструкторе вызывает delete, а Intel нет - ? green > В моём случае экземпляр auto. Как я понимаю, для него delete вызываться не должен, он же в стэке. > Вот вызов global_bar->~foo_class(); после Intel C++ 8.1 Код (Text): mov ecx, [406368] ; __thiscall call 004021B4 А это MSVC 7.1 (с заглушкой void __cdecl operator delete(void *){} Код (Text): push 0 ; <- ??? этот аргумент для delete mov ecx, [405B98] call 00401A4F Видно, что MSVC его всё-таки вызывает. Более того, если линкую с crt, то начинают зачем-то импортироваться HeapXXX и т.п. функции + добавляет кучу кода к exe. Intel этого не делает. > Так и придётся Но если (вдруг) нужно будет нормальный delete для этого класса? В принципе, ничего сложного нет вызвать деструктор ручками, наподобие: Код (Text): __asm mov ecx, global_bar __asm call что-то_вроде~foo_class Однако, заботливый компилятор не позволяет исправлять его же косяки: говорит
Сорри, про аргумент для delete - мой косяк. При включенной оптимизации и использовании заглушки его нет. Но линкер всё равно ищет delete.
Под "статический" я имел в виду "не динамический" т.е. в т.ч. и auto. Вызов operator delete присутствует в коде `scalar deleting destructor', но вызывается он только при вызове `scalar deleting destructor' с параметром 1, т.е. при уничтожении динамического объекта (delete pObj). В нашем случае в коде будут присутствовать только вызовы `scalar deleting destructor'(0) т.е. operator delete не будет вызываться (в run time). Т.е. получается "ложный" unresolved. Тогда может быть так Код (Text): class foo_class { ~foo_class() { some_cleanup(); } }; long __stdcall filter(EXCEPTION_POINTERS * p) { class foo_class0 : public foo_class { public: void operator delete(void*){} }; static_cast<foo_class0*>(global_bar)->~foo_class0(); return EXCEPTION_EXECUTE_HANDLER; }
green > Так вот в том-то и дело, что при нормальном вызове деструктора (т.е. неявный в конце main() ) delete не используется. За код спасибо! Это похоже и есть самое красивое решение для данного случая.
А по-моему, как раз верно. `scalar deleting destructor' возникает при вызове деструктора через указатель, а при неявном вызове деструктора видимо используется какой-то другой `scalar destructor', поэтому и ошибки с delete не возникает.
S_T_A_S_ Я ошибался, слишком обобщив назначение `scalar deleting destructor'. По последним наблюдениям, он вызывается при явном вызове деструктора объекта (pObj->~ObjClass, obj.~ObjClass), а также при уничтожении динамического объекта (delete pObj). При удалении статических и автоматических объектов просто вызывается деструктор класса, хотя это эквивалентно вызову `scalar deleting destructor'(0).
Кто ошибался, так это я Оказывается, правильно нужно делать так (!) Код (Text): global_bar->[b]foo_class::[/b]~foo_class(); В этом случае delete вызываться не будет. ЗЫ Читаю ISO/IEC14882, ничего понять не могу =) Вот что пишут: и То есть ясно, что вызов delete ведёт к вызову деструктора. Нигде не сказано, что деструктор должен вызывать delete. Возможно, microsoft для подстраховки его вызывают В какой-то степени это логично, обычно если есть поинтер, то соответствующий ему объект создан оператором new.
гм... странно по-моему. Это по стандарту так должно? деструктор в самом деле не вызывает delete. delete вызывается в `scalar deleting destructor', там же вызывается и собственно деструктор. `scalar deleting destructor' - это спец. ф-ция, генерируемая компилятором VC++ (насчет других компилеров не знаю). Т.е. это IMHO чисто деталь реализации компилятора, не регламентируемая стандартом.
green > Как должно быть по стандарту я сам не понял В примерах встречаются оба варианта записи. Я просто попробовал 2-й, и msvc "исправился". > Ага, это я уже понял. Но почему тогда со 2-м вариантом вызова деструктора вызывается не `scalar deleting destructor', а обычный, без delete - ?
судя по примеру из стандарта (ISO/IEC ISO/IEC 14882:2003(E) п. 12.4) Код (C): struct B { virtual ˜B() { } }; struct D : B { ˜D() { } }; D D_object; typedef B B_alias; B* B_ptr = &D_object; void f() { D_object.B::˜B(); // calls B’s destructor B_ptr->˜B(); //calls D’s destructor B_ptr->˜B_alias(); // calls D’s destructor B_ptr->B_alias::˜B(); // calls B’s destructor B_ptr->B_alias::˜B_alias(); // error, no B_alias in class B явное указание scope при вызове деструктора имеет то же назначение что и для обычных ф-ций-членов класса, т.е. в данном случае оба варианта вызова должны быть скомпилированы одинаково. Похоже на баг в компиляторе VC++. Ты VC++ 8.0 Beta2 не пробовал на это счет? кстати сл. пример из стандарта проясняет назначение ф-ции `scalar deleting destructor': Код (C): struct B { virtual ˜B(); void operator delete(void*, size_t); }; struct D : B { void operator delete(void*); }; void f() { B* bp = new D; delete bp; //1: uses D::operator delete(void*) }
VC++ 8.0 Beta2 у меня нет. Пробовал как-то VC++ 8.0 Beta1, но это дрянь без CRT ничего собирать не хочет, ругается на отсутствие каких-то __хз_что_за_cookie_ Да уж лучше подожду релиза, эта бета на других нормальных сорцах говорят спотыкается...