计算 C++ 头文件中的所有宏
Evaluate all macros in a C++ header file
我需要构建一个自动化系统来解析其中包含大量 #define
语句的 C++ .h 文件,并使用每个 #define
计算出的值执行某些操作。除了 #define
语句之外,.h 文件中还有很多其他垃圾。
objective是创建一个key-value列表,key是#define
语句定义的所有关键字,value是定义对应的宏的求值. #defines
使用一系列嵌套宏定义关键字,这些宏最终解析为编译时整数常量。有一些不能解析为编译时整数常量,这些必须跳过。
.h 文件会随着时间的推移而演变,因此该工具不能是一个长的硬编码程序,它实例化一个变量等于每个关键字。我无法控制 .h 文件的内容。唯一的保证是它可以用标准的 C++ 编译器构建,并且将添加更多 #defines
但永远不会删除。宏公式可能随时更改。
我看到的选项是:
- 实现部分(或挂接到现有的)C++ 编译器并在预处理器步骤中拦截宏的值。
- 使用正则表达式动态构建一个源文件,该文件将使用当前定义的所有宏,然后编译并执行源文件以获得所有宏的评估形式。不知何故(?)跳过不计算为编译时整数常量的宏。 (另外,不确定正则表达式是否足够表达以捕获所有可能的多行宏定义)
这两种方法都会为该项目的构建过程增加大量的复杂性和脆弱性,我希望避免这种情况。有没有更好的方法来评估 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
只要我可以将它作为键值对列表进一步处理,这个输出的格式并不重要。
这是一个基于澄清评论的假设的概念。
- 只有一个header
- 不包括
- 不依赖包含的代码文件
- 不依赖于之前包含的 headers
- 不依赖包含顺序
其他主要要求:
- 不要冒险影响二进制构建过程
(是制作实际软件产品的部分)
- 不要尝试模拟二进制构建 compiler/parser
如何:
- 复制一份
- 从专用代码文件中包含它,
其中仅包含“#include "copy.h";
或者直接预处理 header
(这对我的习惯来说很奇怪)
- 删除除预处理器和编译指示之外的所有内容,
关注 line-continuation
- 替换所有“#define"s by "HaPoDefine”,
除了一个(例如第一个)
- 重复
- 预处理包含的代码文件
(大多数编译器都有一个开关来执行此操作)
- 保存输出
- 将另一个 "HaPoDefine" 变回“#define”
- 直到没有"HaPoDefine"剩下
- 从中间保存的增量中收获所有宏扩展
- 丢弃所有不相关的东西
- 由于最终的实际数值很可能是编译器(而非预处理器)的结果,因此请使用像 bashs "expr" 这样的工具来计算 human-eye 可读性的值,
小心不要冒二进制构建过程差异的风险
- 使用一些正则表达式魔术来实现任何所需的格式
一种方法是编写一个 "program generator" 来生成一个程序(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":
中打印 #define-statements 的值
# 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, ¯oValuePos) == 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.txt
和 defines.cpp
;但是,出于演示目的,它们很有用。当应用于您的头文件时,defines.txt
和 defines.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?
您能否将 g++
或 gcc
与 -E 选项一起使用,并使用该输出?
-E Stop after the preprocessing stage; do not run the compiler proper.
The output is in the form of preprocessed source code, which
is sent to the standard output. Input files which don't require
preprocessing are ignored.
有了这个,我想:
- 从源
创建所有 #define
键的列表
- 运行 下面针对源文件的适当命令,让 GNU 预处理器执行它的操作
- 从 stdout 中获取预处理结果,过滤以仅采用整数形式的结果,并将其输出到您想要表示的 key/value 对
这两个命令之一:
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
我需要构建一个自动化系统来解析其中包含大量 #define
语句的 C++ .h 文件,并使用每个 #define
计算出的值执行某些操作。除了 #define
语句之外,.h 文件中还有很多其他垃圾。
objective是创建一个key-value列表,key是#define
语句定义的所有关键字,value是定义对应的宏的求值. #defines
使用一系列嵌套宏定义关键字,这些宏最终解析为编译时整数常量。有一些不能解析为编译时整数常量,这些必须跳过。
.h 文件会随着时间的推移而演变,因此该工具不能是一个长的硬编码程序,它实例化一个变量等于每个关键字。我无法控制 .h 文件的内容。唯一的保证是它可以用标准的 C++ 编译器构建,并且将添加更多 #defines
但永远不会删除。宏公式可能随时更改。
我看到的选项是:
- 实现部分(或挂接到现有的)C++ 编译器并在预处理器步骤中拦截宏的值。
- 使用正则表达式动态构建一个源文件,该文件将使用当前定义的所有宏,然后编译并执行源文件以获得所有宏的评估形式。不知何故(?)跳过不计算为编译时整数常量的宏。 (另外,不确定正则表达式是否足够表达以捕获所有可能的多行宏定义)
这两种方法都会为该项目的构建过程增加大量的复杂性和脆弱性,我希望避免这种情况。有没有更好的方法来评估 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
只要我可以将它作为键值对列表进一步处理,这个输出的格式并不重要。
这是一个基于澄清评论的假设的概念。
- 只有一个header
- 不包括
- 不依赖包含的代码文件
- 不依赖于之前包含的 headers
- 不依赖包含顺序
其他主要要求:
- 不要冒险影响二进制构建过程 (是制作实际软件产品的部分)
- 不要尝试模拟二进制构建 compiler/parser
如何:
- 复制一份
- 从专用代码文件中包含它,
其中仅包含“#include "copy.h";
或者直接预处理 header
(这对我的习惯来说很奇怪) - 删除除预处理器和编译指示之外的所有内容, 关注 line-continuation
- 替换所有“#define"s by "HaPoDefine”, 除了一个(例如第一个)
- 重复
- 预处理包含的代码文件 (大多数编译器都有一个开关来执行此操作)
- 保存输出
- 将另一个 "HaPoDefine" 变回“#define”
- 直到没有"HaPoDefine"剩下
- 从中间保存的增量中收获所有宏扩展
- 丢弃所有不相关的东西
- 由于最终的实际数值很可能是编译器(而非预处理器)的结果,因此请使用像 bashs "expr" 这样的工具来计算 human-eye 可读性的值,
小心不要冒二进制构建过程差异的风险 - 使用一些正则表达式魔术来实现任何所需的格式
一种方法是编写一个 "program generator" 来生成一个程序(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":
中打印 #define-statements 的值# 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, ¯oValuePos) == 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.txt
和 defines.cpp
;但是,出于演示目的,它们很有用。当应用于您的头文件时,defines.txt
和 defines.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?
您能否将 g++
或 gcc
与 -E 选项一起使用,并使用该输出?
-E Stop after the preprocessing stage; do not run the compiler proper. The output is in the form of preprocessed source code, which is sent to the standard output. Input files which don't require preprocessing are ignored.
有了这个,我想:
- 从源 创建所有
- 运行 下面针对源文件的适当命令,让 GNU 预处理器执行它的操作
- 从 stdout 中获取预处理结果,过滤以仅采用整数形式的结果,并将其输出到您想要表示的 key/value 对
#define
键的列表
这两个命令之一:
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