2017-03-16 28 views
1

我需要构建一个自动化系统来解析C++ .h文件,其中包含大量的#define语句,并使用每个#define可用的值进行操作。除了#define声明之外,.h文件还有很多其他垃圾。评估C++头文件中的所有宏

目标是创建一个键值列表,其中键是所有由#define语句定义的关键字,值是对应于这些定义的宏的评估。 #defines使用一系列嵌套宏定义关键字,这些宏最终解析为编译时的整型常量。有一些不能解决编译时整数常量,并且这些必须被跳过。

.h文件将随着时间的推移发展,所以该工具不能是一个长的硬编码程序,它实例化一个变量以等于每个关键字。我无法控制.h文件的内容。唯一的保证是它可以用一个标准的C++编译器构建,并且更多的#defines将被添加,但从不删除。宏公式可能随时改变。

我看到这种情况的选项是:

  1. 实现的局部(或钩到现有的)C++编译器和在预处理步骤截取的宏的值。
  2. 使用regexes动态构建一个源文件,该文件将消耗当前定义的所有宏,然后编译并执行源文件以获取所有宏的评估形式。以某种方式(?)跳过不计算为编译时整数常量的宏。 (另外,不知道正则表达式是足够的表现力捕捉到所有可能的多行宏定义)

这两种方法都将增加相当大的复杂性和脆弱性的生成过程为这个项目,我想避免的。有没有更好的方法来评估C++ .h文件中的所有#define宏?

下面是我所期待解析一个例子:

#ifndef Constants_h 
#define Constants_h 

namespace Foo 
{ 
#define MAKE_CONSTANT(A, B) (A | (B << 4)) 
#define MAGIC_NUMBER_BASE 40 
#define MAGIC_NUMBER MAGIC_NUMBER_BASE + 0x2 
#define MORE_MAGIC_1 345 
#define MORE_MAGIC_2 65 


    // Other stuff... 


#define CONSTANT_1 MAKE_CONSTANT (MAGIC_NUMBER + 564, MORE_MAGIC_1 | MORE_MAGIC_2) 
#define CONSTANT_2 MAKE_CONSTANT (MAGIC_NUMBER - 84, MORE_MAGIC_1 & MORE_MAGIC_2^0xA) 
    // etc... 

#define SKIP_CONSTANT "What?" 

    // More CONSTANT_N mixed with more other stuff and constants which do 
    // not resolve to compile-time integers and must be skipped 


} 

#endif Constants_h 

我需要摆脱的,这是所有编译时其决心的定义整型常量的名字和评估。在这种情况下,显示的定义它是

MAGIC_NUMBER_BASE 40 
MAGIC_NUMBER 42 
MORE_MAGIC_1 345 
MORE_MAGIC_2 65 
CONSTANT_1 1887 
CONSTANT_2 -42 

这其实并不重要,这个输出是什么格式,只要我可以用它作为键值对的列表,进一步扎实工作,管道。

+2

只需使用现有的C预处理程序来帮助你。带'-dU'选项的常规GNU'cpp'应该会让你非常接近你之后的结果。 –

+0

为什么输出中的“CONSTANT_1”和“CONSTANT_2”,但是“MAGIC_NUMBER_BASE”,“MAGIC_NUMBER”,“MORE_MAGIC_1”,“MORE_MAGIC_2”不是?它看起来符合你的标准(定义了编译时整数常量的解决方案),至少和其他两个一样。 –

+0

@BenVoigt他们应该在输出中,我现在要修复它。 – Techrocket9

回答

2

一种方法可以是写,其产生程序(printDefines程序),其包括像std::cout << "MAGIC_NUMBER" << " " << (MAGIC_NUMBER_BASE + 0x2) << std::endl;语句的“节目发生器”。显然,执行这些语句将解析相应的宏并打印出它们的值。

头文件中的宏列表可以通过g++-dM -E' option. Feeding this "program generator" with such a list of #defines will generate a "printDefines.cpp" with all the required cout`语句获得。编译并执行生成的printDefines程序然后生成最终输出。它将解析所有的宏,包括那些本身使用其他宏的宏。

请参见下面的shell脚本和下面的程序生成的代码,共同实现这个方法:

脚本打印的#的值定义语句中的“someHeaderfile.h”:

# printDefines.sh 
g++ -std=c++11 -dM -E someHeaderfile.h > defines.txt 
./generateDefinesCpp someHeaderfile.h defines.txt > defines.cpp 
g++ -std=c++11 -o defines.o defines.cpp 
./defines.o 

的代码程序生成“generateDefinesCpp”:

#include <stdio.h> 
#include <string> 
#include <iostream> 
#include <fstream> 
#include <cstring> 

using std::cout; 
using std::endl; 

/* 
* Argument 1: name of the headerfile to scan 
* Argument 2: name of the cpp-file to generate 
* Note: will crash if parameters are not provided. 
*/ 
int main(int argc, char* argv[]) 
{ 
    cout << "#include<iostream>" << endl; 
    cout << "#include<stdio.h>" << endl; 
    cout << "#include \"" << argv[1] << "\"" << endl; 
    cout << "int main() {" << endl; 
    std::ifstream headerFile(argv[2], std::ios::in); 
    std::string buffer; 
    char macroName[1000]; 
    int macroValuePos; 
    while (getline(headerFile,buffer)) { 
     const char *bufferCStr = buffer.c_str(); 
     if (sscanf(bufferCStr, "#define %s %n", macroName, &macroValuePos) == 1) { 
      const char* macroValue = bufferCStr+macroValuePos; 
      if (macroName[0] != '_' && strchr(macroName, '(') == NULL && *macroValue) { 
       cout << "std::cout << \"" << macroName << "\" << \" \" << (" << macroValue << ") << std::endl;" << std::endl; 
      } 
     } 
    } 
    cout << "return 0; }" << endl; 

    return 0; 
} 

的方法可以被优化,使得所述中间文件defines.txtdefines.cpp是没有必要的;但是,为了演示目的,它们很有帮助。当应用到你的头文件的defines.txtdefines.cpp内容将如下:

defines.txt:

#define CONSTANT_1 MAKE_CONSTANT (MAGIC_NUMBER + 564, MORE_MAGIC_1 | MORE_MAGIC_2) 
#define CONSTANT_2 MAKE_CONSTANT (MAGIC_NUMBER - 84, MORE_MAGIC_1 & MORE_MAGIC_2^0xA) 
#define Constants_h 
#define MAGIC_NUMBER MAGIC_NUMBER_BASE + 0x2 
#define MAGIC_NUMBER_BASE 40 
#define MAKE_CONSTANT(A,B) (A | (B << 4)) 
#define MORE_MAGIC_1 345 
#define MORE_MAGIC_2 65 
#define OBJC_NEW_PROPERTIES 1 
#define SKIP_CONSTANT "What?" 
#define _LP64 1 
#define __APPLE_CC__ 6000 
#define __APPLE__ 1 
#define __ATOMIC_ACQUIRE 2 
#define __ATOMIC_ACQ_REL 4 
... 

defines.cpp:

#include<iostream> 
#include<stdio.h> 
#include "someHeaderfile.h" 
int main() { 
std::cout << "CONSTANT_1" << " " << (MAKE_CONSTANT (MAGIC_NUMBER + 564, MORE_MAGIC_1 | MORE_MAGIC_2)) << std::endl; 
std::cout << "CONSTANT_2" << " " << (MAKE_CONSTANT (MAGIC_NUMBER - 84, MORE_MAGIC_1 & MORE_MAGIC_2^0xA)) << std::endl; 
std::cout << "MAGIC_NUMBER" << " " << (MAGIC_NUMBER_BASE + 0x2) << std::endl; 
std::cout << "MAGIC_NUMBER_BASE" << " " << (40) << std::endl; 
std::cout << "MORE_MAGIC_1" << " " << (345) << std::endl; 
std::cout << "MORE_MAGIC_2" << " " << (65) << std::endl; 
std::cout << "OBJC_NEW_PROPERTIES" << " " << (1) << std::endl; 
std::cout << "SKIP_CONSTANT" << " " << ("What?") << std::endl; 
return 0; } 

和执行defines.o的输出那么:

CONSTANT_1 1887 
CONSTANT_2 -9 
MAGIC_NUMBER 42 
MAGIC_NUMBER_BASE 40 
MORE_MAGIC_1 345 
MORE_MAGIC_2 65 
OBJC_NEW_PROPERTIES 1 
SKIP_CONSTANT What? 
+0

这与我一直在使用PowerShell和MSVC的实现非常接近。在每个打印语句中打印macroValue而不是只重新打印一次macroName是否有很大好处? – Techrocket9

+0

没有实质优势;只是在cpp文件中多了一些文档。 –

0

这是一个基于澄清评论的假设的概念。

  • 只有一个报头
  • 没有包括
  • 上包括代码文件
  • 没有依赖于先前包含标头
  • 上包括顺序

主要要求,否则不存在依赖关系不依赖:

  • 不要冒险二进制构建过程 (是使实际的软件产品的一部分)
  • 不要试图效仿二元构建编译器/解析器影响

如何:

  • 复制
  • 从专用代码文件
    包含它,其中只包含“#include”copy.h“;
    或直接预处理头
    (这只是觉得古怪对我的习惯)
  • 删除一切,除了预处理和编译, 注意续行
  • 通过“HaPoDefine”全部替换“的#define” S, 除了一个(例如,第一)
  • 重复
    • 预处理包括代码文件 (最编译器有一个开关来做到这一点)
    • 又将另一个“HaPoDefine”保存输出回“的#define”
  • ,直到没有“HaPoDefine”留
  • 收获从中间的三角洲所有宏展开节省
  • 放弃一切,其不相关
  • 由于最终的实际数值很可能是编译器(而不是预处理器)的结果,因此请使用像bashs“expr”这样的工具来计算人眼可读性的值,请注意不要风险di fferences二进制构建过程
  • 使用一些正则表达式魔术以实现任何所需的格式
+0

澄清:至少现在,这是一个我必须解析的头文件,所以我不需要担心包含。 .h文件被许多团队使用并使用多个编译器(我知道的GCC和MSVC)进行了修改,并应保持C++/14的投诉状态。 – Techrocket9

+0

该文件只需保持二进制构建过程的兼容性。对于文档构建过程,并且只是暂时的,它可以经历完全个别内容的迭代。操作版本也可以有不同的名称和/或扩展名。其实扩展名“.i”很可能。 只有一个文件让事情变得更容易。但要小心思考“一个标题”。头文件的内容总是从正在编译的代码文件的角度来看。代码文件的内容可能不同(例如AUTOSAR概念)。如果你的系统比较简单,那就很幸运。 – Yunnosch

0

Can您使用g++gcc与-E选项,并使用该输出?

-E在预处理阶段后停止;不要运行编译器。 输出采用预处理源代码的形式,将 发送到标准输出。不需要 预处理的输入文件将被忽略。

有了这个,我想:

  1. 创建从源
  2. 运行下面对源文件(S)的相应命令所有#define密钥列表,并让GNU预处理器做它的东西
  3. 从stdout中获取预处理结果,筛选器仅取整数形式的结果,并将其输出到但是要表示键/值对

其中一个两个命令:

gcc -E myFile.c 
g++ -E myFile.cpp 

https://gcc.gnu.org/onlinedocs/gcc-2.95.2/gcc_2.html https://gcc.gnu.org/onlinedocs/cpp/Preprocessor-Output.html

+0

既没有gcc -E也没有g ++ -E实际上在我的文章中对示例头文件进行了宏扩展。 – Techrocket9