Объединение PCH, PDB и Zi приводит к загадочной ошибке компиляции C2859 с VS2017.

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

Я также хотел бы использовать /Zi, чтобы извлечь выгоду из преимуществ параллельной сборки, связанных с /Zf, что подразумевается /Zi.

Я использую компилятор C++ VS2017, но использую систему сборки, отличную от Visual Studio, поэтому ответы, касающиеся настройки VS, бесполезны.

Что я обнаружил, так это то, что я могу настроить сборку для использования предварительно скомпилированных заголовков, или я могу настроить ее для использования /Zi, но я не могу сформировать правильную серию вызовов для выполнения обоих. Когда я пытаюсь сделать то, что считаю правильным, я получаю ошибку C2958, останавливающую сборку, и я не вижу, что я делаю неправильно.

Я построил игрушечный проект, чтобы продемонстрировать то, что я вижу. Заголовок pch.hpp выглядит так:

#pragma once
#include <vector>

И делаем короткую основную строку в main.cpp:

int main() {
    std::vector<int> xs = { 1, 2, 3 };
    return xs.size();
}

Обратите внимание, что это полное содержимое файла для main.cpp: я ничего не пропустил. Я намеренно не включаю здесь pch.hpp, потому что позже мы собираемся принудительно внедрить его с /Fi. В реальном проекте нет строк включения для предварительно скомпилированного заголовка во всех нужных местах, и для этого нужно обновить тысячи файлов. Обратите внимание, что подход с использованием /Fi действительно работает в приведенных ниже командных строках и имеет то преимущество, что механизм, основанный на forceincludes, может работать и с предварительно скомпилированными заголовками в стиле GCC.

Если мы строим вещи без /Zi, все идет нормально:

cl /Fobuild\pch.obj /TP /nologo /EHsc /errorReport:none /MD /O2 /Oy- /Zc:rvalueCast /Zc:strictStrings /volatile:iso /Zc:__cplusplus /permissive- /std:c++17 /Zc:inline /c pch.hpp /Yc /Fpbuild\pch.pch
pch.hpp

cl /Fobuild\main.obj /c main.cpp /TP /nologo /EHsc /errorReport:none /MD /O2 /Oy- /Zc:rvalueCast /Zc:strictStrings /volatile:iso /Zc:__cplusplus /permissive- /std:c++17 /Zc:inline /FIpch.hpp /Yupch.hpp /Fpbuild/pch.pch
main.cpp

Мы находим файлы, которые мы ожидаем найти в каталоге сборки:

main.obj  pch.obj  pch.pch

Однако, если мы попытаемся использовать /Fi и /Fd для создания .pdb для каждого файла и управления его именем, это вообще не сработает:

Мы можем скомпилировать предварительно скомпилированный заголовок следующим образом:

cl /Fobuild\pch.obj /TP /nologo /EHsc /errorReport:none /MD /O2 /Oy- /Zc:rvalueCast /Zc:strictStrings /volatile:iso /Zc:__cplusplus /permissive- /std:c++17 /Zc:inline /c pch.hpp /Yc /Fpbuild\pch.pch /Zi /Fdbuild\pch.pch.pdb

И пока в каталоге build все выглядит нормально:

pch.obj  pch.pch  pch.pch.pdb

Но когда мы пытаемся построить объектный файл для main, все разваливается:

cl /Fobuild\main.obj /c main.cpp /TP /nologo /EHsc /errorReport:none /MD /O2 /Oy- /Zc:rvalueCast /Zc:strictStrings /volatile:iso /Zc:__cplusplus /permissive- /std:c++17 /Zc:inline /FIpch.hpp /Yupch.hpp /Fpbuild/pch.pch /Zi /Fdbuild\main.obj.pdb
main.cpp
main.cpp: error C2859: Z:\data\acm\src\example\build\main.obj.pdb is not the pdb file that was used when this precompiled header was created, recreate the precompiled header.

Это очень загадочная ошибка, потому что сообщение об ошибке предполагает, что main.obj.pdb каким-то образом обрабатывается как вход, но на самом деле это имя файла .pdb, сгенерированного как выход< /em>, согласно значению флага /Fd для сборки main.obj.

Поиск в Google не привел к большому количеству полезных указаний, с множеством подсказок о перенастройке параметров VS, что не очень полезно в случае сборки, управляемой чем-то другим.

Я также убедился, что мой обман с /Fi из pch.hpp не является проблемой. Если я обновлю main.cpp до #include pch.hpp и удалю /Fipch.hpp из строки компиляции для main.obj, все равно будет возникать та же ошибка C2859.

Я понимаю, что при использовании предварительно скомпилированных заголовков нужно правильно настроить множество флагов, среди них флаги /Yc, /Yu и /Fp, но я думаю, что обработал их правильно.

Кто-нибудь видит, где я ошибаюсь?

Правка 1. Демонстрация того, что /Fd может называть разные файлы PDB

В ответ на комментарий ниже, на самом деле не похоже, что все цели должны совместно использовать аргумент /Fd. Вот пример без использования предварительно скомпилированных заголовков, который демонстрирует, что:

Здесь мне нужно было добавить common.cpp и создать main1.cpp и main2.cpp, которые оба связывают его:

Заголовок common.hpp выглядит так:

#pragma once

int common(int arg);

С тривиальной реализацией в common.cpp:

#include "common.hpp"

int common(int arg) {
    return arg + 42;
}

Затем введите две разные основные линии:

main1.cpp:

#include "common.hpp"

int main() {
    std::vector<int> xs = { 1, 2, 3 };
    return common(xs.size());
}

И main2.cpp:

#include "common.hpp"

int main() {
    std::vector<int> xs = { 1, 2, 3, 4};
    return common(xs.size());
}

Затем скомпилируйте все три объектных файла. Обратите внимание, что у нас по-прежнему есть /Fipch.hpp, чтобы включения работали правильно, но мы не используем какой-либо реальный механизм PCH:

cl /Fobuild\common.obj /c common.cpp /TP /nologo /EHsc /errorReport:none /MD /O2 /Oy- /Zc:rvalueCast /Zc:strictStrings /volatile:iso /Zc:__cplusplus /permissive- /std:c++17 /Zc:inline /FIpch.hpp /Zi /Fdbuild\common.obj.pdb

cl /Fobuild\main1.obj /c main1.cpp /TP /nologo /EHsc /errorReport:none /MD /O2 /Oy- /Zc:rvalueCast /Zc:strictStrings /volatile:iso /Zc:__cplusplus /permissive- /std:c++17 /Zc:inline /FIpch.hpp /Zi /Fdbuild\main1.obj.pdb

cl /Fobuild\main2.obj /c main2.cpp /TP /nologo /EHsc /errorReport:none /MD /O2 /Oy- /Zc:rvalueCast /Zc:strictStrings /volatile:iso /Zc:__cplusplus /permissive- /std:c++17 /Zc:inline /FIpch.hpp /Zi /Fdbuild\main2.obj.pdb

И мы находим то, что и ожидали в каталоге build:

common.obj  common.obj.pdb  main1.obj  main1.obj.pdb  main2.obj  main2.obj.pdb

Тогда мы можем пойти дальше и связать:

link /nologo /DEBUG /INCREMENTAL:NO /LARGEADDRESSAWARE /OPT:REF /OUT:build\main1.exe /PDB:build\main1.pdb build\common.obj build\main1.obj

link /nologo /DEBUG /INCREMENTAL:NO /LARGEADDRESSAWARE /OPT:REF /OUT:build\main2.exe /PDB:build\main2.pdb build\common.obj build\main2.obj

И мы обнаруживаем, что получаем как исполняемые файлы, так и файлы PDB для этих исполняемых файлов в каталоге build:

common.obj      main1.exe  main1.obj.pdb  main2.exe  main2.obj.pdb
common.obj.pdb  main1.obj  main1.pdb      main2.obj  main2.pdb

person acm    schedule 01.04.2019    source источник
comment
Параметр /Fd должен быть одинаковым для всех файлов, которые вы компилируете и связываете вместе.   -  person Hans Passant    schedule 01.04.2019
comment
@HansPassant - я не думаю, что в общем случае это правда. Пожалуйста, смотрите мой подробный пример выше в Edit 1. По крайней мере, в случае, когда механизм PCH не используется, вы фактически можете создать другой файл .pdb для каждой TU с помощью разных аргументов для /Fd для каждого вызова cl, а затем связать объектные файлы вместе.   -  person acm    schedule 01.04.2019
comment
Какой, черт возьми, смысл пытаться найти способ, о котором компилятор не может сообщить? Это просто вызывает более серьезные проблемы, которые сложнее диагностировать, например, отсутствие символов в окончательной PDB, сгенерированной компоновщиком.   -  person Hans Passant    schedule 01.04.2019
comment
@HansPassant - Какой файл PDB следует назвать для компиляции common.obj при использовании /Fi, учитывая, что common.obj связан как с main1.exe, так и с main2.exe?   -  person acm    schedule 01.04.2019
comment
Используйте lib.exe для создания статической библиотеки, которую можно использовать для связывания кода с различными исполняемыми файлами. Компоновщик использует pdb, связанный с библиотекой, для извлечения символов. Встраивание символов в файл .obj по-прежнему возможно, вам нужно перевести часы на три десятилетия назад с помощью /Z7.   -  person Hans Passant    schedule 01.04.2019
comment
@HansPassant - это был сокращенный пример из гораздо более сложной системы сборки, предназначенный для выявления проблемы. Конечно, на самом деле мы используем lib.exe для создания архивного файла, не связывая аналог common.obj напрямую с несколькими исполняемыми файлами. Однако я не вижу возможности создать PDB для архивной библиотеки с lib.exe: docs.microsoft.com/en-us/cpp/build/reference/. Поэтому я не уверен, что вижу, как ваше предложение помогает: у нас все еще нет PDB, которую мы могли бы назвать с помощью /Fd при связывании как main1.exe, так и main2.exe.   -  person acm    schedule 02.04.2019
comment
Я спросил на сайте Microsoft и предоставил репродукции на github:   -  person acm    schedule 25.11.2019
comment
github.com/acmorrow/msvc-pch-vs- zi/blob/master/README.md   -  person acm    schedule 25.11.2019


Ответы (1)


Из документации Microsoft для C2859 и создание предварительно скомпилированных заголовков, база данных, используемая для хранения отладочной информации, должна совместно использоваться всеми исходными модулями, использующими предварительно скомпилированные заголовки. Хранение всей этой общей отладочной информации в одной базе данных значительно уменьшает размер объектных файлов и ускоряет компоновку, поскольку компоновщику не приходится иметь дело со всем этим повторением.

Если вы хотите, чтобы все ваши скомпилированные файлы имели собственные копии отладочной информации, используйте вместо этого параметр /Z7.

person 1201ProgramAlarm    schedule 01.04.2019
comment
Объяснение в документации для C2859 довольно непрозрачно; Я читал его, и он не привел меня к правильному выводу. Я также не нашел упоминания о файлах PDB на странице документации для предварительно скомпилированных заголовков. Не говорю, что вы не правы - я подозреваю, что вы правы. Ограничение приводит к некоторым странным ситуациям. См. Редактировать 1. Если бы мы попытались добавить поддержку PCH, как бы это работало. Мы не хотим, чтобы main1 и main2 совместно использовали файл PDB, поскольку каждая из них является собственной программой, но они оба связывают common.obj. Куда должна идти информация PDB для common.obj в сборке на основе /Fi? - person acm; 01.04.2019
comment
@acm Моя интерпретация документации звучит так, будто поддержка PCH требует, чтобы все файлы, использующие этот предварительно скомпилированный заголовок, использовали один и тот же файл PDB. - person 1201ProgramAlarm; 01.04.2019
comment
Да, меня смущает то, как это должно работать в более сложных сборках. Если у вас есть статические библиотеки, которые связаны более чем с одним исполняемым файлом, кажется, нет правильного ответа на вопрос, каким должен быть аргумент /Fd при сборке common.obj с предварительно скомпилированным заголовком. Я полагаю, что ответ заключается в том, что вы должны встроить common.obj в DLL, а не в статическую библиотеку. Но я также не понимаю, как это должно работать, если вы используете один и тот же PCH для нескольких DLL и исполняемых сборок. - person acm; 02.04.2019