如何仅在本地标头上运行预处理器?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (35)

我希望预处理器读取本地头的包含,但忽略系统头的包含。换句话说,如何让预处理器跳过表单的预处理指令:

#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不包括行标记的生成。

我似乎找不到排除系统头处理的标志。

提问于
用户回答回答于

你愿意去多少努力? 有一个令人讨厌的方式来做到这一点,但它需要你设置一个虚拟目录来保存系统头文件的代理。 它不需要你的任何源代码的任何改变。 同样的技术同样适用于C代码。

设置

文件:

./class_a.hpp
./class_b.hpp
./example.cpp
./system-headers/iostream
./system-headers/string

system headers,如./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缺少(a的代理项)系统标头。

$ 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,测试是在MacOSX10.11.5和GCC 6.1.0上进行的。如果您正在使用GCC(GNU编译器集合,并带有前面的示例编译器)gccg++),你的里程不应该随着任何看似合理的替代版本而有很大的变化。

如果您不愿意使用宏名include,你可以把它换成适合你的任何东西-syzygyapoplexynadirreinclude等,并更改代理项头以使用该名称,并在预处理器(编译器)命令行中定义该名称,include的一个优点是你不可能有任何东西用它作为宏名。

自动生成代理标头

如何生成代理头?有各种各样的选择。一个是分析代码(用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将接受指定代理程序头目录的参数。

用户回答回答于

有了clang,你就可以做到,例如:

 clang -Imyinclude -P -E -nostdinc -nobuiltininc main.cpp

似乎没有办法保护这个系统#include但它找不到。

这对gcc不起作用,因为它的预处理程序在使用-nostdinc却找不到#included头文件。

扫码关注云+社区