0

I have a project that doesn't currently use precompiled headers, but I'd like to make it do so, as I've demonstrated that it leads to real compilation speed improvements in the project.

I'd also like to use /Zi, so I can capitalize on the parallel build benefits associated with /Zf, which is implied by /Zi.

I'm using the VS2017 C++ compiler, but I'm using a build system that is not Visual Studio, so answers relating to configuring VS aren't helpful.

What I'm finding is that I can set up the build to use precompiled headers just fine, or I can set it up to use /Zi just fine, but I can't seem to form a proper series of invocations to do both. When I try to do what I think is correct, I end up with error C2958 stopping the build, and I don't see what I'm doing wrong.

I've built a toy project to demonstrate what I'm seeing. The pch.hpp header looks like this:

#pragma once
#include <vector>

And we make a short mainline in main.cpp:

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

Note that this is the complete file contents for main.cpp: I haven't omitted anything. I am intentionally not including pch.hpp here, because we are going to force inject it with /Fi later. The real project doesn't have include lines for a precompiled header in all the right places, and there would be thousands of files to update to do so. Note that the approach using /Fi does appear to work in the command lines below, and has the advantage that a forceincludes based mechanism can work with GCC style precompiled headers as well.

If we build things without /Zi, everything goes fine:

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

We find the files we would expect to find under the build directory:

main.obj  pch.obj  pch.pch

However, if we try to use /Fi and /Fd to generate a per-file .pdb and control its name, it doesn't work at all:

We can compile the precompiled header that way 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

And things look OK in the build directory so far:

pch.obj  pch.pch  pch.pch.pdb

But when we try to build the object file for main, it all falls apart:

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.

This is a very puzzling error, because the error message suggests that main.obj.pdb is being treated as an input somehow, but in fact it is intended to be the name of the .pdb file generated as output, per the value of the /Fd flag for the build of main.obj.

Googling hasn't resulted in much useful guidance, with a lot of hints about reconfiguring VS settings, which isn't really useful in the case of a build driven by something else.

I've also verified that my trickery with the /Fi of pch.hpp isn't the issue. If I update main.cpp to #include pch.hpp and remove the /Fipch.hpp from the compile line for main.obj, the same C2859 error still occurs.

I realize there are a lot of flags to get right when using precompiled headers, among them the /Yc and /Yu and /Fp flags, but I think I've got those handled correctly.

Does anyone see where I've got things wrong?

Edit 1 - Demonstrating that /Fd can name different PDB files

In response to the comment below, it doesn't actually appear to be the case that all targets must share the /Fd argument. Here is an example without the use of precompiled headers that demonstrates that:

Here, I've needed to add common.cpp and create a main1.cpp and main2.cpp which both link it:

The common.hpp header looks like:

#pragma once

int common(int arg);

With a trivial implementation in common.cpp:

#include "common.hpp"

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

Then introduce two different mainlines:

main1.cpp:

#include "common.hpp"

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

And main2.cpp:

#include "common.hpp"

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

Then compile all three object files. Note that we still have the /Fipch.hpp here to make the includes work right, but we aren't using any of the actual PCH machinery:

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

And we find what we would expect in the build directory:

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

Then we can go ahead and link:

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

And we find that we get both executables and PDB files for those executables in the build directory:

common.obj      main1.exe  main1.obj.pdb  main2.exe  main2.obj.pdb
common.obj.pdb  main1.obj  main1.pdb      main2.obj  main2.pdb
acm
  • 12,183
  • 5
  • 39
  • 68
  • The /Fd option must be the same for all files you compile and link together. – Hans Passant Apr 01 '19 at 06:31
  • @HansPassant - I don't think that is actually true in the general case. Please see my elaborated example above in Edit 1. At least in the case where the PCH machinery isn't in play, you can in fact create a different per-TU `.pdb` file via different arguments to `/Fd` for each `cl` invocation, and then link the object files together. – acm Apr 01 '19 at 14:45
  • What the heck is the point of trying to find a way that the compiler cannot report? It just causes bigger problems that are harder to diagnose, like missing symbols from the final PDB generated by the linker. – Hans Passant Apr 01 '19 at 14:49
  • @HansPassant - What PDB file should be named for the compilation of `common.obj` when `/Fi` is used, given that `common.obj` is linked into both `main1.exe` and `main2.exe`? – acm Apr 01 '19 at 15:00
  • Use lib.exe to create a static library that can be used to link code into different executable files. The linker uses the pdb associated with the library to dig up symbols. Embedding symbols in the .obj file is still possible, you have to set the clock back three decades with /Z7. – Hans Passant Apr 01 '19 at 15:16
  • @HansPassant - This was a reduced example from a much more complex build system, intended to identify the problem. Of course in reality we are using `lib.exe` to produce an archive file, not linking the analogue of `common.obj` directly into multiple executables. However, there is no option that I see to produce a PDB for an archive library with `lib.exe`: https://learn.microsoft.com/en-us/cpp/build/reference/running-lib?view=vs-2017. So I'm not sure I see how your suggestion helps: we still don't have a PDB we can name with `/Fd` when linking both `main1.exe` and `main2.exe`. – acm Apr 02 '19 at 18:06
  • I've asked on the microsoft site and provided repros on github: – acm Nov 25 '19 at 19:58
  • https://github.com/acmorrow/msvc-pch-vs-zi/blob/master/README.md – acm Nov 25 '19 at 19:58
  • https://developercommunity.visualstudio.com/content/problem/818709/apparent-pdb-conflict-between-pch-zi-and-static-li.html – acm Nov 25 '19 at 19:58

1 Answers1

1

From the Microsoft documentation for C2859 and creating precompiled headers, the database used to store the debug information must be shared among all source modules using the precompiled header. Having all that shared debug information stored in one database greatly reduces the size of the object files and speeds up linking since the linker does not have to deal with all that repetition.

If you want all your compiled files to have their own copies of the debug information, use the /Z7 option instead.

1201ProgramAlarm
  • 32,384
  • 7
  • 42
  • 56
  • The explanation in the docs for C2859 is pretty opaque; I had read it, and it didn't lead me to the right conclusion. I also find no mention of PDB files on the documentation page for precompiled headers. Not saying you are wrong - I suspect that you are correct. The restriction leads to some odd situations. Please see Edit 1. There, if we were to try to add PCH support, how would it work. We don't want `main1` and `main2` to share a PDB file since each is its own program, but they both link `common.obj`. Where should the PDB information for `common.obj` go in an `/Fi` based build? – acm Apr 01 '19 at 14:52
  • @acm My interpretation of the documentation sounds like the PCH support requires all files using that precompiled header to use the same PDB file. – 1201ProgramAlarm Apr 01 '19 at 15:10
  • Yeah, what I'm confused about is how this is supposed to work in more complex builds. If you have static libraries that get linked into more than one executable, there appears to be no correct answer for what the `/Fd` argument should be when building `common.obj` with a pre-compiled header. I suppose the answer is that you should build `common.obj` into a DLL instead of a static library. But I'm also confused about how this should work if you share the same PCH across multiple DLL and executable builds. – acm Apr 02 '19 at 18:13
  • I think cmake copies the .pdb file to be able to kind of "reuse" it downstream – Johan Boulé Aug 29 '22 at 18:42