Gcc and Precompiled Headers

GCC and Precompiled Headers

Current GCC (i.e. 4.7) and previous versions of it works nicely with precompiled headers only when you have a single common header to your application, and when that single header (which includes in turn all the system ones, and the library specific ones, required by the application) is #include-d (as the first non-comment lexeme of your sources) by every source of your application.

So you should have a single yourapp.h and have every source file (i.e. every compilation unit) of yourapp starting with #include "yourapp.h" with the same preprocessing options (i.e. -D or -I or -U) on the command line. That youapp.h header file is usually #include-ing many others, e.g. system headers (or GTK or Qt ones) like <stdlib.h> or <sys/poll.h> or [in C++] <algorithm> or <gtk/gtk.h> or <QtGui> etc.

Recall that -H is a useful option to get gcc tell you what is included.

Your source files might have some additional #include after #include "yourapp.h" if so wanted.

After a [single] precompiled header has been included by GCC, you may of course #define macros, #include some non-precompiled header, do conditional compilation with #ifdef, etc. But that preprocessing won't be "pre-compiled"!

This may not fit your needs or habits.

Some people (notably from Google, notably Diego Novillo) are working on the PreParsed Header (pph) branch to improve the situation, but the current GCC trunk has not got that work yet.

The explanation about that behavior of GCC is that a preprocessed header is essentially a persistent serialized checkpoint of the entire GCC heap (related to memory management inside GCC thru Ggc and GTY and gengtype). That checkpointed heap can be loaded only when gcc is in its initial empty state. As soon as something is known to gcc (actually cc1 or cc1plus) it cannot load any more any precompiled header file *.h.gch and will revert to parsing the textual header file *.h.



addenda (november 2014 and later)

Even GCC 4.9 wants a single precompiled header. The pre-parsed header effort by Diego Novillo et al. has been abandoned.

Future versions (post C++14) of the C++ standard might define a module mechanism. See e.g. n4047 proposal and the C++20 standard.

(additional addenda, summer 2020) This still holds for GCC-10 where several static analyzer options exist. See also the Clang static analyzer and this draft report. Consider using Frama-C.

Precompiled headers not used by GCC when building with a makefile

You're not including the PCH anywhere in your make command. Try this:

CXX = g++
CXXFLAGS = -O2 -H -Wall -std=c++17
OBJ = main.o #more objects here eventually I would think!

PCH_SRC = lib.hpp
PCH_HEADERS = headersthataregoinginyourpch.hpp andanother.hpp
PCH_OUT = lib.hpp.gch

main: $(OBJ)
$(CXX) $(CXXFLAGS) -o $@ $^

# Compiles your PCH
$(PCH_OUT): $(PCH_SRC) $(PCH_HEADERS)
$(CXX) $(CXXFLAGS) -o $@ $<

# the -include flag instructs the compiler to act as if lib.hpp
# were the first header in every source file
%.o: %.cpp $(PCH_OUT)
$(CXX) $(CXXFLAGS) -include $(PCH_SRC) -c -o $@ $<

First the PCH gets compiled. Then all cpp commands get compiled with -include lib.hpp this guarantees that lib.hpp.gch will always be searched first before lib.hpp

Trying to set up a pre-compiled header

Run Powershell as an administrator and it should work fine - you gain all permissions you need.

Weird Behavior with gcc precompiled headers

With GCC, precompiled headers work only if they are the only header, and if they are included first (without any previous header).

This answer explains more why it is so.
See also the Precompiled headers chapter of GCC documentation, which says:

  • Only one precompiled header can be used in a particular compilation.
  • A precompiled header can't be used once the first C token is seen.

BTW, it could happen that pre-compiling some large header (notably in C++) is not worth the effort. YMMV.

GCC - Multiple precompiled headers and specific paths

Here is an MVCE for your problem
scenario:

main.c

#include <hw.h>
#include <stdio.h>

int main(void)
{
puts(HW);
return 0;
}

hw.h

#ifndef HW_H
#define HW_H
#define HW "Hello World"
#endif

Makefile

srcs := main.c
objs := $(addprefix tmp/,$(srcs:.c=.o))
pch := tmp/hw.h.gch
CPPFLAGS += -I.

.PHONY: all clean

all: hw

tmp:
mkdir -p tmp

tmp/%.o: %.c | $(pch)
gcc -c $(CPPFLAGS) -o $@ $<

$(pch): hw.h | tmp
gcc -c $(CPPFLAGS) -o $@ $<
ifdef ENFORCE_PCH
echo "#error Debug." >> $^
endif

hw: $(objs)
gcc -o $@ $^


clean:
sed -i '/^#error/d' hw.h
rm -fr hw tmp

This project outputs its intermediate files in tmp. The .o files go there
and so does the PCH hw.h.gch.

Build and run it:

$ make && ./hw
mkdir -p tmp
gcc -c -I. -o tmp/hw.h.gch hw.h
gcc -c -I. -o tmp/main.o main.c
gcc -o hw tmp/main.o
Hello World

So far so good. But did it actually use the PCH? Let's see:

$ make clean
sed -i '/^#error/d' hw.h
rm -fr hw tmp
$ make ENFORCE_PCH=true
mkdir -p tmp
gcc -c -I. -o tmp/hw.h.gch hw.h
echo "#error Debug." >> hw.h
gcc -c -I. -o tmp/main.o main.c
In file included from main.c:1:0:
./hw.h:5:2: error: #error Debug.
#error Debug.
^
Makefile:15: recipe for target 'tmp/main.o' failed
make: *** [tmp/main.o] Error 1

No it didn't. We know that because, with ENFORCE_PCH defined, we have
tacked an #error directive to the end of hw.h after generating the
good tmp/hw.h.gch. So if the former is subsequently #include-ed anywhere
instead of the latter, the build breaks. Which it just did.

And that is just as it should be. GCC manual 3.21 Using Precompiled Headers,
para. 3:

A precompiled header file is searched for when #include is seen in the compilation.
As it searches for the included file (see Search Path) the compiler looks for a
precompiled header in each directory just before it looks for the include file in
that directory. The name searched for is the name specified in the #include with ‘.gch’
appended. If the precompiled header file can't be used, it is ignored.

So, given include search path ., the directive #include <hw.h> will cause
gcc to check for the PCH ./hw.h.gch before using ./hw.h, and as there is
no ./hw.h.gch, it will use ./hw.h.

It might appear, from the documentation just quoted, that adding tmp to the include search path - CPPFLAGS += -Itmp -I. - should cause
tmp/hw.h.gch to be used in preference to./hw.h. But in fact it makes no difference.
The documentation omits a crucial qualification. The second sentence ought to read:

As it searches for the included file (see Search Path) the compiler looks for a
precompiled header in each directory just before it looks for the include file in
that directory and will use the precompiled header for preference if the include file
is found.

To be found and used, a PCH has to be a sibling of the matching header. And on consideration
this is what you'd want. Otherwise, a a/foo.h.gch without a matching sibling header might be
found and used thanks to -Ia when there is a b/foo.h.gch, with a matching b/foo.h, that
could be found and used thanks to a later -Ib. Clearly, the latter is the sounder choice.

With this insight it's not hard to see a solution: if you really want to compile and use
a PCH that's not a sibling of its source header, make sure to give it a phony matching header
that is a sibling. You can arrange this as you see fit, e.g.

Makefile (fixed)

srcs := main.c
objs := $(addprefix tmp/,$(srcs:.c=.o))
pch := tmp/hw.h.gch
# Seek headers in tmp first...
CPPFLAGS += -Itmp -I.

.PHONY: all clean

all: hw

tmp:
mkdir -p tmp

tmp/%.o: %.c | $(pch)
gcc -c $(CPPFLAGS) -o $@ $<

$(pch): hw.h | tmp
# Make phony header in tmp...
echo "#error You should not be here" > $(basename $@)
gcc -c $(CPPFLAGS) -o $@ $<
ifdef ENFORCE_PCH
echo "#error Debug." >> $^
endif

hw: $(objs)
gcc -o $@ $^

clean:
sed -i '/^#error/d' hw.h
rm -fr hw tmp

See that the PCH is used from tmp:

$ make clean
sed -i '/^#error/d' hw.h
rm -fr hw tmp
$ make ENFORCE_PCH=true && ./hw
mkdir -p tmp
# Make phony header in tmp...
echo "#error You should not be here" > tmp/hw.h
gcc -c -Itmp -I. -o tmp/hw.h.gch hw.h
echo "#error Debug." >> hw.h
gcc -c -Itmp -I. -o tmp/main.o main.c
gcc -o hw tmp/main.o
Hello World


Related Topics



Leave a reply



Submit