Алекс Jenter - Программирование на Visual C++. Архив рассылки
- Название:Программирование на Visual C++. Архив рассылки
- Автор:
- Жанр:
- Издательство:неизвестно
- Год:неизвестен
- ISBN:нет данных
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Алекс Jenter - Программирование на Visual C++. Архив рассылки краткое содержание
РАССЫЛКА ЯВЛЯЕТСЯ ЧАСТЬЮ ПРОЕКТА RSDN, НА САЙТЕ КОТОРОГО ВСЕГДА МОЖНО НАЙТИ ВСЮ НЕОБХОДИМУЮ РАЗРАБОТЧИКУ ИНФОРМАЦИЮ, СТАТЬИ, ФОРУМЫ, РЕСУРСЫ, ПОЛНЫЙ АРХИВ ПРЕДЫДУЩИХ ВЫПУСКОВ РАССЫЛКИ И МНОГОЕ ДРУГОЕ.
Программирование на Visual C++. Архив рассылки - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
Среди начинающих программистов можно услышать такое мнение: для консольной программы используется только функция main, а для оконной – WinMain. Это мнение, хотя и подтвержденное умолчаниями компилятора и линкера, в общем случае, является ошибочным.
Чтобы немного развлечься, проведем эксперимент. Создадим файл test.cpp:
#include
int main() {
MessageBox(0, "Hello from main()", "A test program", MB_OK);
return 0;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) {
MessageBox(0, "Hello from WinMain()", "A test program", MB_OK);
return 0;
}
Внимание, вопрос: что появится на экране после запуска такой программы? Постарайтесь ответить на этот вопрос, не заглядывая в дальнейшее описание.
ПРИМЕЧАНИЕ
Я не стал рассматривать еще два возможных варианта стартовой функции: wmainили wWinMain, предназначенных для проектов, компилируемых в Unicode. Кроме того, при создании DLL имеется еще один вариант стартовой функции – DllMain.
Функция [w] mainили [w] WinMain, с которой начинается выполнение программы, вовсе не является точкой входа исполняемого модуля! На самом деле, программа на C++ начинает работу с выполнения специальной процедуры инициализации. Что касается Win32, то адрес этой процедуры и содержится в поле AddressOfEntryPointзаголовка portable executable (pe) выполняемого файла. Она представляет собой обычную функцию C, описанную с соглашением о вызовах __stdcall. В зависимости от настроек проекта, в Visual C++ эта функция может называться [w]mainCRTStartup, [w]WinMainCRTStartup или _DllMainCRTStartup (символ 'w' добавляется к имени для Unicode-проектов). Конкретно же для сборки приложения имя функции-точки входа можно задать опцией линкера /entry. Умолчанием для visual c++ является "maincrtstartup". Все сказанное справедливо и для некоторых других компиляторов C++ для Win32.
Что же происходит во время ее выполнения? Вот типичный сценарий работы такой функции (случай DLL здесь не рассматривается).
• Инициализируются переменные CRT (такие, как errnoи osver). Многопоточная библиотека требует особой инициализации.
• Происходит инициализация динамической памяти (кучи).
• Инициализируется среда обработки ошибок в вычислениях с плавающей точкой. Это необходимо не только для библиотечных функций (таких, как sqrt), но и для преобразований между целочисленными и плавающими типами данных.
• Получаются значения аргументов командной строки программы и переменных среды.
• В случае необходимости, происходит инициализация консоли и привязка стандартного вывода к файловым дескрипторам C. При старте исполняемого файла, у которого в уже упомянутом заголовке PE значение поля Subsystemравно 3 (Windows character-mode executable), создается консоль. Это значение можно задать опцией линкера /subsystem. Выбор подсистемы выполнения также влияет на выбор стартовой функции (если ее имя не задано явно). Умолчанием является "console".
• Происходит вызов цепочки функций инициализации CRT и конструкторов глобальных переменных (подробнее об этом – в следующем разделе).
• И лишь после этого вызывается функция [w]main или [w]WinMain. Коротко можно сказать, что функция xxx CRTStartup вызывает соответствующую функцию xxx .
• Программа работает.
• Выполняется последовательность действий по очистке, к которой мы еще вернемся.
• И, наконец, происходит завершение процесса.
Теперь, наконец, можно ответить на мой вопрос: он был задан некорректно :). В самом деле, результат сборки будет зависеть от набора опций компоновщика, установленных в проекте или по умолчанию.
Так, например, при вызове компилятора в командной строке таким образом:
cl test.cpp user32.lib
мы получим консольную программу и сообщение "Hello from main()" (вспомните, что говорилось об умолчаниях).
А вызвав компилятор вот так:
cl test.cpp user32.lib /link /entry:WinMainCRTStartup /subsystem:console
мы получим "чудо чудное": программу, у которой выполняется функция WinMain, но создается окно консоли.
Как в VC++ реализован вызов цепочки функций инициализации/завершения?
Наличие в программе хотя бы одной глобальной переменной – экземпляра класса – заставляет компилятор сделать следующее. Во-первых, он генерирует невидимую за пределами модуля функцию, в которой и выполняются необходимые действия – вычисляется значение инициализатора или вызывается конструктор. Далее создается специальная запись с указателем на эту функцию в сегменте с именем вида ".CRT$ xxx ". Детально разбирать формат именования сегмента мы не будем, сейчас важно только то, что все сегменты такого типа будут при сборке объединены в алфавитном порядке в один сегмент. Таким образом, в момент старта программы в памяти будет находиться массив указателей на функции, при вызове которых и произойдут необходимые действия. В стартовом коде CRT VC этим занимается функция _initterm.
А почему здесь используется термин "функции инициализации/завершения " вместо терминов "конструкторы/деструкторы"?
Напомню, что стандарт языка C++ разрешает инициализацию переменных с помощью неконстантных выражений. Если переменная (даже простого типа) описана в глобальной области, то ее инициализатор должен быть выполнен до вызова функции main/WinMain:
int len = strlen("Hello, world!");
Обработка в этом случае ничем не отличается от инициализации экземпляра класса имеющего конструктор.
Упомянув инициализацию CRT, нельзя умолчать о коде очистки, или завершения. В нем выполняются действия обратного характера (и, в том числе, деструкторы глобальных переменных). Что действительно заслуживает описания, так это то, что код очистки можно вызвать собственноручно. Да-да, он содержится в функции exit. Если же не вызвать ее явно, то она вызовется после возврата из main/WinMain. Наиболее выразительную реализацию вышесказанного я встретил однажды в исходных файлах CRT компилятора WATCOM C++:
exit(main(__argv, __argc, __envp));
То есть, можно сказать, что все выполнение программы имеет целью получение параметра для функции exit. :)
ПРИМЕЧАНИЕ
Вообще-то, exit (вернее, возможность ее прямого вызова) является, скорее, "пережитком" со времен программирования на C. При вызове этой функции из программы на C++ не выполнятся деструкторы для локальных переменных (что естественно, поскольку, в отличие от глобальных объектов, их деструкторы нигде не зарегистрированы). Кроме того, вызов exit из деструктора может привести к входу программы в бесконечный цикл, так что не злоупотребляйте этой функцией.
Читать дальшеИнтервал:
Закладка: