【问题标题】:How do I run the preprocessor on local headers only?如何仅在本地标头上运行预处理器?
【发布时间】:2014-01-20 06:50:54
【问题描述】:

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

#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 不包括行标记的生成。
我似乎找不到排除系统标头处理的标志。

【问题讨论】:

  • -nostdinc++ 工作吗?
  • @sftrabbit 它仍然会尝试引入文件,但不会搜索系统目录。这会导致错误;类似:“iostream:没有这样的文件或目录”

标签: c++ gcc clang c-preprocessor header-files


【解决方案1】:

您可以使用临时包含的注释保护系统包含,将 cmets 保留在预处理器输出 (-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

由于您正在修改源文件,因此最好先创建一个副本然后处理这些副本。加上其他一些细节,但以上是您需要的总体思路。

【讨论】:

    【解决方案2】:

    你愿意付出多少努力?有一种令人讨厌的晦涩方法,但它需要您设置一个虚拟目录来保存系统标头的代理项。 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 &lt;iostream&gt; 时,它会在当前目录中查找并没有找到文件,然后在./system-headers 中查找并找到文件iostream,然后将其处理到输出中。它包含一行include &lt;iostream&gt;。由于include 是一个宏,它被扩展(到#include)但阻止了进一步的扩展,并且# 不作为指令处理,因为§16.3.4 ¶3。因此,输出包含#include &lt;iostream&gt;

    当预处理器遇到#include "class_a.hpp" 时,它会在当前目录中查找文件并将其内容包含在输出中。

    冲洗并重复其他标题。如果class_a.hpp 包含#include &lt;iostream&gt;,那么最终会再次扩展到#include &lt;iostream&gt;(带有前导空格)。如果您的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,测试是在 Mac OS X 10.11.5 和 GCC 6.1.0 上完成的。如果您使用的是 GCC(GNU 编译器集合,带有领先的示例编译器 gccg++),您的里程不应该与任何可能的替代版本有很大差异。

    如果您不习惯使用宏名称 include,您可以将其更改为适合您的任何其他名称 — syzygyapoplexynadirreinclude,... — 并更改代理标题使用该名称,并在预处理器(编译器)命令行上定义该名称。 include 的一个优点是你不可能有任何东西使用它作为宏名称。

    自动生成代理头

    osgxasks:

    我们如何自动生成模拟系统标头?

    有多种选择。一种是分析您的代码(例如使用grep)以查找被引用或可能被引用的名称并生成适当的代理标头。生成一些未使用的标头并不重要——它们不会影响流程。注意,如果使用#include &lt;sys/wait.h&gt;,代理项必须是./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 的完整生产就绪版本将接受指定代理标头目录的参数。

    【讨论】:

    • 我们如何自动生成模拟系统头文件?
    • 这太棒了。你的第一句话吓到我了“你愿意付出多少努力?”这个解决方案实际上很简单,到目前为止非常完美。
    • 这个cpp -Dinclude=#include 部分不适合我。 #include 已扩展,我无法从 #include 更改 include 单词(g++ (GCC) 6.2.1 20161010)。太糟糕了,因为这确实是一个不错的技巧。
    • 这是一个正确的观察,@rkioji。你下一步做什么——你如何解决这个限制——取决于你的最终目标是什么。如果您将代理系统标头也增加到 #include 原始文件,您可能会安排在输出中生成带有文件名和可识别行号的合适消息(@ 之前的 #line 9998 "system-header.h" 和 @ 之后的 #line 9999 "system-header.h" 987654383@ 至少会给你标记,可以用来删除包含的材料。但是各种宏扩展将基于标题进行。
    • 任何更复杂的东西,您开始编写自己的自定义 C 预处理器。当然,这是可以做到的,但这不是一个容易的提议。
    【解决方案3】:

    您可以将#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,其中将包含系统标头。

    【讨论】:

    • 我认为这行不通,因为它不会将 #include &lt;iostream&gt; 文本放在输出中。相反,什么都没有。
    【解决方案4】:

    使用 clang 你可以做例如:

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

    似乎没有办法保留它无法找到的系统#include 行。

    这对 gcc 不起作用,因为它的预处理器在使用 -nostdinc 时会停止,并且找不到 #included 头文件。

    【讨论】:

    • 但是 gcc 没有等效的好方法吗? :/ 这太基础了...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-02-01
    • 1970-01-01
    • 2012-06-08
    • 1970-01-01
    • 1970-01-01
    • 2018-09-27
    相关资源
    最近更新 更多