Чтение онлайн

на главную - закладки

Жанры

Программирование на Visual C++. Архив рассылки

Jenter Алекс

Шрифт:

"Да, у меня тоже была такая ошибка. Вылечилось добавлением в исходники пустой функции main{}.Это какой-то глюк у Microsoft. :( "

Что же на самом деле стоит за этой проблемой и как ее решить? Давайте разберемся.

Многое в этой статье справедливо для любой среды программирования на C/C++, но детали реализации будут приводиться для Microsoft Visual C++ версий 5.0 и 6.0.

Большое спасибо Павлу Блудову за ценные замечания в ходе обсуждения статьи.

Библиотека C Run-Time

Обычно C/C++-программа опирается на мощную поддержку С Run-Time Library – библиотека времени

исполнения языка C, далее – CRT; более редкое название – RTL (run-time library). Многим функциям этой библиотеки для правильной работы требуется дополнительная инициализация (CRT startup code). В частности, для вывода текста на консоль с помощью функции printf необходимо, чтобы дескриптор стандартного вывода stdout был предварительно связан с устройством вывода операционной системы (например, стандартным выводом и консолью Win32). То же самое справедливо и для функций работы с кучей – таких, как malloc для c и оператора new для C++.

Таким образом, даже в минимальной программе, содержащей вызов printf или попытку выделения динамической памяти, будет содержаться внушительный (для такой программы) код инициализации CRT – свыше 30 килобайт.

ПРИМЕЧАНИЕ

При использовании CRT в виде дополнительной динамической библиотеки (DLL) размер исполняемого модуля может быть меньше 30 Кб – об этом речь пойдет чуть позже.

При дальнейшем рассмотрении дело оказывается еще хуже. Выясняется, что инициализация CRT нужна, даже если ни одна из входящих в нее функций явно в программе не используется.

Так, некоторые операции с плавающей точкой требуют наличия кода инициализации: например, на случай, если будет выполняться обработчик исключительных ситуаций (floating point handler). Объявление глобальной переменной, являющейся экземпляром класса, имеющего конструктор или деструктор, тоже требует наличия стартового кода CRT. Это происходит из-за того, что вызовы конструкторов и деструкторов в VC реализованы как часть стартового кода CRT. Использование механизмов обработки исключений C++ и Run-Time Type Information (RTTI) также влечет за собой необходимость инициализации.

Исходя из этого, разработчики современных компиляторов C++ строят CRT таким образом, чтобы её стартовый код включался в программу по умолчанию. В большинстве случаев это – именно то поведение, которое требуется. В самом деле, большой проект на C++ редко обходится без использования CRT-функций или вычислений c плавающей точкой. Да и "довесок" в 30 Кб в таком случае невелик.

Если это вас устраивает, проблема с упомянутым ATL-проектом решается достаточно просто. Необходимо зайти в настройки проекта ("Project" – "Settings"), выбрать нужную Release-конфигурацию и на закладке "C++" удалить опцию препроцессора _ATL_MIN_CRT. Вопрос будет снят. Дальше можно не читать.

Но встречаются случаи, когда считаешь буквально каждый байт исполняемого модуля. Это может быть ядро инсталлятора или самораспаковывающегося архива, элемент управления ActiveX, который скачивается через Интернет, или приложение для встраиваемой системы. Компиляторы C++ (и Visual C++, в том числе), на мой взгляд, наиболее подходят для такого рода разработок. Приложение может, в конце концов, состоять из большого количества модулей, и мало что значащие 30 Кб могут превратиться

в несколько сотен килобайт, а то и мегабайт. Но для контроля над процессом сборки придется погрузиться в некоторые детали реализации поддержки CRT.

main или WinMain?

Среди начинающих программистов можно услышать такое мнение: для консольной программы используется только функция main, а для оконной – WinMain. Это мнение, хотя и подтвержденное умолчаниями компилятора и линкера, в общем случае, является ошибочным.

Чтобы немного развлечься, проведем эксперимент. Создадим файл test.cpp:

#include <windows.h>

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

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

Измена. Тайный наследник

Лаврова Алиса
1. Тайный наследник
Фантастика:
фэнтези
5.00
рейтинг книги
Измена. Тайный наследник

Инквизитор Тьмы

Шмаков Алексей Семенович
1. Инквизитор Тьмы
Фантастика:
попаданцы
альтернативная история
аниме
5.00
рейтинг книги
Инквизитор Тьмы

Наследник

Майерс Александр
3. Династия
Фантастика:
попаданцы
аниме
фэнтези
5.00
рейтинг книги
Наследник

Ротмистр Гордеев 3

Дашко Дмитрий
3. Ротмистр Гордеев
Фантастика:
попаданцы
альтернативная история
5.00
рейтинг книги
Ротмистр Гордеев 3

(Не)нужная жена дракона

Углицкая Алина
5. Хроники Драконьей империи
Любовные романы:
любовно-фантастические романы
6.89
рейтинг книги
(Не)нужная жена дракона

Идеальный мир для Лекаря 28

Сапфир Олег
28. Лекарь
Фантастика:
юмористическое фэнтези
аниме
фэнтези
5.00
рейтинг книги
Идеальный мир для Лекаря 28

Сыночек в награду. Подари мне любовь

Лесневская Вероника
1. Суровые отцы
Любовные романы:
современные любовные романы
5.00
рейтинг книги
Сыночек в награду. Подари мне любовь

Инквизитор Тьмы 2

Шмаков Алексей Семенович
2. Инквизитор Тьмы
Фантастика:
попаданцы
альтернативная история
аниме
5.00
рейтинг книги
Инквизитор Тьмы 2

Генерал Скала и ученица

Суббота Светлана
2. Генерал Скала и Лидия
Любовные романы:
любовно-фантастические романы
6.30
рейтинг книги
Генерал Скала и ученица

Искатель 1

Шиленко Сергей
1. Валинор
Фантастика:
фэнтези
попаданцы
рпг
5.00
рейтинг книги
Искатель 1

Сердце Дракона. Том 10

Клеванский Кирилл Сергеевич
10. Сердце дракона
Фантастика:
фэнтези
героическая фантастика
боевая фантастика
7.14
рейтинг книги
Сердце Дракона. Том 10

Печать мастера

Лисина Александра
6. Гибрид
Фантастика:
попаданцы
технофэнтези
аниме
фэнтези
6.00
рейтинг книги
Печать мастера

Выстрел на Большой Морской

Свечин Николай
4. Сыщик Его Величества
Детективы:
исторические детективы
полицейские детективы
8.64
рейтинг книги
Выстрел на Большой Морской

Кодекс Крови. Книга VII

Борзых М.
7. РОС: Кодекс Крови
Фантастика:
боевая фантастика
попаданцы
аниме
5.00
рейтинг книги
Кодекс Крови. Книга VII