Проблемы MSVC без CRT.

Тема в разделе "WASM.ZEN", создана пользователем S_T_A_S_, 14 май 2005.

  1. S_T_A_S_

    S_T_A_S_ New Member

    Публикаций:
    0
    Регистрация:
    27 окт 2003
    Сообщения:
    1.754
    В сети есть статьи об использовании MSVC без CRT. Например, by Matt Pietrek, на RSDN и UINC. Как правило пишут про 6ю (12ю) версию компилятора и Си.
    Провожу некоторые опыты с .NET 7.1 (13.1) и C++
    и наблюдаю странные явления :)
    Следующий код без проблем компилируется intel'овским компилером (8.1)
    Код (ASM):
    1.  
    2. #pragma comment(linker, "/nodefaultlib:libc.lib")
    3.  
    4. class foo_class
    5. {
    6.     ~foo_class() { some_cleanup(); }
    7. };
    8.  
    9. static foo_class * global_bar;
    10.  
    11. long __stdcall filter(EXCEPTION_POINTERS * p)
    12. {
    13.     global_bar->~foo_class();
    14.  
    15.     return EXCEPTION_EXECUTE_HANDLER;
    16. }
    17.  
    18. int main()
    19. {
    20.     SetUnhandledExceptionFilter(filter);
    21.  
    22.     foo_class bar;
    23.     global_bar = &bar;
    24.  
    25.     //
    26.  
    27.     return 0;
    28. }
    MSVC начинает плакать
    Код (Text):
    1. 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):
    1. global_bar->~foo_class();
    строка
    Код (Text):
    1. void __cdecl operator delete(void *){};
    проблему "решает".
    Однако, это создаст кучу новых проблем, когда delete действительно потребуется :dntknw:
    Другой вариант - использовать вместо деструктора отдельный метод с такой же функциональностью и вызывать его явно. То есть, такой же кривой, как и предыдущий :)
    Собственно, вопросы:
    - использование delete - баг компилятора или я что-то (как всегда) не понял?
    - кто-нибудь таким занимался? с какими ещё проблемами можно\придётся столкнуться?
     
  2. BuLdOzEr

    BuLdOzEr New Member

    Публикаций:
    0
    Регистрация:
    5 апр 2005
    Сообщения:
    5
    В случае MSVC по-моему если реализовать new и delete как в той статье на RSDN (реализация там взята из atlinit.cpp) придется делать еще следуещее (добавить в код):
    Код (C):
    1. extern "C" HANDLE _crtheap;
    2. ...
    3. _crtheap = HeapCreate(...) // с нужными параметрами
    4. // В случае наличия чисто виртуальных (и наверное просто виртуальных) классов функций и т.п.
    5. int __cdecl _purecall()
    6. {
    7.   return 0;
    8. };
    Вполне возможно что я где то намудрил...
    В общем посмотрите файлы purevirt.c atlinit.cpp heapinit.c (они где то в папке Vc7 в Visual Studio)
     
  3. rst

    rst New Member

    Публикаций:
    0
    Регистрация:
    5 май 2003
    Сообщения:
    165
    Используй автоматические переменные а не указатели. И не понадобится Delete.

    НО автоматические переменные должны быть объявлены не в global scope. Т.е. они должны быть объявлены внутри функции. В противном случае они не будут работать. по той причине, что инициализацией глобальных переменных занимается опять же CRT.
     
  4. S_T_A_S_

    S_T_A_S_ New Member

    Публикаций:
    0
    Регистрация:
    27 окт 2003
    Сообщения:
    1.754
    В том-то и дело, что у меня автоматическая (локальная для main() ) переменная, и new\delete для неё совершенно не требуется (свою реализацию написать можно, но они не нужны в этом контексте). Если убрать фильтр исключений - всё работает на ура. Какого икса MSVC пытается влепить delete при обращении через глобальный указатель - непонятно :-( А без него тоже не понятно как обойтись - требуется в фильтре вызывать деструктор.
    ЗЫ: где-то в MSDN видел про инициализацию глобальный переманных в CRT, а сейчас найти не могу :dntknw:
     
  5. rst

    rst New Member

    Публикаций:
    0
    Регистрация:
    5 май 2003
    Сообщения:
    165
    S_T_A_S_, больше похоже на бред. Я совсем недавно с эксепшенами игрался - не требовалось там delete. У меня все компилится следующим образом:
    Код (C):
    1. #pragma comment(linker, "/ENTRY:WinMain")
    2. #pragma comment(linker, "/SUBSYSTEM:WINDOWS")
    3. #pragma comment(linker, "/NODEFAULTLIB:LIBC")
    4. #pragma comment(linker, "/NODEFAULTLIB:LIBCP")
    Хотя.. Классов у меня там нет.
    Но ты можешь поступить следующим образом:
    Код (C):
    1. void operator delete( void * p )
    2. {
    3.     free( p );
    4. }
    Это код оператора delete из CRT. Кто тебе мешает просто такую заглушку вставить?
    У меня возникает подозрение, что этот при использовании эксепшена происходит раскрутка стека, и соответсвенно вызов всех дестркуторов. НО если я не ошибаюсь - это фича С++, а не WinAPI. Соответственно не могу понять - причем тут дестркуторы. Если бы ты try/catch использовал - там вполне понятно. Но в этом случае - не понятно. В общем сделай реализацию delete, как я выше написал и не парься :)
     
  6. green

    green New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2003
    Сообщения:
    1.217
    Адрес:
    Ukraine
    S_T_A_S_
    Дело в том, что foo_class::`scalar deleting destructor' используется компилятором для уничтожения как статических так и динамических экземпляров foo_class (тип уничтожения определяется параметром `scalar deleting destructor').
    Т.е. в данном случае operator delete в `scalar deleting destructor' никогда не будет вызван, но из-за того что компилятор не инлайнит эту ф-цию, возникает этот unresolved.
    Можно так:
    Код (C):
    1. class foo_class
    2. {
    3.     ~foo_class() { some_cleanup(); }
    4.     void operator delete(void*){}
    5. };
     
  7. S_T_A_S_

    S_T_A_S_ New Member

    Публикаций:
    0
    Регистрация:
    27 окт 2003
    Сообщения:
    1.754
    rst >
    Дык и вставляю :) Не пойму, почему delete вообще там нужно :dntknw:
    >
    Полностью согласен, что раскрутки стэка не должно быть в моём случае. Я как раз и пытаюсь "сэмулировать" некоторые вещи CRT и вызываю деструктор вручную (для этого и замуты с глобальным поинтером). Вот почему MSVC в деструкторе вызывает delete, а Intel нет - ?
    green >
    В моём случае экземпляр auto. Как я понимаю, для него delete вызываться не должен, он же в стэке.
    >
    Вот вызов global_bar->~foo_class(); после Intel C++ 8.1
    Код (Text):
    1. mov     ecx, [406368]  ; __thiscall
    2. call    004021B4
    А это MSVC 7.1 (с заглушкой void __cdecl operator delete(void *){};)
    Код (Text):
    1. push    0  ; <- ??? этот аргумент для delete
    2. mov     ecx, [405B98]
    3. call    00401A4F
    Видно, что MSVC его всё-таки вызывает. Более того, если линкую с crt, то начинают зачем-то импортироваться HeapXXX и т.п. функции + добавляет кучу кода к exe. Intel этого не делает.
    >
    Так и придётся :dntknw: Но если (вдруг) нужно будет нормальный delete для этого класса?
    В принципе, ничего сложного нет вызвать деструктор ручками, наподобие:
    Код (Text):
    1.     __asm  mov  ecx, global_bar
    2.     __asm  call  что-то_вроде~foo_class
    Однако, заботливый компилятор не позволяет исправлять его же косяки: говорит
     
  8. S_T_A_S_

    S_T_A_S_ New Member

    Публикаций:
    0
    Регистрация:
    27 окт 2003
    Сообщения:
    1.754
    Сорри, про аргумент для delete - мой косяк. При включенной оптимизации и использовании заглушки его нет. Но линкер всё равно ищет delete.
     
  9. green

    green New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2003
    Сообщения:
    1.217
    Адрес:
    Ukraine
    Под "статический" я имел в виду "не динамический" т.е. в т.ч. и auto.
    Вызов operator delete присутствует в коде `scalar deleting destructor', но вызывается он только при вызове `scalar deleting destructor' с параметром 1, т.е. при уничтожении динамического объекта (delete pObj).
    В нашем случае в коде будут присутствовать только вызовы
    `scalar deleting destructor'(0) т.е. operator delete не будет вызываться (в run time).
    Т.е. получается "ложный" unresolved.
    Тогда может быть так
    Код (Text):
    1. class foo_class
    2. {
    3.     ~foo_class() { some_cleanup(); }
    4. };
    5.  
    6. long __stdcall filter(EXCEPTION_POINTERS * p)
    7. {
    8.     class foo_class0 : public foo_class
    9.     {
    10.     public:
    11.         void operator delete(void*){}
    12.     };
    13.     static_cast<foo_class0*>(global_bar)->~foo_class0();
    14.  
    15.     return EXCEPTION_EXECUTE_HANDLER;
    16. }
     
  10. S_T_A_S_

    S_T_A_S_ New Member

    Публикаций:
    0
    Регистрация:
    27 окт 2003
    Сообщения:
    1.754
    green >
    Так вот в том-то и дело, что при нормальном вызове деструктора (т.е. неявный в конце main() ) delete не используется.
    За код спасибо! Это похоже и есть самое красивое решение для данного случая.
     
  11. green

    green New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2003
    Сообщения:
    1.217
    Адрес:
    Ukraine


    да, сорри, выходит я неверно представлял себе назначение

    `scalar deleting destructor'.
     
  12. S_T_A_S_

    S_T_A_S_ New Member

    Публикаций:
    0
    Регистрация:
    27 окт 2003
    Сообщения:
    1.754
    А по-моему, как раз верно. `scalar deleting destructor' возникает при вызове деструктора через указатель, а при неявном вызове деструктора видимо используется какой-то другой `scalar destructor', поэтому и ошибки с delete не возникает.
     
  13. green

    green New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2003
    Сообщения:
    1.217
    Адрес:
    Ukraine
    S_T_A_S_

    Я ошибался, слишком обобщив назначение `scalar deleting destructor'. По последним наблюдениям, он вызывается при явном вызове деструктора объекта (pObj->~ObjClass, obj.~ObjClass), а также при уничтожении динамического объекта (delete pObj). При удалении статических и автоматических объектов просто вызывается деструктор класса, хотя это эквивалентно вызову `scalar deleting destructor'(0).
     
  14. S_T_A_S_

    S_T_A_S_ New Member

    Публикаций:
    0
    Регистрация:
    27 окт 2003
    Сообщения:
    1.754
    Кто ошибался, так это я :)
    Оказывается, правильно нужно делать так (!)
    Код (Text):
    1. global_bar->[b]foo_class::[/b]~foo_class();
    В этом случае delete вызываться не будет.
    ЗЫ Читаю ISO/IEC14882, ничего понять не могу =) Вот что пишут:
    и
    То есть ясно, что вызов delete ведёт к вызову деструктора. Нигде не сказано, что деструктор должен вызывать delete. Возможно, microsoft для подстраховки его вызывают :) В какой-то степени это логично, обычно если есть поинтер, то соответствующий ему объект создан оператором new.
     
  15. green

    green New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2003
    Сообщения:
    1.217
    Адрес:
    Ukraine
    гм... странно по-моему. Это по стандарту так должно?
    деструктор в самом деле не вызывает delete.
    delete вызывается в `scalar deleting destructor', там же вызывается и собственно деструктор.
    `scalar deleting destructor' - это спец. ф-ция,
    генерируемая компилятором VC++ (насчет других компилеров не знаю). Т.е. это IMHO чисто деталь реализации компилятора, не регламентируемая стандартом.
     
  16. S_T_A_S_

    S_T_A_S_ New Member

    Публикаций:
    0
    Регистрация:
    27 окт 2003
    Сообщения:
    1.754
    green >
    Как должно быть по стандарту я сам не понял :dntknw: В примерах встречаются оба варианта записи. Я просто попробовал 2-й, и msvc "исправился".
    >
    Ага, это я уже понял. Но почему тогда со 2-м вариантом вызова деструктора вызывается не `scalar deleting destructor', а обычный, без delete - ?
     
  17. green

    green New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2003
    Сообщения:
    1.217
    Адрес:
    Ukraine
    не знаю...
    Придется наконец почитать стандарт. :)
    хотя мне кажется это фича VC++.
     
  18. S_T_A_S_

    S_T_A_S_ New Member

    Публикаций:
    0
    Регистрация:
    27 окт 2003
    Сообщения:
    1.754
    мне тоже, кажется, что это "особенность" msvc, но воот фича ли... :)
     
  19. green

    green New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2003
    Сообщения:
    1.217
    Адрес:
    Ukraine
    судя по примеру из стандарта (ISO/IEC ISO/IEC 14882:2003(E) п. 12.4)
    Код (C):
    1. struct B {
    2. virtual ˜B() { }
    3. };
    4. struct D : B {
    5. ˜D() { }
    6. };
    7.  
    8. D D_object;
    9. typedef B B_alias;
    10. B* B_ptr = &D_object;
    11. void f() {
    12. D_object.B::˜B(); // calls B’s destructor
    13. B_ptr->˜B(); //calls D’s destructor
    14. B_ptr->˜B_alias(); // calls D’s destructor
    15. B_ptr->B_alias::˜B(); // calls B’s destructor
    16. B_ptr->B_alias::˜B_alias(); // error, no B_alias in class B
    явное указание scope при вызове деструктора имеет то же назначение что и для обычных ф-ций-членов класса, т.е. в данном случае оба варианта вызова должны быть скомпилированы одинаково.
    Похоже на баг в компиляторе VC++.
    Ты VC++ 8.0 Beta2 не пробовал на это счет?
    кстати сл. пример из стандарта проясняет назначение ф-ции `scalar deleting destructor':
    Код (C):
    1. struct B {
    2. virtual ˜B();
    3. void operator delete(void*, size_t);
    4. };
    5. struct D : B {
    6. void operator delete(void*);
    7. };
    8. void f()
    9. {
    10. B* bp = new D;
    11. delete bp; //1: uses D::operator delete(void*)
    12. }
     
  20. S_T_A_S_

    S_T_A_S_ New Member

    Публикаций:
    0
    Регистрация:
    27 окт 2003
    Сообщения:
    1.754
    VC++ 8.0 Beta2 у меня нет. Пробовал как-то VC++ 8.0 Beta1, но это дрянь без CRT ничего собирать не хочет, ругается на отсутствие каких-то __хз_что_за_cookie_ Да уж лучше подожду релиза, эта бета на других нормальных сорцах говорят спотыкается...