GCC - 多个预编译头文件和特定路径
Posted
技术标签:
【中文标题】GCC - 多个预编译头文件和特定路径【英文标题】:GCC - Multiple precompiled headers and specific paths 【发布时间】:2016-05-13 16:52:22 【问题描述】:背景
我正在处理一个大型的Makefile
项目,我想整理一下。它构建了几十个子项目,每个子项目大约包含 100 个.cpp
和.h
文件。我已经对其进行了设置,使其能够为多种操作系统(Linux、OSX/Mac、QNX 等)和多种架构(x86/i386
、x64/amd64
、@987654328)构建debug
和release
@, arm64/aarch64
) 并行。这是因为它是一个庞大的项目,而使其快速构建的唯一方法是与多个工具链并行。
我有一个主要规则,即所有项目都遵守在构建时将中间对象(即:.o
文件)存储在临时目录中。因此,为 Linux、arm64、发布模式构建 test.c
;将在当前工作目录的以下子目录中构建目标文件:
.tmp/Linux/arm64/release
问题
此功能在我的构建中没有问题,但通过此设置,我似乎无法正确使用 GCC 的预编译头文件(即:.GCH
文件)。通过我的设置,我有一对 stdafx.h
/stdafx.cpp
。使用 GCC,我可以很容易地创建一个 stdafx.h.gch
文件。但是,如果文件与源文件位于同一路径中,该项目似乎只使用它(这会加速构建)。如果预编译的标头位于中间对象路径(即:.tmp/Linux/arm64/release
),则不会被检测或使用。即使我将包含路径显式添加到包含gch
文件的中间对象路径,它也会失败。包含文件名本身的完整路径会导致它被视为无效的链接描述文件,并被忽略。
因此,我的第一个解决方法是制定一个规则,强制所有 OS/arch 构建等待初始预编译头生成,而不是基于每个 OS/arch 构建 gch
。但是,如果我使用发布模式设置构建 gch
并尝试对 make
进行调试构建,则会收到以下警告:
warning: stdafx.h.gch: created with -gnone, but used with -gdwarf-2
首先,我不知道这是否会对我的构建产生严重后果,其次,不同的操作系统可能会为 gch
一代传递不同的编译时间定义标志,因此这不是“一刀切” " 用例,据我所知。
问题
如何解决这个问题,以便预编译的标头位于$PWD
以外的位置,并且可以被 GCC 检测到?我目前正在使用 gcc v5.3.1。
谢谢。
【问题讨论】:
您能否发布您尝试让 GCC 看到位于不同文件夹中的gch
的编译行之一?
@user657267 当然:INC += .tmp/$OS_TYPE/$CPU_TYPE/$REL_TYPE
。 gcc $INC -c $INPUT -o $OUTPUT
。不是最有帮助的,但我的Makefile
非常冗长,并且与构建期间使用的include
路径相呼应,我可以确认标题通常是从该路径解析的,.gch
文件除外。
对不起,我的意思是它在执行时实际扩展的内容。
【参考方案1】:
这里是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
生成文件
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
此项目在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: #error Debug.
#error Debug.
^
Makefile:15: recipe for target 'tmp/main.o' failed
make: *** [tmp/main.o] Error 1
不,它没有。我们知道,因为定义了ENFORCE_PCH
,我们有
将#error
指令添加到hw.h
的末尾在 生成
好tmp/hw.h.gch
。所以如果前者随后是#include
-ed 在任何地方
而不是后者,构建中断。它刚刚做了。
这是应该的。 GCC 手册3.21 Using Precompiled Headers, 段3:
当编译中出现#include 时,将搜索预编译的头文件。 当它搜索包含的文件(参见搜索路径)时,编译器会寻找一个 在每个目录中查找包含文件之前的预编译头文件 那个目录。搜索的名称是在#include 中使用“.gch”指定的名称 附加。如果预编译的头文件不能使用,则忽略。
因此,给定包含搜索路径.
,指令#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
使用。但实际上并没有什么区别。
文档省略了一个关键的限定条件。第二句应改为:
当它搜索包含的文件时(参见搜索路径),编译器会寻找一个 在每个目录中查找包含文件之前的预编译头文件 该目录 如果包含文件,则将使用预编译头作为首选项 找到了。
要被发现和使用,PCH 必须是匹配标头的兄弟。并在考虑
这就是你想要的。否则,没有匹配同级标题的 a/foo.h.gch
可能是
感谢-Ia
找到并使用,当有一个b/foo.h.gch
,有一个匹配的b/foo.h
,那
感谢后来的-Ib
,可以找到并使用它。显然,后者是更合理的选择。
有了这种见解,不难找到解决方案:如果你真的想编译和使用 一个不是其源头同级的 PCH,确保给它一个 phony 匹配的头 是兄弟姐妹。您可以按照您认为合适的方式进行安排,例如
Makefile(固定)
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
看到PCH是从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
【讨论】:
【参考方案2】:如果您强制编译器使用-include tmp/pch.h
或-include-pch tmp/pch.h.gch
,pch.h
中的保护块将阻止它再次被包含。
# Makefile
SOURCES := main.cpp
OBJECTS := $(SOURCES:%.cpp=tmp/%.o)
PCH_H := tmp/pch.h
PCH := $(PCH_H).gch
$(PCH) : *.h
$(COMPILE.cpp) -x c++-header src/pch.h -o $@
$(OBJECTS) : tmp/%.o : src/%.cpp $(PCH)
$(COMPILE.cpp) -include $(PCH_H) $< -o $@
注意事项:
gcc 搜索名为 pch.h.gch
的预编译头文件
clang 搜索名为 pch.h.pch
或 .gch
的预编译头文件
clang 需要-include pch.h
或-include-pch pch.h.pch
gcc 总是搜索预编译的头文件,不支持-include-pch
【讨论】:
以上是关于GCC - 多个预编译头文件和特定路径的主要内容,如果未能解决你的问题,请参考以下文章