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/i386x64/amd64、@987654328)构建debugrelease @, 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_TYPEgcc $INC -c $INPUT -o $OUTPUT。不是最有帮助的,但我的Makefile 非常冗长,并且与构建期间使用的include 路径相呼应,我可以确认标题通常是从该路径解析的,.gch 文件除外。 对不起,我的意思是它在执行时实际扩展的内容。 【参考方案1】:

这里是MVCE 解决您的问题 场景:

ma​​in.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 &lt;hw.h&gt; 将导致 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.gchpch.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 - 多个预编译头文件和特定路径的主要内容,如果未能解决你的问题,请参考以下文章

使用 GCC 预编译的 STL

编译知识

如何在不扩展包含的头文件的情况下预编译 C 源文件?

怎样添加预编译静态库 libwebrtc

编译过程学习

源代码到可执行程序的过程详解:预编译编译汇编链接