C++ 结合PCH、PDB和Zi会导致令人费解的C2859编译错误与VS2017
我有一个项目目前不使用预编译头,但我想让它使用预编译头,因为我已经证明了它可以提高项目的实际编译速度 我还想使用C++ 结合PCH、PDB和Zi会导致令人费解的C2859编译错误与VS2017,c++,windows,visual-c++,visual-studio-2017,precompiled-headers,C++,Windows,Visual C++,Visual Studio 2017,Precompiled Headers,我有一个项目目前不使用预编译头,但我想让它使用预编译头,因为我已经证明了它可以提高项目的实际编译速度 我还想使用/Zi,因此我可以利用与/Zf相关的并行构建优势,这是/Zi所暗示的 我使用VS2017 C++编译器,但我使用的不是VisualStudio的构建系统,所以与配置VS有关的答案没有帮助。p> 我发现,我可以将构建设置为使用预编译头,也可以将其设置为使用/Zi很好,但我似乎无法形成一系列适当的调用来同时执行这两个操作。当我尝试做我认为正确的事情时,我最终会出现错误C2958停止构建,我
/Zi
,因此我可以利用与/Zf
相关的并行构建优势,这是/Zi
所暗示的
我使用VS2017 C++编译器,但我使用的不是VisualStudio的构建系统,所以与配置VS有关的答案没有帮助。p>
我发现,我可以将构建设置为使用预编译头,也可以将其设置为使用/Zi
很好,但我似乎无法形成一系列适当的调用来同时执行这两个操作。当我尝试做我认为正确的事情时,我最终会出现错误C2958
停止构建,我看不出我做错了什么
我建立了一个玩具项目来展示我所看到的。pch.hpp
标题如下所示:
#pragma once
#include <vector>
请注意,这是main.cpp
的完整文件内容:我没有遗漏任何内容。我有意不在这里包括pch.hpp
,因为我们稍后将强制使用/Fi
注入它。真正的项目没有在所有正确的位置包含预编译头的include行,因此需要更新数千个文件。请注意,使用/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
我们可以在build目录下找到我们希望找到的文件:
main.obj pch.obj pch.pch
common.obj common.obj.pdb main1.obj main1.obj.pdb main2.obj main2.obj.pdb
common.obj main1.exe main1.obj.pdb main2.exe main2.obj.pdb
common.obj.pdb main1.obj main1.pdb main2.obj main2.pdb
但是,如果我们尝试使用/Fi
和/Fd
来生成每个文件.pdb
并控制其名称,则它根本不起作用:
我们可以这样编译预编译头OK:
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
以某种方式被视为一个输入,但事实上,根据构建main.obj
的/Fd
标志的值,它是作为输出生成的.pdb
文件的名称
谷歌搜索并没有带来太多有用的指导,其中有很多关于重新配置VS设置的提示,这在由其他东西驱动的构建中并不真正有用
我还验证了我对pch.hpp
的/Fi
的欺骗不是问题所在。如果我将main.cpp
更新为#include pch.hpp
并从main.obj
的编译行中删除/Fipch.hpp
,则仍然会发生相同的C2859
错误
我意识到在使用预编译头时有很多标志需要正确处理,其中包括/Yc
和/Yu
和/Fp
标志,但我认为我已经正确处理了这些标志
有人知道我哪里做错了吗
编辑1-演示/Fd可以命名不同的PDB文件
作为对下面评论的回应,实际上似乎并非所有目标都必须共享/Fd
参数。下面是一个不使用预编译头的示例,该示例演示了:
在这里,我需要添加common.cpp
并创建一个main 1.cpp
和main2.cpp
两个链接:
common.hpp
标题如下所示:
#pragma once
int common(int arg);
使用common.cpp
中的一个简单实现:
int main() {
std::vector<int> xs = { 1, 2, 3 };
return xs.size();
}
#include "common.hpp"
int common(int arg) {
return arg + 42;
}
#include "common.hpp"
int main() {
std::vector<int> xs = { 1, 2, 3 };
return common(xs.size());
}
#include "common.hpp"
int main() {
std::vector<int> xs = { 1, 2, 3, 4};
return common(xs.size());
}
然后介绍两条不同的主线:
main1.cpp
:
int main() {
std::vector<int> xs = { 1, 2, 3 };
return xs.size();
}
#include "common.hpp"
int common(int arg) {
return arg + 42;
}
#include "common.hpp"
int main() {
std::vector<int> xs = { 1, 2, 3 };
return common(xs.size());
}
#include "common.hpp"
int main() {
std::vector<int> xs = { 1, 2, 3, 4};
return common(xs.size());
}
然后编译所有三个对象文件。请注意,我们这里仍然有/Fipch.hpp
,以使includes正常工作,但我们没有使用任何实际的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
目录中找到我们所期望的:
main.obj pch.obj pch.pch
common.obj common.obj.pdb main1.obj main1.obj.pdb main2.obj main2.obj.pdb
common.obj main1.exe main1.obj.pdb main2.exe main2.obj.pdb
common.obj.pdb main1.obj main1.pdb main2.obj main2.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
我们发现,在build
目录中,我们得到了这些可执行文件的可执行文件和PDB文件:
main.obj pch.obj pch.pch
common.obj common.obj.pdb main1.obj main1.obj.pdb main2.obj main2.obj.pdb
common.obj main1.exe main1.obj.pdb main2.exe main2.obj.pdb
common.obj.pdb main1.obj main1.pdb main2.obj main2.pdb
根据和的Microsoft文档,用于存储调试信息的数据库必须使用预编译头在所有源模块之间共享。将所有共享的调试信息存储在一个数据库中,可以大大减少对象文件的大小并加快链接速度,因为链接器不必处理所有这些重复
如果您希望所有编译的文件都有自己的调试信息副本,请改用
/Z7
选项。对于所有编译并链接在一起的文件,/Fd选项必须相同。@HansPassant-我认为在一般情况下实际上不是这样。请参见上面编辑1中详细阐述的示例。至少在PCH机制不起作用的情况下,您实际上可以为每个cl
调用通过/Fd
的不同参数创建不同的per TU.pdb
文件,然后将对象文件链接在一起。试图找到编译器无法报告的方法有什么意义?它只会导致更难诊断的更大问题,例如链接器生成的最终PDB中缺少符号。@HansPassant-使用/Fi
时,编译common.obj
应该命名什么PDB文件,鉴于common.obj
链接到main1.exe
和main2.exe
,请使用lib.exe创建一个静态库,该库可用于将代码链接到不同的可执行文件中。链接器使用与库关联的pdb来挖掘符号。在.obj文件中嵌入符号仍然是可能的,您必须使用/Z7将时钟向后拨三十年