As far as I understand it's also one of the reasons modules are a thing... or at least people want them to be.
Precompiled headers are a pretty ugly solution and the way they've been implemented in the past could be really nasty. (IIRC in old GCC versions it would copy some internal state to disk, then later load it from disk and manually adjust pointers!)
There must still be some dark pointer magic going on, because I noticed that unless I disabled ASLR on Debian Stretch, each build of a precompiled header came out different, screwing up ccache. I can only conclude that the specific memory layout during an individual run influences the specific precompiled header (".gch") output. We now run our build process under 'setarch x86_64 --addr-no-randomize.
This isn't uncommon, especially for file formats which are meant for internal consumption. Of course, they end up being huge cans of worms in terms of security, stability and maintainability going forward.
Basically, instead of defining a real serialization format (and thus having to write serializer/deserializer code), it's way easier to just `fwrite` out your internal structs to disk, one after another, and write some much simpler walker code to walk through any pointed fields appropriately. At some point though this becomes technical debt which needs to be repaid in the form of a total serialization rewrite.
Blender, the popular open-source 3D modelling tool, uses a format like this for their .blend files, and it is really gross. IIRC a few releases back they started working to improve the format to be a little less dependent on low-level internal details, but now they have the nightmare of backwards compatibility to deal with.
The basic problem is that C/C++ have no mechanism for native serialization, unlike e.g. Java, Python, or any number of other languages, so you're either stuck `fwrite`ing structs or reinventing the wheel.
Which is mostly due to the lack of run-time reflection. OTOH, with a little creativity its possible to create code generators to attach a commonly named (say .serialize method) to classes to dump their POD fields, and call serialize on directly encapsulated classes.
But your basically right, everyone ends up doing it their own way which just ends up being a PITA.
IME, using precompiled headers with gcc is largely a waste of time. I desperately wanted it to be otherwise. I tried many variants: including everything, nothing, a few select, commonly used headers. No matter what I tried, nothing was faster than no PCH. This is a project that has ~90k LOC in 136 object files and compiles in about 2 minutes on 64 cores.
Yes, I was measuring time to rebuild everything (including the PCH) from scratch. So it's probable that incremental compilation is slightly faster using PCH, it's just not nearly as much as I was hoping for.
It is, but precompiled headers are a pain in the a… (often less so than not using them, but still)
They force programmers to tell the compiler what intermediate result to cache. Finding the best intermediate result to cache is a black art, and that set will change when your source code grows, forcing you to either accept that your precompiled headers may not help that much, or to spend large amounts of time optimizing build times.
There's no need for black arts for most. For most code out there, the headers that really blow up compilation times come from either the standard library, or from large third party libraries. A simple rule of thumb, therefore, is to simply shove all such headers into your precompiled header, and only ever #include the latter in your code.
Simply put - if it's #include <...>, it goes into the precompiled header. Otherwise, it goes directly into the source.
The downside of this is that every time you add a new dependency, the entire project is rebuilt, since the change in your precompiled header affects all translation units. But adding dependencies is rare, and changing code and rebuilding is far more common.
Precompiled headers are a pretty ugly solution and the way they've been implemented in the past could be really nasty. (IIRC in old GCC versions it would copy some internal state to disk, then later load it from disk and manually adjust pointers!)