Как на самом деле работают предварительно скомпилированные заголовки

Все мои вопросы связаны с компилятором vc ++, но я предполагаю, что другие компиляторы C ++ имеют такое же поведение.

  1. Are the precompiled headers a preprocessor-related stuff or this is all about the compilation process? Or both? I have several guess:
    • PCH-engine only expands MACRO-definitions and nested headers and translates them into binary format (pch file). In this case all source-files (I mean cpp/hpp which may be included in PCH too) will being recompiled in EVERY source files in project. Or not?
    • Все исходные файлы будут скомпилированы только один раз и сведены в один obj-файл? Например, сколько раз будет компилироваться вариант библиотеки в этом примере? Т.е. только один раз - в PCH или два раза - не в PCH, а в обоих файлах * .cpp или трижды - в PCH и обоих файлах * .cpp? и почему?

//stdafx.h
#include <boost/variant/variant.hpp>

//test1.cpp
#include "stdafx.h"
#include <boost/variant/variant.hpp>
...

//test2.cpp
#include "stdafx.h"
...
  1. Какие файлы я должен помещать в предварительно скомпилированные заголовки? Думаю, это то, что используется в проекте повсюду и меняется очень редко. А библиотеки, буст например? Мы используем boost только в нескольких исходных файлах, стоит ли помещать его в PCH?

person Dmitry Katkevich    schedule 08.04.2016    source источник
comment
Предварительно скомпилированные заголовки более или менее похожи на то, на что это похоже. Это все заголовки, включенные в "stdafx.h" файл, предварительно обработанные, проанализированные и сохраненные в виде дерева синтаксического анализа (если вы не знаете, что это такое, прочтите некоторую теорию компилятора). Это означает, что когда вы включаете такой файл, компилятору не нужно выполнять все эти шаги для файлов заголовков, и компиляция будет быстрее. Файлы заголовков Boost являются хорошими кандидатами для включения в PCH, как и любые внешние (и, возможно, даже некоторые внутренние) заголовки библиотеки.   -  person Some programmer dude    schedule 08.04.2016
comment
Есть этот документ о внутреннем устройстве PCH.   -  person Dan Mašek    schedule 08.04.2016


Ответы (3)


У меня нет особых знаний о внутренностях VC ++. Однако при наличии некоторых знаний о конструкции и теории компилятора то, что представляют собой эти так называемые «предварительно скомпилированные заголовки», не может быть ничем иным, как просто результатом начального лексического анализа и этапов токенизации классической конструкции компилятора.

Рассмотрим простой файл заголовка, который содержит следующее:

#ifdef FOO
#define BAR 10
#else
#undef FOOBAR
class Foo {
public:
     void bar();
};
#include "foobar.h"
#endif

Вы должны понимать, что эффект от использования так называемого «предварительно скомпилированного» заголовка должен быть идентичен использованию файла заголовка, как есть.

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

Единственное, что вы можете концептуально сделать, «предварительно скомпилировать» заголовочный файл, - это предварительно проанализировать его. Преобразуйте отдельные элементы языка, отдельные ключевые слова, такие как "#ifdef", "class" и все другие, в отдельные двоичные токены. Удалите все комментарии, пробелы и т. Д.

Первый этап компиляции традиционного языка включает в себя синтаксический анализ исходного текста на внутренние языковые элементы. Лексический анализ и этап токенизации. После анализа отдельных языковых элементов предпринимается попытка выяснить, как полученный проанализированный исходный код должен быть преобразован в объектный модуль. И здесь 99% работы компилятора. Фаза начального лексического анализа на самом деле невелика, но это почти все, что вы можете сделать, чтобы «предварительно скомпилировать» исходный код и сохранить внутреннее двоичное представление токенизированного источника, чтобы эту фазу можно было пропустить, когда реальный код использует "предварительно скомпилированный" исходный код.

Я предполагаю, что VC ++ практически не накладывает ограничений на содержимое предварительно скомпилированных заголовков. Однако, если есть некоторые ограничения - скажем, предварительно скомпилированные заголовки не могут иметь каких-либо условных директив препроцессора (ifdef / ifndef), за исключением классических защитных мер, - тогда можно было бы проделать больше работы для создания предварительно скомпилированных заголовков и сохранить немного больше работы здесь. Другие ограничения на содержимое предварительно скомпилированных заголовков также могут привести к смещению некоторых дополнительных функций в фазу предварительной компиляции.

person Sam Varshavchik    schedule 08.04.2016
comment
Хорошо, тогда как насчет первого файла test1.cpp, описанного в моем примере выше. Он включает как PCH, так и ‹boost / variant.hpp›. Если я понимаю, в этом случае ‹boost / variant.hpp› будет предварительно обработан дважды - в PCH и в test1.cpp. Или нет? И следует ли включать такие заголовки в файлы * .cpp, если они уже включены в PCH, чтобы избежать дублирования? - person Dmitry Katkevich; 08.04.2016
comment
PCH может включать что угодно, AFAIK. Я ожидал, что они сбросят AST в том виде, в котором он был построен. Поскольку вы бы поместили туда наиболее часто используемые заголовки (учитывая, насколько быстро может расти количество включаемых файлов), это даст довольно существенные преимущества. - person Dan Mašek; 08.04.2016
comment
@DmitryKatkevich Обратите внимание, что в начале boost/variant.hpp есть защита включения. Все состояние сохраняется, и компиляция работает так же, как и без предварительной компиляции заголовков. Это как если бы состояние компилятора было приостановлено и сохранено, а затем снова перезагружено и продолжено с этого момента. - person Dan Mašek; 08.04.2016
comment
@Dan Mašek Хорошо. И если мой файл * .cpp не использует ничего из PCH (например, я добавляю ускорение в PCH, но использую его только в нескольких исходных файлах), будет ли ненужный «штраф» во время предварительной обработки? - person Dmitry Katkevich; 08.04.2016
comment
@DmitryKatkevich Компилятору потребуется больше памяти, и на любом из этапов, которые не были завершены во время создания PCH, вероятно, будет больше работы. Есть баланс, вы хотели бы использовать его для вещей, которые включены в большинство ваших файлов .cpp. Если есть только подмножество кода, которое имеет ряд общих включаемых файлов, вы можете рассмотреть возможность разделения его в библиотеку, которая будет иметь свой собственный файл PCH. - person Dan Mašek; 08.04.2016
comment
@Dan Mašek Верно ли, что если я включу stdafx.h в файл cpp, весь код будет скомпилирован независимо от того, используется ли PCH в cpp или нет? (Я имею в виду, что некоторые исходные файлы могут не использовать ничего из PCH) Думаю, да. В этом случае PCH будет компилироваться каждый раз при включении в cpp. В моем примере выше и test1.cpp, и test2.cpp включают stdafx.h, поэтому boost / option будет Скомпилирован дважды, и это не очень хорошо. Но в то же время людям рекомендуется включать ускорение в PCH, хотя ускорение - это то, что используется только в нескольких исходных файлах, я думаю ... - person Dmitry Katkevich; 08.04.2016
comment
Да, вы должны включать только заголовки, используемые подавляющим большинством вашего кода. Вы могли бы легко использовать несколько файлов PCH (вы можете установить разные для каждого файла .cpp). Лично я их не использую - я считаю, что хорошая модульность, инкапсуляция, использование форвардных объявлений и включение только того, что вы используете (например, <boost/variant/variant.hpp>, а не <boost/variant.hpp>) также дают хорошие результаты. Параллельная компиляция и наличие системы CI для выполнения многих полных сборок также вам помогут. - person Dan Mašek; 09.04.2016

Предварительно скомпилированный заголовочный файл, который компилируется с помощью stdafx.cpp, имеет вид stdafx.h. Разработчик помещал в этот заголовок редко изменяемые и часто необходимые файлы заголовков и символы. Такие как Windows.h, vector и некоторые глобальные макросы и символы. Под часто используемым я подразумеваю для всех файлов в данном проекте.

Какова цель и полезность такого файла (PCH)? Что ж, компилятор VC ++ будет компилировать весь stdafx.h файл рекурсивно, все заголовки включают все макросы и другие символы. В первый раз это займет много времени, и будет создан файл PCH (отсюда p перекомпилированный h читатель). В последующих сборках элементы, включенные через stdafx.h, не будут перекомпилированы (поскольку они уже находятся в некотором двоичном / предварительно скомпилированном формате). Это сокращает время сборки, и оно будет варьироваться в зависимости от того, сколько элементов (заголовков, символов и т. Д.) Помещается в stdafx.h файл.

Если у вас большая кодовая база и меньше элементов в stdafx, вы не получите преимущества (например, включая повсюду общие заголовки Windows и STL, везде есть externs и typedef). Лучше найти эти элементы, поместить их в stdafx.h и удалить их из файлов заголовков / CPP. Это значительно сократит общее время сборки.

Здесь вы можете изменить его:  введите описание изображения здесь

person Ajay    schedule 09.04.2016

Я думаю, что MSVC ищет <application_name>.pch для единственного предварительно скомпилированного заголовка для единицы перевода и использует его вместо включенного заголовка, включенного в #line 1 "c:\\application_name\\stdafx.h" в предварительно обработанном .i файле, если он доступен. Предварительно скомпилированный заголовок, вероятно, является сериализованным AST, то есть заголовок был преобразован в лексический формат и преобразован в представление AST. Тогда ему не нужно lex (или синтаксический анализ) этой области предварительно обработанного вывода и просто использует .pch, который содержит вывод lex + parse того, что записано в выводе препроцессора в stdafx.h. Препроцессор уже выполнил всю остальную работу с stafx.h, такую ​​как раскрытие макросов (которые не отображаются в выходных данных .i файла / препроцессора).

person Lewis Kelsey    schedule 15.03.2021