Когда Вы создаете новый проект в Visual Studio, в него автоматически добавляется заголовочный файл с именем "pch.h", это так называемый файл предварительно скомпилированных заголовков, или файл precompiled headers (в ранних версиях Visual Studio, этот файл назывался "stdafx.h"). Назначение этого файла - ускорить процесс компиляции, что имеет значение для средних и больших проектов. В файл precompiled headers должно быть добавлено подключение (директивой #include) любых "стабильных" заголовочных файлов (которые никогда не изменяются, или изменяются редко), например заголовки Standard Library, такие как < vector>. Файл precompiled header компилируется только когда был изменен он сам, или был изменен какой-либо из подключаемых в нем файлов. Если Вы вносите изменения только в модули исходного кода проекта, то при сборке пропускается компиляция всех файлов, которые содержаться в precompiled header.
Опции компилятора, относящиеся к precompiled headers, начинаются с /Y. На страницах свойств проекта соответствующие опции находятся в разделе Configuration Properties -> C/C++ -> Precompiled Headers. Вы можете здесь выбрать не использовать precompiled headers, и также можете указать специальное имя заголовка и путь имя и путь результата компиляции precompiled headers.
Пользовательский предварительно скомпилированный код. Для больших проектов, для которых важно экономить время на сборку, Вы можете решить создать свои собственные предварительно скомпилированные файлы (Precompile Source Code). Компиляторы Microsoft C и C++ предоставляют опции для предварительной компиляции любого кода C или C++, включая inline-код. Используя эту функцию повышения производительности, Вы можете скомпилировать стабильное тело кода, сохранить скомпилированное состояние в файл, и при последующих компиляциях комбинировать предварительно скомпилированный код с кодом, находящимся в текущей разработке. Каждая последующая компиляция будет происходить быстрее, потому что не нужно заново компилировать предварительно скомпилированный код.
[Когда следует использовать Precompile Source Code]
Precompiled-код полезен во время разработки для снижения времени компиляции, особенно в следующих ситуациях:
• Всегда есть большой объем кода, который редко изменяется.
• Ваша программа состоит из нескольких модулей, и все они используют стандартный набор подключаемых файлов и одинаковые опции компиляции. В этом случае все подключаемые файлы проекта можно предварительно скомпилировать в один precompiled header.
Первая компиляция - в которой будет создан файл precompiled header (PCH) - будет происходить дольше, чем последующие компиляции, которые будут подключать precompiled-код.
Вы можете предварительно компилировать как программы на языке C, таки и программы на языке C++. В программировании C++ общей практикой является выделение информации интерфейса класса в заголовочные файлы. Эти заголовочные файлы могут быть позже подключены в программы, которые используют эти классы. Путем предварительной компиляции этих заголовков Вы можете снизить время, которое тратится на компиляцию программы.
Примечание: хотя Вы можете использовать только один файл precompiled header (с расширением *.pch) на исходный файл, в проекте также можно использовать несколько pch-файлов.
[Два выбора для предварительного компилирования кода]
В среде Visual C++ Вы можете предварительно компилировать любой код C или C++; Вы не ограничены только заголовочными файлами.
Прекомпиляция требует предварительного планирования, однако часто эта техника позволяет значительно ускорить сборки, если предварительно компилируется код, отличающийся от простых заголовочных файлов.
Используйте precompiled-код, когда знаете, что Ваши исходные файлы используют общий набор заголовочных файлов, однако не подключают их в одинаковом порядке, или когда Вы хотите добавить исходный код в свою предварительную компиляцию.
Опциями precompiled-header служат /Yc (Create Precompiled Header File) и /Yu (Use Precompiled Header File). Используйте /Yc для создания precompiled header. Когда используется опциональная директива #pragma hdrstop, опция /Yc позволит Вам предварительно компилировать как заголовочные файлы, так и исходный код. Выберите /Yu для использования существующего файла precompiled header в имеющейся компиляции. Вы можете также использовать опцию /Fp с опциями /Yc и /Yu, чтобы предоставить альтернативное имя для precompiled header.
[Правила целостности Precompiled Header]
Поскольку PCH-файлы содержат информацию о рабочем окружении (компьютере разработчика, в том числе информацию адресов памяти программы), Вы должны использовать PCH-файл только на том компьютере, где он было создан.
Правила целостности для каждого файла. Опция /Yu компилятора позволяет Вам указать, какой PCH-файл использовать.
Когда Вы используете PCH-файл, компилятор подразумевает, что используется одно и то же рабочее окружение компиляции - используются целостные опции компилятора, прагмы, версия и т. д. - которые дают тот же эффект компиляции, который был при создании PCH-файла, если Вы не укажете нечто другое. Если компилятор определил какое-либо нарушение целостности, то он выдаст предупреждение и покажет по мере возможности, в чем было это нарушение. Такие предупреждения не обязательно показывают проблему в PCH-файле; они просто сообщают Вам о возможном возникновении конфликтов. Далее будут описаны требования целостности для PCH-файлов.
Целостность опций компилятора. Следующие опции компилятора могут привести к предупреждениям нарушения целостности при использовании PCH-файла:
• Макросы, созданные с помощью опции препроцессора (/D) должны быть одинаковыми, как были в момент компиляции создания PCH-файла и в текущей компиляции. Состояние констант #define не проверяется, однако в случае их изменений возможны непредсказуемые результаты.
• PCH-файлы не работают с опциями /E и /EP.
• PCH-файлы должны быть созданы либо с опцией Generate Browse Info (/FR), либо с опцией Exclude Local Variables (/Fr) перед последующими компиляциями, которые могут использовать PCH-файл и эти опции.
Совместимость "C 7.0" (/Z7). Если действует опция "C 7.0-Compatible" (/Z7) при создании PCH-файла, то последующие компиляции, которые используют этот PCH-файл, могут использовать отладочную информацию.
Если опция "C 7.0-Compatible" (/Z7) не применялась при создании PCH-файла, то последующие компиляции, использующие этот PCH-файл вместе с опцией /Z7, будут приводить к генерации предупреждения. Отладочная информация, помещенная в текущий .obj файл, и локальные символы в файле PCH не будут доступны для отладчика.
Целостность путей подключения. PCH-файл не содержит информацию о путях поиска подключаемых файлов (Include Path), которая действовала в момент создания этого PCH-файла. Когда Вы используете PCH-файл, компилятор всегда использует пути поиска подключаемых файлов, настроенные для текущей компиляции.
Целостность исходных файлов. Кода Вы указываете использовать Precompiled Header File (опцию /Yu), компилятор игнорирует все директивы препроцессора (включая директивы #pragma) которые появляются в исходном коде, подвергшемся предварительной компиляции. Способ компиляции, заданный этими директивами препроцессора, должен быть таким же, как использовался в момент создания Precompiled Header File (с опцией /Yc).
Целостность #pragma. Директивы #pragma, обработанные в момент создания PCH-файла, обычно влияют на файл, который будет использовать этот PCH-файл в последующих компиляциях. Прагмы comment и message не влияют на остальную часть компиляции.
Следующие директивы #pragma влияют только на код в файле PCH; они не влияют на код, который использует этот PCH-файл:
comment
page
subtitle
linesize
pagesize
title
message
skip
Следующие директивы #pragma сохраняются как часть precompiled header, и влияют на компиляцию, которая использует этот precompiled header:
alloc_text
include_alias
pack
auto_inline
init_seg
pointers_to_members
check_stack
inline_depth
setlocale
code_seg
inline_recursion
vtordisp
data_seg
intrinsic
warning
function
optimize
[Правила целостности для /Yc и /Yu]
Когда Вы используете precompiled header с опциями /Yc или /Yu, компилятор сравнивает текущее рабочее окружение компиляции и то окружение, которое существовало в момент создания PCH-файла. Убедитесь, что для текущей компиляции задаете одинаковое, целостное окружение, совпадающее с предыдущим (с использованием одинаковых опций компилятора, прагм и т. д.). Если компилятор обнаружит нарушение целостности, то он выдаст предупреждение, и если есть возможность, то укажет на его причину. Подобные предупреждения необязательно показывают проблему в файле PCH, они просто предупреждают о возможном возникновении конфликтов. В таблице ниже приведены требования целостности к опциям компилятора.
Опция |
Имена |
Правило |
/D |
Определение констант и макросов |
Должно быть одинаковым между компиляцией с созданным precompiled header и текущей компиляцией. Состояние определяемых констант не проверяется, однако могут быть непредсказуемые результаты, если Ваши файлы зависят от значений измененных констант. |
/E или /EP |
Копирование вывода препроцессора в стандартный вывод (stdout) |
Технология precompiled headers не работает с опцией /E или /EP. |
/Fr или /FR |
Генерация информации Microsoft Source Browser |
Чтобы опции /Fr и /FR были допустимы с опцией /Yu, они должны действовать в момент создания precompiled header. Последующие компиляции, которые используют precompiled header, также генерируют информацию Source Browser. Эта информация помещается в один файл *.sbr, и к нему обращаются другие файлы таким же способом, как и к информации CodeView. Вы не можете изменить место размещения информации Source Browser. |
/GA, /GD, /GE, /Gw или /GW |
Опции протокола Windows |
Должны быть одинаковыми между компиляцией, создавшей precompiled header, и текущей компиляцией. Если эти опции различаются, то будет выведено предупреждающее сообщение. |
/Zi |
Генерация полной отладочной информации |
Если эти опции действуют в момент создания precompiled header, то последующие компиляции, которые используют эту предкомпиляцию, могут использовать отладочную информацию. Если /Zi не действовала, когда был создан precompiled header, то последующие компиляции, которые используют опцию /Zi, приведут к выводу предупреждения компилятора. Отладочная информация помещается в текущий объектный файл, и локальные символы, определенные в precompiled header, будут недоступны для отладчика. |
Примечание: технология precompiled предназначена только для использования в исходном коде C и C++.
[Использование Precompiled Headers в проекте]
В предыдущих секциях обзорно рассматривалось управление precompiled headers: опции /Yc, /Yu и /Fp и прагма hdrstop. В этой секции описывается метод создания опций precompiled header для проекта вручную. В конце будет приведет пример makefile и кода, который им обслуживается.
Другой способ использования вручную созданных опций в проекте - изучить один из файлов makefile, находящиеся в директории MFC\SRC, которая создается в процессе установки по умолчанию Visual C++. Эти файлы makefile дают простой способ применения precompiled header, представленный в этой секции, однако он больше использует макросы Microsoft Program Maintenance Utility (NMAKE), и дает больше возможностей по управлению процессом сборки.
PCH-файлы в процессе сборки. Кодовая база программного проекта обычно содержится в нескольких модулях исходного кода на языке C или C++, объектных файлах, библиотеках и заголовочных файлах. Обычно файлы makefile координируют комбинацию этих элементов в исполняемый файл (*.exe). На следующем рисунке показана структура makefile, который использует файл precompiled header. Имена макроса NMAKE и имена файлов в этой диаграмме соответствуют коду примера, который находится в Примере 1 Makefile для PCH и Примере 2 кода для PCH.
Структура Makefile, который использует файл Precompiled Header:
Рисунок использует три диаграммных устройства, чтобы показать поток процесса сборки. Прямоугольники с именами представляют каждый файл или макрос; дерево макросов представляют один или большее количество файлов. Затененные области представляют каждое действие по компиляции или линковке. Стрелки показывают, какие файлы и макросы комбинируются во время процесса компиляции или линковки.
В начале верхней части диаграммы, STABLEHDRS и BOUNDRY это оба макросы NMAKE, в которых Вы перечисляете файлы, которые вряд ли нуждаются в перекомпиляции. Эти файлы компилируются строкой команды
CL /c /W3 /Yc$(BOUNDRY) applib.cpp myapp.cpp
только если файл precompiled header (STABLE.pch) не существует, или если Вы сделали изменения в файлы, перечисленные в этих двух макросах. В любом случае файл precompiled header будет содержать код только из файлов, перечисленных в макросе STABLEHDRS. Список последнего файла, который Вы хотите предварительно скомпилировать, находится в макросе BOUNDRY.
Файлы, перечисленные в этих макросах, могут быть либо заголовочными файлами (*.h), либо исходным кодом C или C++ (один PCH-файл не может использоваться с модулями обоих языков C и C++). Обратите внимание, что Вы можете использовать макрос hdrstop для остановки предварительной компиляции в некоторой точке файла BOUNDRY.
Ниже на диаграмме APPLIB.obj представляет поддержку кода, используемого в Вашем конечном приложении. Он создается из APPLIB.cpp, а также из файлов, перечисленные в макросе UNSTABLEHDRS, и предварительно скомпилированного кода из precompiled header.
MYAPP.obj представляет конечное приложение. Он создается из MYAPP.cpp, файлов, перечисленных в макросе UNSTABLEHDRS, и предварительно скомпилированного кода из precompiled header.
И наконец, исполняемый файл (MYAPP.EXE) создается линковкой файлов, перечисленных в макросе OBJS (файлы APPLIB.obj и MYAPP.obj).
Пример 1 Makefile для PCH. Следующий makefile использует макросы и структуры команд управления потоком !IF, !ELSE, !ENDIF, чтобы упростить адаптацию для Вашего проекта.
Содержимое файла makefile:
# Makefile: иллюстрация эффективного использования precompiled
# headers в проекте
# Usage: NMAKE option
# option: DEBUG=[0|1]
# (если DEBUG не определен, то это эквивалентно DEBUG=0)
#
OBJS = myapp.obj applib.obj
# Список всех стабильных (редко изменяемых) файлов в макросе STABLEHDRS:
STABLEHDRS = stable.h another.h
# Список конечного заголовочного файла, который здесь прекомпилируется:
BOUNDRY = stable.h
# Список изменяемых заголовков (хедеры, находящиеся в разработке):
UNSTABLEHDRS = unstable.h
# Список всех опций компилятора для обоих версий
# приложения, debug и final:
CLFLAGS = /c /W3
# Список всех опций линкера для обоих версий приложения, debug и final:
LINKFLAGS = /NOD /ONERROR:NOEXE
!IF "$(DEBUG)" == "1"
CLFLAGS = /D_DEBUG $(CLFLAGS) /Od /Zi /f
LINKFLAGS = $(LINKFLAGS) /COD
LIBS = slibce
!ELSE
CLFLAGS = $(CLFLAGS) /Oselg /Gs
LINKFLAGS = $(LINKFLAGS)
LIBS = slibce
!ENDIF
myapp.exe: $(OBJS)
link $(LINKFLAGS) @<<
$(OBJS), myapp, NUL, $(LIBS), NUL;
<<
# Компиляция myapp
myapp.obj : myapp.cpp $(UNSTABLEHDRS) stable.pch
$(CPP) $(CLFLAGS) /Yu$(BOUNDRY) myapp.cpp
# Компиляция applib
applib.obj : applib.cpp $(UNSTABLEHDRS) stable.pch
$(CPP) $(CLFLAGS) /Yu$(BOUNDRY) applib.cpp
# Компиляция заголовков
stable.pch : $(STABLEHDRS)
$(CPP) $(CLFLAGS) /Yc$(BOUNDRY) applib.cpp myapp.cpp
Кроме макросов STABLEHDRS, BOUNDRY и UNSTABLEHDRS, показанных на рисунке "Структура Makefile, который использует файл Precompiled Header", используемых в PCH-файлах в процессе сборки, этот makefile предоставляет макросы CLFLAGS и LINKFLAGS. Вы должны использовать эти макросы, чтобы указать списки опций компилятора и линкера, применяемых при сборке к отладочной или финальной версии исполняемого файла приложения. Здесь есть также макрос LIBS, где Вы перечисляете библиотеки, требуемые для проекта.
Файл makefile также использует !IF, !ELSE, !ENDIF чтобы выяснить, определен ли символ DEBUG в командной строке NMAKE:
NMAKE DEBUG=[1|0]
Эта функция делает возможной использовать один и тот же makefile как для отладочной, так и для финальной версии программы — для финальной версии используйте DEBUG=0. Если ничего не указывать, то подразумевается финальная версия, следующие строки команд эквивалентны:
NMAKE
NMAKE DEBUG=0
Для дополнительной информации по файлам makefile см. руководство NMAKE [6]. Также см. описание опций компилятора [7] и линкера [8].
Пример 2 кода для PCH. Следующие файлы исходного кода используются в примере makefile, описанном выше. Обратите внимание, что комментарии содержат важную информацию.
Заголовочный файл another.h:
// ANOTHER.H: содержит интерфейс для кода, который скорее
// всего будет редко изменяться.
#ifndef __ANOTHER_H
#define __ANOTHER_H
#include < iostream>
void savemoretime( void );
#endif // __ANOTHER_H
Заголовочный файл stable.h:
// STABLE.H: содержит интерфейс для кода, который скорее
// всего не будет изменяться. Этот список
// в makefile представлен макросом STABLEHDRS.
#ifndef __STABLE_H
#define __STABLE_H
#include < iostream>
void savetime( void );
#endif // __STABLE_H
Заголовочный файл unstable.h:
// UNSTABLE.H: содержит интерфейс для кода, который вероятно
// будет подвержен изменениям. Как только этот код
// в заголовке станет стабильным, удалите его
// имя из макроса UNSTABLEHDR файла makefile,
// и поместите в макрос STABLEHDRS.
#ifndef __UNSTABLE_H
#define __UNSTABLE_H
#include < iostream.h>
void notstable( void );
#endif // __UNSTABLE_H
Код пользовательской библиотеки приложения applib.cpp:
// APPLIB.CPP: этот файл содержит код, который реализует интерфейс,
// задекларированный в заголовочных файлах STABLE.H,
// ANOTHER.H и UNSTABLE.H.
//#include "another.h"
#include "stable.h"
#include "unstable.h"
// Следующие строки представляют код, который считается стабильным,
// и он скорее всего не будет меняться. Связанный код интерфейса
// будет подвержен предварительной компиляции. В этом примере
// прекомпилируются заголовочные файлы STABLE.H и ANOTHER.H.
void savetime( void )
{ cout << "Почему перекомпилируется стабильный код?\n"; }
void savemoretime( void )
{ cout << "Почему, в самом деле?\n\n"; }
// Следующие строки представляют код, который все еще находится
// в разработке. Связанный файл заголовка не прекомпилируется.
void notstable( void )
{ cout << "Unstable-код требует"
<< " частой перекомпиляции.\n";
}
Основной модуль приложения, содержащий функцию main:
// MYAPP.CPP: приложение примера.
// Весь прекомпилированный код, кроме файла, перечисленного
// в макросе BOUNDRY из makefile (в этом примере stable.h),
// должен быть подключен перед файлом, перечисленным
// в макросе BOUNDRY. Unstable-код должен быть подключен
// после precompiled-кода.
#include "another.h"
#include "stable.h"
#include "unstable.h"
int main( void )
{
savetime();
savemoretime();
notstable();
}