以下是您问题场景的 MVCE:
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 '/^
rm -fr hw tmp
这个项目会在 tmp
文件夹中输出它的中间文件。 .o
文件和 PCH hw.h.gch
文件都会存放在此处。
构建并运行:
$ 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
目前为止还不错。但是它实际上使用了PCH吗?让我们看一下:
$ 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:
^
Makefile:15: recipe for target 'tmp/main.o' failed
make: *** [tmp/main.o] Error 1
没有。我们知道这是因为,在定义了
ENFORCE_PCH
后,我们已经在生成好的
tmp/hw.h.gch
之后,在
hw.h
的末尾添加了一个
#error
指令。因此,如果以后任何地方使用前者而不是后者进行
#include
,则会导致构建失败。事实就是如此。
这正是应该的。GCC手册
3.21 使用预编译头文件,第3段:
预编译头文件在编译时搜索 #include 时被查找。随着它搜索包含的文件(请参见搜索路径),编译器在每个目录中寻找可执行文件。按顺序查找预编译头文件。搜索的名称是附加了“.gch”的#include中指定的名称。如果不能使用预编译头文件,则忽略它。
因此,给定包含搜索路径“.”,指令“#include <hw.h>”将导致
gcc
在使用
./hw.h
之前检查PCH
./hw.h.gch
,因为没有
./hw.h.gch
,所以它会使用
./hw.h
。
从刚才引用的文档来看,似乎将
tmp
添加到包含搜索路径 -
CPPFLAGS += -Itmp -I.
- 应该导致使用
tmp/hw.h.gch
而不是
./hw.h
。但实际上这没有任何影响。文档忽略了一个重要的限定条件。第二个句子应该写成:
随着它搜索包含的文件(请参见搜索路径),编译器在每个目录中寻找可执行文件。按顺序查找预编译头文件。如果找到include文件,则首选使用预编译头文件。
要找到并使用PCH,必须使PCH成为相应头文件的同级目录。考虑到这一点,这正是你想要的。否则,在使用
-Ia
时,可能会找到并使用没有匹配的兄弟头文件的
a/foo.h.gch
,而当有一个匹配的
b/foo.h
然后可以通过稍后的
-Ib
找到和使用
b/foo.h.gch
。显然,后者是更好的选择。
有了这个洞察力,解决方案就不难想到:如果你真的想编译并使用一个不是源头文件的 PCH,一定要给它一个虚假的匹配头文件,而这个头文件是与源头文件在同一级别的。你可以根据自己的需要进行安排,例如:
Makefile(已修复)
srcs := main.c
objs := $(addprefix tmp/,$(srcs:.c=.o))
pch := tmp/hw.h.gch
CPPFLAGS += -Itmp -I.
.PHONY: all clean
all: hw
tmp:
mkdir -p tmp
tmp/%.o: %.c | $(pch)
gcc -c $(CPPFLAGS) -o $@ $<
$(pch): hw.h | 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 '/^
rm -fr hw tmp
请注意 PCH 从 tmp
中使用:
$ make clean
sed -i '/^#error/d' hw.h
rm -fr hw tmp
$ make ENFORCE_PCH=true && ./hw
mkdir -p 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
gch
文件吗? - user657267INC += .tmp/${OS_TYPE}/${CPU_TYPE}/${REL_TYPE}
。gcc ${INC} -c ${INPUT} -o ${OUTPUT}
。虽然不是最有帮助的,但我的Makefile
非常冗长,并在构建过程中回显所使用的include
路径,我可以确认头文件通常从该路径解析,但.gch
文件除外。 - Cloud