如何显示“预处理”代码忽略了GCC包含的内容

时间:2019-02-26 17:40:17

标签: c linux gcc c-preprocessor

我想知道是否可以用gcc输出“预处理”代码,但“忽略”(不扩展)包括:

ES我有这个主要知识:

#include <stdio.h>
#define prn(s) printf("this is a macro for printing a string: %s\n", s);

int int(){
char str[5] = "test"; 
prn(str);
return 0;
}

我运行gcc -E main -o out.c

我知道了

/*
all stdio stuff
*/

int int(){
char str[5] = "test";
printf("this is a macro for printing a string: %s\n", str);
return 0;
}

我只想输出:

#include <stdio.h>
int int(){
char str[5] = "test";
printf("this is a macro for printing a string: %s\n", str);
return 0;
}

或者至少

int int(){
char str[5] = "test";
printf("this is a macro for printing a string: %s\n", str);
return 0;
}

PS:如果有可能扩展“本地” ""包含而不扩展“全局” <>包含

7 个答案:

答案 0 :(得分:2)

当cpp扩展包含时,它会添加# directives (linemarkers)以将错误追溯到原始文件。

您可以添加一个后处理步骤(可以用任何脚本语言编写,甚至可以用C编写),以仅解析行标记并过滤出项目目录之外文件中的行;更好的是,标志(3)之一标记了系统头文件(这些东西来自using dummy = decltype(call(std::declval<void(*)()>())); 提供的路径,无论是由编译器驱动程序隐式地还是由显式的),因此您也可以利用它。

例如在Python 3中:

-isystem

我使用#!/usr/bin/env python3 import sys skip = False for l in sys.stdin: if not skip: sys.stdout.write(l) if l.startswith("# "): toks = l.strip().split(" ") linenum, filename = toks[1:3] flags = toks[3:] skip = "3" in flags

gcc -E foo.c | ./filter.py

答案 1 :(得分:2)

我同意Matteo Italia的评论,即如果仅阻止对#include指令进行扩展,那么生成的代码将不代表编译器实际看到的内容,因此在故障排除中将很少使用。

这里有个解决方法。在包含之前和之后添加变量声明。任何合理唯一的变量都可以。

int begin_includes_tag;
#include <stdio.h>
... other includes
int end_includes_tag;

那么您可以做:

> gcc -E main -o out.c | sed '/begin_includes_tag/,/end_includes_tag/d'

sed命令将删除这些变量声明之间的所有内容。

答案 2 :(得分:1)

假设文件名为c.c

gcc -E c.c | tail -n +`gcc -E c.c | grep -n -e "#*\"c.c\""  | tail -1 | awk -F: '{print $1}'`

似乎# <number> "c.c"标记了每个#include之后的行

当然,您也可以将gcc -E c.c保存在一个文件中,以使其两次不执行

优点是在执行#include 之前不修改源代码或删除gcc -E ,这只是删除了从上到下的所有行由#include制作的...如果我是正确的

答案 3 :(得分:1)

防止#include扩展,以文本方式运行预处理器,删除# 1 "<stdint>"等。文本预处理器生成的垃圾会重新暴露受保护的#include

此shell函数可以做到:

expand_cpp(){
     sed 's|^\([ \t]*#[ \t]*include\)|magic_fjdsa9f8j932j9\1|' "$@" \
     | cpp | sed 's|^magic_fjdsa9f8j932j9||; /^# [0-9]/d'
}

只要您将包含词保持在一起,而不是像

那样疯狂地做
#i\
ncl\
u??/
de <iostream>

(上面您可以看到2个反斜杠连续线+ 1个三字组(?? / == \)反斜杠连续线)。

如果愿意,可以用相同的方式保护#if s #ifdef s #ifndef s #endif s和#else s。

适用于您的示例

example.c:

#include <stdio.h>
#define prn(s) printf("this is a macro for printing a string: %s\n", s);

int int(){
char str[5] = "test";
prn(str);
return 0;
}

expand_cpp < example.cexpand_cpp example.c一样,它会生成:

#include <stdio.h>


int int(){
char str[5] = "test";
printf("this is a macro for printing a string: %s\n", str);;
return 0;
}

答案 4 :(得分:1)

您可以使用f = open("../input/input_file.txt", "r") print([line for line in f]) f.close() 来显示-dI指令并对预处理器输出进行后处理。

假设您的文件名为#include

foo.c

或隐藏SOURCEFILE=foo.c gcc -E -dI "$SOURCEFILE" | awk ' /^# [0-9]* "/ { if ($3 == "\"'"$SOURCEFILE"'\"") show=1; else show=0; } { if(show) print; }' 的所有# line_number "file"行:

$SOURCEFILE

注意:AWK脚本不适用于包含空格的文件名。要使用空格处理文件名,您可以修改AWK脚本以比较SOURCEFILE=foo.c gcc -E -dI "$SOURCEFILE" | awk ' /^# [0-9]* "/ { ignore = 1; if ($3 == "\"'"$SOURCEFILE"'\"") show=1; else show=0; } { if(ignore) ignore=0; else if(show) print; }' 而不是$0

答案 5 :(得分:0)

许多先前的答案都朝着使用跟踪#指令的方向发展。

实际上,它是经典Unix(带有awk)中的单行代码:

gcc -E file.c | awk '/# [1-9][0-9]* "file.c"/ {skip=0; next} /# [1-9][0-9]* ".*"/ {skip=1} (skip<1) {print}'

答案 6 :(得分:0)

TL; DR

将文件名分配给fname并在shell中运行以下命令。在整个过程中,fname被假定为sh变量,其中包含要处理的源文件。

fname=file_to_process.c ;
grep -G '^#include' <./"$fname" ;
grep -Gv '^#include[ ]*<' <./"$fname" | gcc -x c - -E -o - $(grep -G '^#include[ ]*<' <./"$fname" | xargs -I {} -- expr "{}" : '#include[ ]*<[ ]*\(.*\)[ ]*>' | xargs -I {} printf '-imacros %s ' "{}" ) | grep -Ev '^([ ]*|#.*)$'

gcc以外的所有内容都是纯POSIX sh,没有bashisms或非便携式选项。第一个grep在那里输出#include指令。

GCC的-imacros

来自gcc文档:

-imacros file :与“ -include”完全一样,只是扫描文件产生的所有输出都是 扔掉。它定义的宏保持定义。这使您可以获取所有 标题中的宏,而无需处理其声明

那么-include是什么?

-include file :处理文件,就像#include“ file”出现在主文件的第一行一样 源文件。但是,搜索文件的第一个目录是预处理程序的 工作目录,而不是包含主源文件的目录。如果 在此处找不到,它将在#include“ ...”的其余部分中搜索 搜索链正常。

简而言之,因为您不能在<>指令中使用""-include,所以它始终会像源代码中的#include <file>一样起作用。

第一种方法

ANSI C保证assert是宏,因此非常适合简单测试: printf 'int main(){\nassert(1);\nreturn 0;}\n' | gcc -x c -E - -imacros assert.h。 选项-x c-告诉gcc从stdin读取源文件,并且使用的语言是C。输出不包含assert.h的任何声明,但是仍然很混乱,可以使用grep进行清理:

printf 'int main(){\nassert(1);\nreturn 0;}\n' | gcc -x c -E - -imacros assert.h | grep -Ev '^([ ]*|#.*)$'

注意:通常,gcc不会扩展旨在成为宏的令牌,但是缺少定义。然而,assert恰好完全扩展:__extension__是编译器选项,__assert_fail是函数,__PRETTY_FUNCTION__是字符串文字。

自动化

以前的方法可行,但可能很乏味;

  1. 每个#include都需要从文件中手动删除,并且

  2. 它必须作为gcc的参数添加到-imacros调用中。

第一部分易于编写:将grep -Gv '^#include[ ]*<' <./"$fname"传送到gcc。

第二部分进行一些锻炼(至少没有awk):

2.1从先前的grep命令-v

中删除grep -G '^#include[ ]*<' <./"$fname"否定匹配项

2.2在exprxarg之前的管道中,从每个包含指令中提取标头名称:xargs -I {} -- expr "{}" : '#include[ ]*<[ ]*\(.*\)[ ]*>'

2.3再次管道传输到xarg,并使用-imacros 前缀xargs -I {} printf '-imacros %s ' "{}"打印printf 2.4将所有命令替换都包含在“ $()”中,并将其放置在gcc中。

完成。从我回答的开始,这就是您最终得到冗长命令的方式。

解决细微问题

此解决方案仍存在缺陷;如果本地头文件本身包含全局文件,则这些全局文件将被扩展。解决此问题的一种方法是使用grep + sed从本地文件传输所有全局包含,并将它们收集在每个* .c文件中。

printf '' > std ;
for header in *.h ; do
    grep -G '^#include[ ]*<' <./$header >> std ;
    sed -i '/#include[ ]*</d' $header ;
done;
for source in *.c ; do
    cat std > tmp;
    cat $source >> tmp;
    mv -f tmp $source ;
done

现在可以在pwd内的任何* .c文件上调用处理脚本,而不必担心,全局包含中的任何内容都会泄漏到其中。最后的问题是重复。本地标头,包括它们自己的本地包含,可能会重复,但这仅在不保护标头的情况下才会发生,并且通常 every 标头应始终保持


最终版本和示例

为演示这些脚本的实际效果,下面是一个小演示:

文件h1.h

#ifndef H1H
#define H1H
#include <stdio.h>
#include <limits.h>
#define H1 printf("H1:%i\n", h1_int)
int h1_int=INT_MAX;
#endif

文件h2.h

#ifndef H2H
#define H2H
#include <stdio.h>
#include "h1.h"
#define H2 printf("H2:%i\n", h2_int)
int h2_int;
#endif

文件main.c

#include <assert.h>
#include "h1.h"
#include "h2.h"
int main(){
  assert(1);
  H1;
  H2;
}

脚本preproc.sh的最终版本:

fname="$1"

printf '' > std ;
for source in *.[ch] ; do
    grep -G '^#include[ ]*<' <./$source >> std ;
    sed -i '/#include[ ]*</d' $source ;
    sort -u std > std2;
    mv -f std2 std;
done;
for source in *.c ; do
    cat std > tmp;
    cat $source >> tmp;
    mv -f tmp $source ;
done

grep -G '^#include[ ]*<' <./"$fname" ;

grep -Gv '^#include[ ]*<' <./"$fname" | gcc -x c - -E -o - $(grep -G '^#include[ ]*<' <./"$fname" | xargs -I {} -- expr "{}" : '#include[ ]*<[ ]*\(.*\)[ ]*>' | xargs -I {} printf '-imacros %s ' "{}" ) | grep -Ev '^([ ]*|#.*)$'

呼叫./preproc.sh main.c的输出:

#include <assert.h>
#include <limits.h>
#include <stdio.h>
int h1_int=0x7fffffff;
int h2_int;
int main(){
 ((void) sizeof ((
 1
 ) ? 1 : 0), __extension__ ({ if (
 1
 ) ; else __assert_fail (
 "1"
 , "<stdin>", 4, __extension__ __PRETTY_FUNCTION__); }))
          ;
  printf("H1:%i\n", h1_int);
  printf("H2:%i\n", h2_int);
}

这应该总是编译。如果您确实要打印每一个#include "file",请从grep模式< preproc.sh`中删除'^#include[ ]*<' in 16-th line of ,但要注意,标头的内容将被复制,并且代码可能会失败,如果标头包含变量的初始化。在我的示例中,故意是这种情况。

摘要

这里有很多不错的答案,那又为什么呢?因为这似乎是具有以下属性的唯一解决方案:

  1. 扩展本地包含
  2. 包含的全局变量将被丢弃
  3. 扩展了在本地或全局包含中定义的宏

方法足够通用,不仅可用于玩具示例,而且实际上可用于驻留在单个目录中的中小型项目。