你愿意付出多少努力?有一种令人讨厌的晦涩方法可以做到这一点,但它要求您设置一个虚拟目录来保存系统标头的代理项。 OTOH,它不需要对任何源代码进行任何更改。同样的技术对于 C 代码也同样有效。
Setup
Files:
./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
,就是你想要的。
Analysis
The -Dinclude=#include
参数相当于#define include #include
。当预处理器从宏生成输出时,即使它看起来像指令(例如#include
),它不是预处理器指令。引用 C++11 标准 ISO/IEC 14882:2011(据我所知,这并不是说版本之间发生了变化,而且逐字地与 C11 标准、ISO/IEC 9899:2011 中的内容相同,在 §6.10.3 中) :
§16.3 宏替换
¶8 If a #
预处理标记,后跟标识符,在词法上出现在预处理指令可以开始的位置,标识符不受宏替换的影响。
§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,测试是在 Mac OS X 10.11.5 和 GCC 6.1.0 上完成的。如果您使用 GCC(GNU 编译器集合,带有领先的示例编译器gcc
and g++
),您的里程应该不会因任何可行的替代版本而有太大变化。
如果您不喜欢使用宏名称include
,你可以将其更改为任何适合你的其他内容 -syzygy
, apoplexy
, nadir
, reinclude
,... - 并更改代理标头以使用该名称,并在预处理器(编译器)命令行上定义该名称。优点之一include
是你不可能有任何东西使用它作为宏名称。
自动生成代理标头
osgx https://stackoverflow.com/users/196561/osgx asks https://stackoverflow.com/questions/20889460/how-do-i-run-the-preprocessor-on-local-headers-only#comment63755844_38163793:
我们如何自动生成模拟系统头?
有多种选择。一种是分析您的代码(使用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
添加了一些格式后,当我用我对以下问题的答案扫描源代码树时,会生成这样的标题列表:
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
将接受指定代理标头目录的参数。