Вход/Регистрация
Программирование на Visual C++. Архив рассылки
вернуться

Jenter Алекс

Шрифт:

Теперь, наконец, можно ответить на мой вопрос: он был задан некорректно :). В самом деле, результат сборки будет зависеть от набора опций компоновщика, установленных в проекте или по умолчанию.

Так, например, при вызове компилятора в командной строке таким образом:

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 из деструктора может привести к входу программы в бесконечный цикл, так что не злоупотребляйте этой функцией.

Со времен создания библиотеки языка C осталась и такая возможность, как регистрация цепочки обработчиков завершения с помощью функций atexit/_onexit. Функции, зарегистрированные вызовом atexit/_onexit, будут вызваны в ходе завершения программы в порядке, обратном порядку их регистрации. Для программы на C++ с этой целью лучше воспользоваться глобальными деструкторами.

На самом деле, в программе на VC регистрация деструкторов глобальных объектов также выполняется с помощью внутреннего вызова atexit после вызова конструктора. Это имеет довольно веские основания: если конструктор объекта вызван не был, то не будет вызван и его деструктор. Но, в любом случае, это – деталь реализации, на которую полагаться не стоит.

Внутри exit содержится вызов функции более низкого уровня – _exit. Ее вызов не приведет к вызову деструкторов и exit-обработчиков, а только выполнит самую необходимую очистку (не буду вдаваться в подробности, замечу только, что при этом вызываются C-терминаторы (функции из таблицы в сегментах "CRT$XT[A-Z]"), в частности, подчищается low-level i/o) и завершит программу вызовом функции Windows API ExitProcess.

И, наконец, функция abort является способом "пожарного" завершения программы. Она выводит диагностическое сообщение и также вызывает _exit для завершения процесса.

Вызов любой из этих функций приведет к необходимости включения стартового кода CRT.

Уменьшаем размер выполняемого модуля

Но в нашем примере нет ничего, что потребовало бы использовать CRT. Более того, включив оптимизацию по размеру (/O1) и генерацию карты исполняемого файла (Generate Link Map, /Fm), можно заметить, что размер функции main – всего 23 байта. А размер выполняемого модуля составляет около 36 килобайт. Неужели нельзя его немного уменьшить?

Конечно, такие способы существуют, и некоторые из них я опишу ниже. Но важно понимать, что каждый из них не является общим решением (иначе именно он использовался бы по умолчанию), и имеет свои недостатки.

Использование внешней библиотеки CRT

Откомпилируем нашу программу следующей командой:

cl /MD test.cpp user32.lib

Размер полученного в результате EXE-файла составляет около 16 килобайт. Что за чудеса? Куда делась половина исполняемого модуля? Неужели он "похудел" за счет исключения CRT?

  • Читать дальше
  • 1
  • ...
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • ...

Ебукер (ebooker) – онлайн-библиотека на русском языке. Книги доступны онлайн, без утомительной регистрации. Огромный выбор и удобный дизайн, позволяющий читать без проблем. Добавляйте сайт в закладки! Все произведения загружаются пользователями: если считаете, что ваши авторские права нарушены – используйте форму обратной связи.

Полезные ссылки

  • Моя полка

Контакты

  • chitat.ebooker@gmail.com

Подпишитесь на рассылку: