Предкомпилируемая информация установки что это
С++ Builder: как ускорить компиляцию с помощью предкомпилированных заголовков
Принцип действия предкомпилированных заголовков
Для управления предкомпилированными предназначена директива компилятора #pragma hdrstop. Все заголовочные файлы, включенные до этой директивы, помещаются в один образ, например:
Таким образом, для повторного использования предкомпилированного заголовка необходимо выполнение двух условий:
Сократить затраты на компиляцию стандартных заголовков до минимума можно только в том случае, если скомпилировать один образ, содержащий все стандартные заголовки, необходимые для проекта. Для этого нужно, чтобы:
Выполнить эти условия достаточно просто, для этого в начало каждого cpp-файла необходимо поместить следующие строки:
Из-за наличия этих констант включить math.hpp в файл pch.h нельзя.
Кстати, С++ Builder при добавлении новых модулей в проект реализует описанную стратегию управления предкомпилированными заголовками. Например, при создании нового приложения, файл Unit1.cpp будет таким:
Если посмотреть на текст vcl.h, то можно увидеть, что он является оболочкой для включения большого числа других стандартных заголовочных файлов.
Управлять составом включаемых в vcl.h заголовков можно с помощью специальных символов (INC_VCLDB_HEADERS, INC_VCLEXT_HEADERS и др.). В моей версии pch.h эти символы определяются с помощью #define до включения vcl.h, что приводит к увеличению числа включаемых файлов.
Как в существующем проекте перейти к использованию предкомпилированных заголовков
Даже в большом проекте перейти к использованию предкомпилированных заголовков достаточно просто.
После этого в начало каждого cpp-модуля необходимо вставить 2 строки:
Все ранее включенные заголовочные файлы остаются на своих местах, их удалять не надо. Например:
Так как во всех стандартных заголовках применяются стражи повторного включения, то повторное их упоминание не влечет за собой повторного включения.
Теоретически можно еще больше повысить эффективность компиляции, если включить в pch.h не только стандартные, но и все пользовательские заголовочные файлы. Практически, так как пользовательские заголовки меняются достаточно часто, это может повлечь за собой частую перекомпиляцию pch.h, что негативно скажется на времени компиляции. Кроме того, пользовательские заголовки обычно не бывают очень большими и компилируются очень быстро. Поэтому включать их pch.h не целесообразно.
Как проверить, что предкомпилированные заголовки используются эффективно
При добавлении в проект новых файлов нужно не забывать включать в них pch.h, иначе для них не будет использован общий предкомпилированный образ. Такая же ситуация может возникнуть, если в каком-то модуле включаются стандартные заголовки, которые не вошли в pch.h. Для того, чтобы отследить такие файлы, есть несколько способов:
#ifndef PCH_H #define PCH_H #define INC_VCLDB_HEADERS #define INC_VCLEXT_HEADERS #include /* Все, что подключается предыдущими 3-мя строчками // Core (minimal) Delphi RTL headers #include #include #include #include #include #include // Core (minimal) VCL headers #if defined(INC_VCL) #include #include #include
Ускорение сборки C и C++ проектов
Многие программисты не понаслышке знают о том, что программа на языке C и C++ собирается очень долго. Кто-то решает эту проблему, сражаясь на мечах во время сборки, кто-то — походом на кухню «выпить кофе». Это статья для тех, кому это надоело, и он решил, что пора что-то предпринять. В этой статье разобраны различные способы ускорения сборки проекта, а также лечение болезни «поправил один заголовочный файл — пересобралась половина проекта».
Общие принципы
Прежде чем начать, давайте узнаем/вспомним об основных фазах трансляции кода C/C++ в исполняемую программу.
Согласно п.5.1.1.2 драфта N1548 «Programming languages — C» и п.5.2 драфта N4659 «Working Draft, Standard for Programming Language C++» (опубликованные версии стандартов можно приобрести здесь и здесь) определены 8 и 9 фаз трансляции соответственно. Давайте опустим детали и рассмотрим абстрактно процесс трансляции:
Для упрощения согласования разных единиц трансляции был придуман механизм заголовочных файлов, заключающийся в объявлении четкого интерфейса. Впоследствии каждая единица трансляции в случае надобности включает заголовочный файл через директиву препроцессора #include.
Далее рассмотрим, как можно ускорить сборку на разных фазах. Кроме самого принципа также будет полезно описать, как внедрить тот или иной способ в сборочную систему. Примеры будут приводиться для следующих сборочных систем: MSBuild, Make, CMake.
Зависимости при компиляции
Зависимости при компиляции – это то, что в наибольшей степени влияет на скорость сборки C/C++ проектов. Они возникают всякий раз, когда вы включаете заголовочный файл через препроцессорную директиву #include. При этом создается впечатление, что существует лишь один источник объявления для какой-то сущности. Реальность же далека от идеала — компилятору приходится многократно обрабатывать одни и те же объявления в разных единицах трансляции. Еще сильнее картину портят макросы: стоит перед включением заголовка добавить объявление макроса, как его содержимое может в корне измениться.
Рассмотрим пару способов, как можно уменьшить число зависимостей.
Способ N1: убирайте неиспользуемые включения. Не надо платить за то, что вы не используете. Так вы сокращаете работу как препроцессору, так и компилятору. Можно как вручную «перелопатить» заголовки/исходные файлы, так и воспользоваться утилитами: include-what-you-use, ReSharper C++, CppClean, Doxygen + Graphviz (для визуализации диаграммы включений) и т.д.
Способ N2: используйте зависимость от объявления, а не от определения. Выделим 2 главных аспекта:
1) В заголовочных файлах не используйте объекты там, где можно воспользоваться ссылками или указателями. Для ссылок и указателей достаточно опережающего объявления, поскольку компилятор знает размер ссылки/указателя (4 или 8 байт в зависимости от платформы), а размер передаваемых объектов не имеет значения. Простой пример:
Теперь, при изменении первого заголовка компилятору придется перекомпилировать единицы трансляции, зависимые как от Foo.h, так и Bar.h.
Чтобы разорвать подобную связь, достаточно отказаться от передачи объекта obj по значению в пользу передачи по указателю или ссылке в заголовке Bar.h:
Что касается стандартных заголовков, то здесь можно волноваться поменьше и просто включать их в заголовочный файл при необходимости. Исключением является разве что iostream. Этот заголовочный файл настолько вырос в размерах, что к нему дополнительно поставляется заголовок iosfwd, содержащий только опережающие объявления нужных сущностей. Именно его и стоит включать в ваши заголовочные файлы.
2) Используйте идиомы Pimpl или интерфейсного класса. Pimpl убирает детали реализации, помещая их в отдельный класс, объект которого доступен через указатель. Второй подход основан на создании абстрактного базового класса, детали реализации которого переносятся в производный класс, переопределяющем чистые виртуальные функции. Оба варианта устраняют зависимости на этапе компиляции, но также вносят свои накладные расходы во время работы программы, а именно: создание и удаление динамического объекта, добавление уровня косвенной адресации (из-за указателя); и отдельно в случае интерфейсного класса — расходы на вызов виртуальных функций.
Способ N3 (опционально): дополнительно можно создавать заголовки, содержащие только опережающие объявления (аналог iosfwd). Эти «опережающие» заголовки затем можно включать в другие обычные заголовки.
Параллельная компиляция
При стандартном подходе компилятору раз за разом будет поступать новый файл для препроцессирования и компиляции. Поскольку каждая единица трансляции самодостаточна, то хороший способ ускорения — распараллелить фазы I-II трансляции, обрабатывая одновременно N файлов за раз.
В Visual Studio режим включается флагом /MP[processMax] на уровне проекта, где processMax — опциональный аргумент, отвечающий за максимальное количество процессов компиляции.
В make режим включается флагом -jN, где N — число процессов компиляции.
Если вы используете CMake (к тому же и в кросс-платформенной разработке), то им можно сгенерировать файлы для обширного списка сборочных систем через флаг -G. Например, CMake генерирует для C++ анализатора PVS-Studio решение для Visual Studio под Windows, так и Unix Makefiles под Linux. Чтобы CMake генерировал проекты в решении Visual Studio с флагом /MP, добавьте следующие строки в ваш CMakeLists.txt:
Также через CMake (с версии 2.8.0) можно позвать сборочную систему с флагами параллелизации. Для MSVC (/MP указан в CMakeLists.txt) и Ninja (параллелизм уже включен):
Распределенная компиляция
Воспользовавшись предыдущим советом, можно в разы снизить время сборки. Однако, когда проект огромен, и этого может быть недостаточно. Увеличивая число процессов компиляции, вы натыкаетесь на барьер в виде максимального числа одновременно компилируемых файлов из-за процессора/оперативной памяти/дисковых операций. Здесь и приходит на помощь распределенная компиляция, использующая свободные ресурсы товарища за спиной. Идея проста:
1) препроцессируем исходные файлы на одной локальной машине или на всех доступных машинах;
2) компилируем препроцессированные файлы на локальной и на удаленных машинах;
3) ожидаем результата от других машин в виде объектных файлов;
4) компонуем объектные файлы;
Выделим основные особенности распределенной компиляции:
1) Универсальный, через символическую ссылку (symlink)
2) Для CMake, начиная с версии 3.4
Кэш компилятора
Другим способом уменьшить время сборки является применение кэша компилятора. Немного изменим фазу II трансляции кода:
Теперь при компиляции препроцессированного файла на основе его содержимого, флагов компиляции, вывода компилятора вычисляется хэш-значение (учитывает флаги компиляции). Впоследствии хэш-значение и соответствующий ему объектный файл регистрируется в кэше компилятора. При повторной компиляции с теми же флагами неизмененного файла из кэша будет взят уже готовый объектный файл и подан на вход компоновщика.
Что можно использовать:
1) Универсальный, через символическую ссылку
2) Для CMake, начиная с версии 3.4
Кэш компилятора также можно интегрировать в распределенную компиляцию. Например, для использования ccache с distcc/Icecream, выполните следующие действия:
1) Установите переменную CCACHE_PREFIX:
2) Воспользуйтесь одним из пунктов 1 — 2 регистрации ccache.
Предварительно откомпилированные заголовочные файлы
При компиляции большого количества исходных файлов компилятор, по факту, выполняет множество раз одну и ту же работу по разбору тяжеловесных заголовков (например, iostream). Основная идея заключается в том, чтобы вынести эти тяжеловесные заголовки в отдельный файл (обычно именуется префиксным заголовком), который компилируется единожды и затем включается во все единицы трансляции самым первым.
В MSVC для создания предкомпилированного заголовка по умолчанию генерируются 2 файла: stdafx.h и stdafx.cpp (можно использовать и другие имена). Первым шагом необходимо скомпилировать stdafx.cpp с флагом /Yc«path-to-stdafx.h». По умолчанию создается файл с расширением .pch. Чтобы использовать предкомпилированный заголовок при компиляции исходного файла используем флаг /Yu«path-to-stdafx.h». Совместно с флагами /Yc и /Yu также можно использовать /Fp«path-to-pch» для указания пути к .pch файлу. Теперь необходимо подключить в каждой единице трансляции префиксный заголовок самым первым: либо непосредственно через #include «path-to-stdafx.h», либо принудительно через флаг /FI«path-to-stdafx.h».
Подробно о предварительно откомпилированных заголовках вы можете прочитать здесь.
Если вы используете CMake, то рекомендуем попробовать модуль cotire: он может в автоматическом режиме проанализировать исходные файлы, сгенерировать префиксный и предкомпилированный заголовки и подключить их к единицам трансляции. Есть также возможность указать свой префиксный заголовок (например, stdafx.h).
Single Compilation Unit
Суть данного метода — создать единый компилируемый файл (блок трансляции), в который включаются другие единицы трансляции:
Если в единый компилируемый файл включаются все единицы трансляции, то такой способ иначе называют Unity build. Выделим основные особенности Single Compilation Unit:
Замена компонентов трансляции
Замена одного из компонентов трансляции на более быстрый аналог также может увеличить скорость сборки. Однако, делать это стоит на свой страх и риск.
В качестве более быстрого компилятора можно воспользоваться Zapcc. Авторы обещают многократное ускорение перекомпиляции проектов. Это можно проследить на примере перекомпиляции Boost.Math:
Zapcc не жертвует производительностью программ, основан на Clang и полностью с ним совместим. Здесь можно ознакомиться с принципом работы Zapcc. Если ваш проект основан на CMake, то заменить компилятор очень легко:
Если ваша ОС использует ELF-формат объектных файлов (Unix-подобные системы), то можно заменить компоновщик GNU ld на GNU gold. GNU gold идет в составе binutils, начиная с версии 2.19, и активируется флагом -fuse-ld=gold. В CMake его можно активировать, например, следующим кодом:
Использование SSD/RAMDisk
Очевидным «бутылочным горлышком» в сборке является скорость дисковых операций (в особенности случайного доступа). Перенос временных файлов проекта или его самого на более быструю память (HDD с повышенной скоростью случайного доступа, SSD, RAID из HDD/SSD, RAMDisk) в некоторых ситуациях может сильно помочь.
Модульная система в C++
Большинство вышеперечисленных способов исторически возникли из-за выбора принципа трансляции C/C++ языков. Механизм заголовочных файлов, несмотря на кажущуюся простоту, доставляет много хлопот для C/C++ программистов.
Уже достаточно продолжительное время идет обсуждение о включении модулей в стандарт C++ (и, возможно, появится в C++20). Модулем будет считаться связанный набор единиц трансляции (модульная единица) с определенным набором внешних (экспортируемых) имен, называемых интерфейсом модуля. Модуль будет доступен для всех импортирующих его единиц трансляции только через его интерфейс. Неэкспортируемые имена помещаются в имплементацию модуля.
Другим важным достоинством модулей является то, что они не подвергаются изменениям через макросы и директивы препроцессора, в отличие от заголовочных файлов. Справедливо также и обратное: макросы и директивы препроцессора внутри модуля не влияют на единицы трансляции, импортирующие его. Семантически, модули представляют собой самостоятельные, полностью скомпилированные единицы трансляции.
В данной статье не будет детально рассматриваться устройство будущих модулей. Если вы хотите узнать о них больше, то рекомендуем к просмотру выступление Бориса Колпакова на CppCon 2017 о модулях C++ (там также показана разница по времени сборок):
На сегодняшний момент компиляторы MSVC, GCC, Clang предлагают экспериментальную поддержку модулей.
А что-нибудь про сборку PVS-Studio будет?
В этом разделе давайте рассмотрим, насколько бывают эффективными и полезными описанные подходы.
За основу возьмем ядро анализатора PVS-Studio для анализа C и C++ кода. Оно, конечно же, написано на C++ и представляет собой консольное приложение. Ядро является небольшим проектом по сравнению с такими гигантами, как LLVM/Clang, GCC, Chromium и т.д. Вот, например, что выдает CLOC на нашей кодовой базе:
Отметим, что до проведения всяких работ наш проект собирался за 1.5 минуты (использовались параллельная компиляция и один предкомпилированный заголовок) на следующей конфигурации рабочей машины:
Рисунок 1. Сборка анализатора PVS-Studio, 1 поток, без оптимизаций. Сверху — сборка Debug версии, снизу — Release.
Как видно из диаграммы, за счет большей скорости произвольного доступа проект на RAMDisk без оптимизаций в 1 поток собирается быстрей.
Второй этап замеров — дорабатываем напильником исходный код: удаляем ненужные включения заголовков, устраняем зависимости от определения, улучшаем предкомпилированный заголовок (убираем из него часто изменяемые заголовки) — и постепенно прикручиваем оптимизации:
Рисунок 2. Компиляция в 1 поток после оптимизаций.
Рисунок 4. Компиляция в 8 потоков после оптимизаций.
Сделаем краткие выводы:
Заключение
Для многих программистов языки C/C++ ассоциируются как нечто «долго компилирующееся». И на это есть свои причины: выбранный в свое время способ трансляции, метапрограммирование (для C++), тысячи их. Благодаря описанным методам оптимизации можно лишить себя подобных предрассудков о чрезмерно долгой компиляции. В частности, время сборки нашего ядра анализатора PVS-Studio для анализа C и C++ кода удалось снизить с 1 минуты 30 секунд до 40 секунд путем интеграции Single Compilation Units и переработки заголовочных и исходных файлов. Более того, если бы до начала оптимизаций не были использованы параллельная компиляция и предкомпилированные заголовки, нами было бы получено семикратное уменьшение времени сборки!
В окончание хочется добавить, что об этой проблеме прекрасно помнят в комитете стандартизации и полный ходом идет решение данной проблемы: все мы ждем нового стандарта C++20, который, возможно, одним из нововведений «завезёт» модули в любимый многими язык и сделает жизнь C++ программистов гораздо проще.
Для новичков про stdafx.h
Для чего нужны Precompiled Headers
Precompiled headers предназначены для ускорения сборки проектов. Обычно программисты начинают знакомиться с Visual C++, используя крошечные проекты. На них сложно заметить выигрыш от precompiled headers. Что с ними, что без них, на глаз программа компилируется одинаковое время. Это добавляет путаницы. Человек не видит для себя пользы от этого механизма и решает, что он для специфичных задач и ему никогда не понадобится. И иногда считает так многие годы.
На самом деле, precompiled headers весьма полезная технология. Пользу от него можно заметить, даже если в проекте всего несколько десятков файлов. Особенно выигрыш становится заметен, если используются такие тяжёлые библиотеки как boost.
Всё это приводит к тому, что препроцессор в компиляторе вновь и вновь выполняет идентичную работу. Он должен читать одни и те же файлы, вставлять их друг в друга, выбирать #ifdef ветки и подставлять значения макросов. Происходит колоссальное дублирование одних и тех же операций.
Можно существенно сократить объем работы, которую должен проделать препроцессор при компиляции проекта. Идея в том, чтобы заранее препроцессировать группу файлов и затем просто подставлять готовый фрагмент текста.
На самом деле, делается ещё ряд шагов. Можно хранить не просто текст, а более обработанную информацию. Я не знаю, как именно устроено в Visual C++. Но, например, можно хранить текст уже разбитый на лексемы. Это ещё больше ускорит процесс компиляции.
Как работают Precompiled Headers
Файл, который содержит precompiled headers, имеет расширение «.pch». Имя файла обычно совпадает с названием проекта. Естественно, это и другие используемые имена можно изменить в настройках. Файл может быть весьма большим и зависит от того, как много заголовочных файлов в нём раскрыто. Например, в проекте PVS-Studio он занимает около 3 мегабайт.
Файл *.pch возникает после компиляции stdafx.cpp. Файл собирается с ключом «/Yc». Этот ключ как раз и говорит компилятору, что нужно создать precompiled headers. Файл stdafx.cpp может содержать одну строчку: #include «stdafx.h».
В файле «stdafx.h» находится самое интересное. Сюда нужно включить заголовочные файлы, которые будут заранее препроцессироваться. В качестве примера, вот файл stdafx.h, используемый нами в PVS-Studio (файл сокращён для статьи):
Директивы «#pragma warning» нам нужны, чтобы избавиться от предупреждений, выдаваемых на стандартные библиотеки.
Теперь во все файлы *.c/*.cpp следует включить «stdafx.h». Заодно стоит удалить из этих файлов заголовки, которые уже включаются с помощью «stdafx.h».
Как использовать Precompiled Headers
При создании нового проекта Wizard в Visual Studio создаёт два файла: stdafx.h и stdafx.cpp. Именно с помощью них и реализуется механизм precompiled headers.
На самом деле, эти файлы могут называться, как угодно. Важно не название, а параметры компиляции в настройках проекта.
В *.c/*.cpp файле можно использовать только один precompiled header. Однако, в одном проекте может присутствовать несколько разных precompiled headers. Пока будем считать, что он у нас только один.
Итак, если вы воспользовались wizard-ом, то у вас уже есть файлы stdafx.h и stdafx.cpp. Плюс выставлены все необходимые ключи компиляции.
Для всех *.c/*.cpp файлов мы указали, что они должны использовать precompiled headers. Этого мало. Теперь в каждый из файлов нужно добавить #include «stdafx.h».
Заголовочный файл «stdafx.h» должен включаться в *.c/*.cpp файл самым первым. Обязательно! Иначе всё равно возникнут ошибки компиляции.
Если подумать, в этом есть логика. Когда файл «stdafx.h» находится в самом начале, то можно подставить уже препроцессированный текст. Этот текст всегда одинаков и ни от чего не зависит.
Представьте ситуацию, если бы мы могли включить до «stdafx.h» ещё какой-то файл. А в этом файле возьмём и напишем: #define bool char. Возникает неоднозначность. Мы меняем содержимое всех файлов, в которых упоминается «bool». Теперь просто так нельзя взять и подставить заранее препроцессированный текст. Ломается весь механизм «precompiled headers». Думаю, это одна из причин, почему «stdafx.h» должен быть расположен в начале. Возможно, есть и другие.
Life hack
Прописывать #include «stdafx.h» во все *.c/*.cpp файлы достаточно утомительно и не интересно. Дополнительно получится ревизия в системе контроля версий, где будет изменено огромное количество файлов. Нехорошо.
Ещё одно неудобство вызывают сторонние библиотеки, включаемые в проект в виде файлов с кодом. Править эти файлы нет смыла. По правильному для них нужно отключить использование «precompiled headers». Однако, если используется несколько мелких сторонних библиотек, это неудобно. Программист постоянно спотыкается об precompiled headers.
Есть вариант, как использовать precompiled headers легко и просто. Способ подойдёт не везде и всегда, но мне он часто помогал.
Можно не прописывать во все файлы #include «stdafx.h», а воспользоваться механизмом «Forced Included File».
Идём на вкладку настроек «Advanced». Выбираем все конфигурации. В поле «Forced Included File» пишем:
Теперь «stdafx.h» автоматически будет включаться в начало ВСЕХ компилируемых файлов. PROFIT!
Больше не потребуется писать #include «stdafx.h» в начале всех *.c/*.cpp файлов. Компилятор сделает это сам.
Что включать в stdafx.h
Это очень важный момент. Бездумное включение в «stdafx.h» всего подряд не только не ускорит компиляцию, но и наоборот замедлит её.
Все файлы, включающие «stdafx.h», зависят от его содержимого. Пусть в «stdafx.h» включен файл «X.h». Если вы поменяете хоть что-то в «X.h», это может повлечь полную перекомпиляцию всего проекта.
Правило. Включайте в «stdafx.h» только те файлы, которые никогда не изменяются или меняются ОЧЕНЬ редко. Хорошими кандидатами являются заголовочные файлы системных и сторонних библиотек.
Если включаете в «stdafx.h» собственные файлы из проекта, соблюдайте двойную бдительность. Включайте только те файлы, которые меняются очень-очень редко.
Если какой-то *.h файл меняется раз в месяц, это уже слишком часто. Как правило, редко удаётся сделать все правки в h-файле с первого раза. Обычно требуется 2-3 итерации. Согласитесь, 2-3 раза полностью перекомпилировать весь проект — занятие неприятное. Плюс полная перекомпиляция потребуется всем вашим коллегам.
Несколько Precompiled Headers
Зачем в одном проекте может понадобиться несколько precompiled headers? Действительно, это нужно не часто. Но приведу пару примеров.
В проекте используются одновременно *.c и *.cpp файлы. Для них нельзя использовать единый *.pch файл. Компилятор выдаст ошибку.
Нужно создать два *.pch файла. Один должен получаться при компилировании C-файла (xx.c), а другой при компилировании C++-файла (yy.cpp). Соответственно, в настройках надо указать, чтобы в С-файлах использовался один precompiled header, а в С++-файлах — другой.
Примечание. Не забудьте указать разные имена для *.pch файлов. Иначе один файл будет перетирать другой.
Другая ситуация. Одна часть проекта использует одну большую библиотеку, а другая часть другую большую библиотеку.
Естественно, не стоит всем участкам кода знать про обе библиотеки. В (неудачных) библиотеках могут пересекаться имена каких-то сущностей.
Логично сделать два precompiled headers и использовать их в разных участках программы. Как уже отмечалось, можно задать произвольные имена файлов, из которых генерируются *.pch файлы. Да и имя *.pch файла тоже можно изменить. Всё это, конечно, требуется делать аккуратно, но ничего сложного в использовании двух precompiled headers нет.
Типовые ошибки при использовании Precompiled Headers
Прочитав внимательно материал выше, вы сможете понять и устранить ошибки, связанные с stdafx.h. Но давайте ещё раз пройдёмся по типовым ошибкам компиляции и разберём их причины. Повторенье — мать ученья.
Fatal error C1083: Cannot open precompiled header file: ‘Debug\project.pch’: No such file or directory
Fatal error C1010: unexpected end of file while looking for precompiled header. Did you forget to add ‘#include «stdafx.h»’ to your source?
Сообщение говорит само за себя, если его прочитать. Файл компилируется с ключом /Yu. Это значит, что следует использовать precompiled header. Но в файл не включён «stdafx.h».
Нужно вписать в файл #include «stdafx.h».
Если это невозможно, то следует не использовать precompiled header для этого *.c/*.cpp файла. Уберите ключ /Yu.
Fatal error C1853: ‘project.pch’ precompiled header file is from a previous version of the compiler, or the precompiled header is C++ and you are using it from C (or vice versa)
В проекте присутствуют как C (*.c), так и C++ (*.cpp) файлы. Для них нельзя использовать единый precompiled header (*.pch файл).
Из-за precompiled header компилятор глючит
Скорее всего, что-то сделано не так. Например, #include «stdafx.h» расположен не в самом начале.
Этот код не скомпилируется. Компилятор выдаст на первый взгляд странное сообщение об ошибке:
Компилятор считает, что все, что указано до строчки #include «stdafx.h» (включительно), является precompiled header. При компиляции файла компилятор заменит все, что до #include «stdafx.h» на текст из *.pch файла. В результате теряется строчка «int A = 10».
Содержимое файла «my.h» не будет использоваться. В результате, нельзя будет использовать функции, объявленные в этом файле. Такое поведение очень сбивает программистов с толку. Они «лечат» его полным отключением precompiled headers и потом рассказывают байки о глючности Visual C++. Запомните, компилятор — это один из наиболее редко глючащих инструментов. В 99.99% случаев надо не злиться на компилятор, а искать ошибку у себя (Proof).
Чтобы таких ситуаций не было, ВСЕГДА пишите #include «stdafx.h» в самом начале файла. Комментарии перед #include «stdafx.h» можно оставить. Они всё равно никак не участвуют в компиляции.
Ещё один вариант — используйте Forced Included File. См. выше раздел «Life hack».
Из-за precompiled headers проект постоянно перекомпилируется целиком
В stdafx.h включён файл, который регулярно редактируется. Или случайно включён автогенерируемый файл.
Внимательно проверьте содержимое файла «stdafx.h». В него должны входить только заголовочные файлы, которые не изменяются или изменяются крайне редко. Учтите, что включённые файлы могут не меняться, но внутри они ссылаются на другие изменяющиеся *.h файлы.
Творится что-то непонятное
Иногда может возникнуть ситуация, что вы поправили код, а ошибка не исчезает. Отладчик показывает непонятные вещи.
Причиной может быть *.pch файл. Как-то так получилось, что компилятор не замечает изменения в одном из заголовочных файлов и не перестраивает *.pch файл. В результате, подставляется старый код. Возможно, это происходило из-за каких-то сбоев, связанных с временем модификации файлов.
Это ОЧЕНЬ редкая ситуация. Но она возможна и про неё надо знать. Я за многие годы программирования сталкивался с ней только 2-3 раза. Помогает полная перекомпиляция проекта.
Проект, использующий precompiled headers не удаётся проверить с помощью PVS-Studio
Это наиболее частая ситуация, с которой к нам обращаются в поддержку. Подробности изложены в документации: «Устранение неисправностей при работе PVS-Studio». Здесь опишу ситуацию кратко.
Если решение (solution) компилируется, это вовсе не значит, что оно правильно устроено. Часто одно решение (solution) содержит множество проектов. В каждом проекте используются свои precompiled headers (имеется свой stdafx.h и stdafx.cpp).
Возникают проблемы, когда начинают использовать файлы из соседнего проекта. Это удобно и так часто делается. Вот только забывают, что в *.cpp файле написано: #include «stdafx.h».
И, какой из stdafx.h подхватится, это интересный вопрос. Но раз программа компилируется — программисту везёт.
К сожалению, нам сложно повторить поведение, которое возникает при использовании *.pch файла. «Честный» препроцессор работает по-другому.
В том, что solution, на самом деле, устроен не верно, можно убедиться, временно отключив precompiled headers. Сразу может вылезти масса интересных ошибок, и программист будет искренне удивляться, каким же чудом компилировался его проект.
За подробностями вновь делаю отсылку к документации. Плюс, если что-то всё равно не ясно, мы подскажем в поддержке.
Заключение
Как вы увидели, ничего сложного в precompiled headers нет. Все «многочисленные глюки компилятора», с которыми сталкивается программист при их использовании, на самом деле, являются непониманием принципов работы. Надеюсь, эта статья поможет устранить непонимание.
Precompiled headers являются очень полезным механизмом, позволяющим существенно увеличить скорость компиляции проектов.