如何仅在本地标头上运行预处理器
How do I run the preprocessor on local headers only?
我希望预处理器读取本地标头的包含,但忽略系统标头的包含。 换句话说,我如何让预处理器跳过形式的预处理指令:
#include <h-char-sequence> new-line
但仍处理以下形式的指令:
#include "q-char-sequence" new-line
作为代码示例,请观察以下文件:
#include <iostream> //system
#include "class_a.hpp" //local
#include <string> //system
#include "class_b.hpp" //local
int main() {}
如何使预处理器的输出为:
#include <iostream>
class A{};
#include <string>
class B{};
int main() {}
本地包含文件可能包含其他本地包含文件,预处理器会递归地将它们全部引入;就像通常一样。 它仍然会打印所有系统文件头,但不会引入它们的内容。
在 GCC 上,到目前为止,我的调用如下所示:g++ -E -P main.cpp
,其中-E
在预处理后停止,-P
排除了行标记的生成。
我似乎找不到排除系统标头处理的标志。
你愿意付出多少努力? 有一种令人讨厌的晦涩方法可以做到这一点,但它需要您设置一个虚拟目录来保存系统标头的代理项。 OTOH,它不需要对您的任何源代码进行任何更改。 同样的技术同样适用于 C 代码。
设置
文件:
./class_a.hpp
./class_b.hpp
./example.cpp
./system-headers/iostream
./system-headers/string
"系统标头"(例如./system-headers/iostream
包含一行(该行上没有#
!
include <iostream>
每个类标头都包含一行,如下所示:
class A{};
example.cpp
的内容是您在问题中显示的内容:
#include <iostream> //system
#include "class_a.hpp" //local
#include <string> //system
#include "class_b.hpp" //local
int main() {}
运行 C 预处理器
像这样运行 C 预处理器会产生如下所示的输出:
$ cpp -Dinclude=#include -I. -Isystem-headers example.cpp
# 1 "example.cpp"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "example.cpp"
# 1 "system-headers/iostream" 1
#include <iostream>
# 2 "example.cpp" 2
# 1 "class_a.hpp" 1
class A{};
# 3 "example.cpp" 2
# 1 "system-headers/string" 1
#include <string>
# 4 "example.cpp" 2
# 1 "class_b.hpp" 1
class B{};
# 5 "example.cpp" 2
int main() {}
$
如果消除# n
行,则输出为:
$ cpp -Dinclude=#include -I. -Isystem-headers example.cpp | grep -v '^# [0-9]'
#include <iostream>
class A{};
#include <string>
class B{};
int main() {}
$
其中,在包含#include
的行的开头给予或占用空格,就是您想要的。
分析
-Dinclude=#include
参数等效于#define include #include
。 当预处理器从宏生成输出时,即使它看起来像指令(如#include
),它也不是预处理器指令。 引用 C++11 标准 ISO/IEC 14882:2011(并不是说 AFAIK 版本之间发生了变化——而且是 C11 标准中的内容,ISO/IEC 9899:2011 也在 §6.10.3 中):
§16.3 宏替换
¶8 如果一个
#
预处理标记,后跟一个标识符,在预处理指令可以开始的时候以词法方式出现,则该标识符不受宏替换的影响。§16.3.4 重新扫描和进一步更换
¶2 如果在扫描替换列表期间找到被替换的宏的名称(不包括源文件的其余预处理令牌),则不会替换它。
¶3 生成的完全宏替换的预处理令牌序列不会作为预处理指令进行处理,即使它类似于一个,...
当预处理器遇到#include <iostream>
时,它会在当前目录中查找并且找不到任何文件,然后在./system-headers
中查找并找到文件iostream
以便将其处理到输出中。 它包含一行,include <iostream>
。 由于include
是一个宏,因此它被扩展(到#include
),但阻止了进一步的扩展,并且由于§16.3.4¶3,#
不作为指令处理。 因此,输出包含#include <iostream>
.
当预处理器遇到#include "class_a.hpp"
时,它会查找当前目录并找到文件并将其内容包含在输出中。
冲洗并重复其他标题。 如果class_a.hpp
包含#include <iostream>
,那么最终会再次扩展到#include <iostream>
(具有前导空间)。 如果您的system-headers
目录缺少任何标头,则预处理器将在正常位置进行搜索并查找并包含该标头。 如果您使用编译器而不是直接cpp
,则可以禁止它在带有-nostdinc
的系统目录中查找 - 因此,如果缺少(代理项)系统标头,预处理器将生成错误system-headers
。
$ g++ -E -nostdinc -Dinclude=#include -I. -Isystem-headers example.cpp | grep -v '^# [0-9]'
#include <iostream>
class A{};
#include <string>
class B{};
int main() {}
$
请注意,生成代理系统标头非常容易:
for header in algorithm chrono iostream string …
do echo "include <$header>" > system-headers/$header
done
JFTR,测试是在带有GCC 6.1.0的Mac OS X 10.11.5上完成的。 如果你使用的是GCC(GNU编译器集合,带有领先的示例编译器gcc
和g++
),你的里程应该不会与任何合理的替代版本有太大差异。
如果您不习惯使用宏名称include
,可以将其更改为适合您的任何其他名称 -syzygy
、apoplexy
、nadir
、reinclude
、...,并将代理项标头更改为使用该名称,并在预处理器(编译器)命令行上定义该名称。include
的一个优点是,您不太可能使用它作为宏名称。
自动生成代理项标头
OSGX问:
我们如何自动生成模拟系统标头?
有多种选择。一种是分析您的代码(例如使用grep
)以查找被引用或可能被引用的名称并生成适当的代理项标头。 生成一些未使用的标头并不重要 - 它们不会影响流程。请注意,如果使用#include <sys/wait.h>
,则必须./system-headers/sys/wait.h
代理项;这稍微使显示的 shell 代码复杂化,但不是很多。另一种方法是查看系统头目录中的头像(/usr/include
、/usr/local/include
等),并为在那里找到的头生成代理项。 例如,mksurrogates.sh
可能是:
#!/bin/sh
sysdir="./system-headers"
for header in "$@"
do
mkdir -p "$sysdir/$(dirname $header)"
echo "include <$header>" > "$sysdir/$header"
done
我们可以编写listsyshdrs.sh
来查找命名目录下的源代码中引用的系统标头:
#!/bin/sh
grep -h -e '^[[:space:]]*#[[:space:]]*include[[:space:]]*<[^>]*>' -r "${@:-.}" |
sed 's/^[[:space:]]*#[[:space:]]*include[[:space:]]*<([^>]*)>.*/1/' |
sort -u
添加了一些格式,当我扫描源代码树并回答 SO 问题时,它会生成这样的标题列表:
algorithm arpa/inet.h assert.h cassert
chrono cmath cstddef cstdint
cstdlib cstring ctime ctype.h
dirent.h errno.h fcntl.h float.h
getopt.h inttypes.h iomanip iostream
limits.h locale.h map math.h
memory.h netdb.h netinet/in.h pthread.h
semaphore.h signal.h sstream stdarg.h
stdbool.h stddef.h stdint.h stdio.h
stdlib.h string string.h sys/ipc.h
sys/mman.h sys/param.h sys/ptrace.h sys/select.h
sys/sem.h sys/shm.h sys/socket.h sys/stat.h
sys/time.h sys/timeb.h sys/times.h sys/types.h
sys/wait.h termios.h time.h unistd.h
utility vector wchar.h
因此,要为当前目录下的源树生成代理项:
$ sh mksurrogatehdr.sh $(sh listsyshdrs.sh)
$ ls -lR system-headers
total 344
-rw-r--r-- 1 jleffler staff 20 Jul 2 17:27 algorithm
drwxr-xr-x 3 jleffler staff 102 Jul 2 17:27 arpa
-rw-r--r-- 1 jleffler staff 19 Jul 2 17:27 assert.h
-rw-r--r-- 1 jleffler staff 18 Jul 2 17:27 cassert
-rw-r--r-- 1 jleffler staff 17 Jul 2 17:27 chrono
-rw-r--r-- 1 jleffler staff 16 Jul 2 17:27 cmath
-rw-r--r-- 1 jleffler staff 18 Jul 2 17:27 cstddef
-rw-r--r-- 1 jleffler staff 18 Jul 2 17:27 cstdint
-rw-r--r-- 1 jleffler staff 18 Jul 2 17:27 cstdlib
-rw-r--r-- 1 jleffler staff 18 Jul 2 17:27 cstring
-rw-r--r-- 1 jleffler staff 16 Jul 2 17:27 ctime
-rw-r--r-- 1 jleffler staff 18 Jul 2 17:27 ctype.h
-rw-r--r-- 1 jleffler staff 19 Jul 2 17:27 dirent.h
-rw-r--r-- 1 jleffler staff 18 Jul 2 17:27 errno.h
-rw-r--r-- 1 jleffler staff 18 Jul 2 17:27 fcntl.h
-rw-r--r-- 1 jleffler staff 18 Jul 2 17:27 float.h
-rw-r--r-- 1 jleffler staff 19 Jul 2 17:27 getopt.h
-rw-r--r-- 1 jleffler staff 21 Jul 2 17:27 inttypes.h
-rw-r--r-- 1 jleffler staff 18 Jul 2 17:27 iomanip
-rw-r--r-- 1 jleffler staff 19 Jul 2 17:27 iostream
-rw-r--r-- 1 jleffler staff 19 Jul 2 17:27 limits.h
-rw-r--r-- 1 jleffler staff 19 Jul 2 17:27 locale.h
-rw-r--r-- 1 jleffler staff 14 Jul 2 17:27 map
-rw-r--r-- 1 jleffler staff 17 Jul 2 17:27 math.h
-rw-r--r-- 1 jleffler staff 19 Jul 2 17:27 memory.h
-rw-r--r-- 1 jleffler staff 18 Jul 2 17:27 netdb.h
drwxr-xr-x 3 jleffler staff 102 Jul 2 17:27 netinet
-rw-r--r-- 1 jleffler staff 20 Jul 2 17:27 pthread.h
-rw-r--r-- 1 jleffler staff 22 Jul 2 17:27 semaphore.h
-rw-r--r-- 1 jleffler staff 19 Jul 2 17:27 signal.h
-rw-r--r-- 1 jleffler staff 18 Jul 2 17:27 sstream
-rw-r--r-- 1 jleffler staff 19 Jul 2 17:27 stdarg.h
-rw-r--r-- 1 jleffler staff 20 Jul 2 17:27 stdbool.h
-rw-r--r-- 1 jleffler staff 19 Jul 2 17:27 stddef.h
-rw-r--r-- 1 jleffler staff 19 Jul 2 17:27 stdint.h
-rw-r--r-- 1 jleffler staff 18 Jul 2 17:27 stdio.h
-rw-r--r-- 1 jleffler staff 19 Jul 2 17:27 stdlib.h
-rw-r--r-- 1 jleffler staff 17 Jul 2 17:27 string
-rw-r--r-- 1 jleffler staff 19 Jul 2 17:27 string.h
drwxr-xr-x 16 jleffler staff 544 Jul 2 17:27 sys
-rw-r--r-- 1 jleffler staff 20 Jul 2 17:27 termios.h
-rw-r--r-- 1 jleffler staff 17 Jul 2 17:27 time.h
-rw-r--r-- 1 jleffler staff 19 Jul 2 17:27 unistd.h
-rw-r--r-- 1 jleffler staff 18 Jul 2 17:27 utility
-rw-r--r-- 1 jleffler staff 17 Jul 2 17:27 vector
-rw-r--r-- 1 jleffler staff 18 Jul 2 17:27 wchar.h
system-headers/arpa:
total 8
-rw-r--r-- 1 jleffler staff 22 Jul 2 17:27 inet.h
system-headers/netinet:
total 8
-rw-r--r-- 1 jleffler staff 23 Jul 2 17:27 in.h
system-headers/sys:
total 112
-rw-r--r-- 1 jleffler staff 20 Jul 2 17:27 ipc.h
-rw-r--r-- 1 jleffler staff 21 Jul 2 17:27 mman.h
-rw-r--r-- 1 jleffler staff 22 Jul 2 17:27 param.h
-rw-r--r-- 1 jleffler staff 23 Jul 2 17:27 ptrace.h
-rw-r--r-- 1 jleffler staff 23 Jul 2 17:27 select.h
-rw-r--r-- 1 jleffler staff 20 Jul 2 17:27 sem.h
-rw-r--r-- 1 jleffler staff 20 Jul 2 17:27 shm.h
-rw-r--r-- 1 jleffler staff 23 Jul 2 17:27 socket.h
-rw-r--r-- 1 jleffler staff 21 Jul 2 17:27 stat.h
-rw-r--r-- 1 jleffler staff 21 Jul 2 17:27 time.h
-rw-r--r-- 1 jleffler staff 22 Jul 2 17:27 timeb.h
-rw-r--r-- 1 jleffler staff 22 Jul 2 17:27 times.h
-rw-r--r-- 1 jleffler staff 22 Jul 2 17:27 types.h
-rw-r--r-- 1 jleffler staff 21 Jul 2 17:27 wait.h
$
这假设头文件名不包含空格,这并非不合理 - 这将是一个勇敢的程序员,他创建了带有空格或其他棘手字符的头文件名。
mksurrogates.sh
的完整生产就绪版本将接受指定代理项标头目录的参数。
使用 clang 您可以执行以下操作:
clang -Imyinclude -P -E -nostdinc -nobuiltininc main.cpp
似乎没有办法保留系统#include
它找不到的行。
这不适用于 gcc,因为它的预处理器在使用-nostdinc
时将停止,并且找不到#included
头文件。
您可以使用临时包含的注释保护系统包含,将注释保留在预处理器输出 (-CC
) 中,然后再次删除保护程序。
像这样:
sed -i 's%#include <%//PROTECTED #include <%g' $(find . -name '*.[hc]pp')
g++ -E -P -CC main.cpp -o new_main.cpp
sed -i 's%//PROTECTED %%g' new_main.cpp
由于您正在修改源文件,因此最好先创建一个副本,然后改为处理这些副本。加上一些其他细节,但以上是您需要的一般想法。
您可以在配置标头中放置一个#define SYSTEM_HEADERS 0
并像这样操作
#include "config.h" // the configuration header
#include "class_a.hpp"
#include "class_b.hpp"
#if SYSTEM_HEADERS // which is #if 0
#include <iostream>
#include <string>
#endif
当您需要系统标头时,您可以将其设置为#define SYSTEM_HEADERS 1
,其中包括系统标头。
- 在运行时处理类型擦除的数据-如何不重新发明轮子
- 错误:无效的预处理指令 #i 的意思是 #if?
- C++预处理会生成变量成员、资源库和映射
- 使用预处理指令检查是否包含标头?
- 预处理的 C/C++ 文件是否特定于计算机?
- 使用 GCC 对 C 文件进行部分预处理(不删除 "define" 指令)
- 在运行时处理 OpenGL 错误
- 在 CPLEX 中求解线性规划,无需剪切和预处理
- CPP -D 选项,用于预处理 Fortran 代码
- 错误:粘贴"tmp_UINT"和"+"未提供有效的预处理令牌
- 任务计划程序库的预处理不起作用 - 多定义错误
- Eclipse 问题 - 编译期间不考虑 .c 和 .cpp 文件中定义的预处理
- 使用python预处理后,C++(opencv)中的垫子类型数据与image_to_array相同
- Howo 使用 cl 预处理为 masm 组装生成一个单独的文件
- 我有一个预处理的 C/C++ 源文件 (cacti.i).如何从这个 .i 文件生成可执行二进制文件,以便我可以像 ./
- 如何使用Visual Studio C/C++编译器(cl.exe)来预处理我的objective-C代码
- 如何运行批处理文件和读取输出
- 是具有预处理前分支实现的结构违反ODR
- 与不完整的Cholesky预处理的共轭梯度返回特征库的意外错误
- Visual Studio C - 无法输出预处理文件