我想知道是否可以用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:如果有可能扩展“本地” ""
包含而不扩展“全局” <>
包含
答案 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.c
或expand_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__
是字符串文字。
自动化
以前的方法可行,但可能很乏味;
每个#include
都需要从文件中手动删除,并且
它必须作为gcc
的参数添加到-imacros
调用中。
第一部分易于编写:将grep -Gv '^#include[ ]*<' <./"$fname"
传送到gcc。
第二部分进行一些锻炼(至少没有awk):
2.1从先前的grep命令-v
grep -G '^#include[ ]*<' <./"$fname"
否定匹配项
2.2在expr
内xarg
之前的管道中,从每个包含指令中提取标头名称: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
,但要注意,标头的内容将被复制,并且代码可能会失败,如果标头包含变量的初始化。在我的示例中,故意是这种情况。
摘要
这里有很多不错的答案,那又为什么呢?因为这似乎是具有以下属性的唯一解决方案:
方法足够通用,不仅可用于玩具示例,而且实际上可用于驻留在单个目录中的中小型项目。