【问题标题】:Evaluate all macros in a C++ header file评估 C++ 头文件中的所有宏
【发布时间】:2017-03-16 20:52:08
【问题描述】:

我需要构建一个自动化系统来解析包含大量 #define 语句的 C++ .h 文件,并使用每个 #define 的值做一些事情。除了#define 语句之外,.h 文件中还有很多其他垃圾。

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

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

我看到的选项是:

  1. 实现部分(或挂钩到现有的)C++ 编译器,并在预处理器步骤中截取宏的值。
  2. 使用正则表达式动态构建一个源文件,该文件将使用当前定义的所有宏,然后编译并执行源文件以获得所有宏的评估形式。不知何故(?)跳过不评估为编译时整数常量的宏。 (另外,不确定正则表达式是否足够表达以捕获所有可能的多行宏定义)

这两种方法都会给这个项目的构建过程增加相当大的复杂性和脆弱性,这是我想避免的。有没有更好的方法来评估 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

这个输出的格式并不重要,只要我可以将它作为一个键值对列表进一步处理。

【问题讨论】:

  • 只需使用现有的 C 预处理器来帮助您。带有 -dU 选项的常规 GNU cpp 应该会让您非常接近您所追求的结果。
  • 为什么CONSTANT_1CONSTANT_2 在输出中但MAGIC_NUMBER_BASEMAGIC_NUMBERMORE_MAGIC_1MORE_MAGIC_2 不是?看起来它们至少和其他两个一样符合您的标准(定义解析为编译时整数常量)。
  • @BenVoigt 它们应该在输出中,我现在就修复它。
  • @MatteoItalia 当我在 cpp 上使用 -dU 标志时,我没有得到任何预期的输出值。我还需要其他标志吗?
  • @Techrocket9:问题是预处理器的规则根本不符合您的要求。当外部宏被使用时,宏使用的宏会被扩展,而不是在它被定义的地方。所以CONSTANT_1 在被使用之前不是编译时整数表达式……而且可能有些用途是,有些不是。

标签: c++ macros c-preprocessor constants compile-time-constant


【解决方案1】:

一种方法可以是编写一个“程序生成器”,它生成一个程序(printDefines 程序),其中包含像std::cout &lt;&lt; "MAGIC_NUMBER" &lt;&lt; " " &lt;&lt; (MAGIC_NUMBER_BASE + 0x2) &lt;&lt; std::endl; 这样的语句。显然,执行此类语句将解析相应的宏并打印出它们的值。

头文件中的宏列表可以通过g++-dM -E' option. Feeding this "program generator" with such a list of #defines will generate a "printDefines.cpp" with all the requiredcout`-statements 获得。编译并执行生成的 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, &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 的内容如下:

定义.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
...

定义.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?

【讨论】:

  • 这与我在 powershell 和 MSVC 中的实现非常接近。在每个打印语句中打印 macroValue 而不是再次打印 macroName 是否有很大的优势?
  • 没有实质性优势;只是在 cpp 文件中多了一点文档。
【解决方案2】:

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

  • 只有一个标题
  • 不包括
  • 不依赖包含的代码文件
  • 不依赖于以前包含的标头
  • 不依赖于包含顺序

其他主要要求:

  • 不要冒险影响二进制构建过程 (作为制作实际软件产品的部分)
  • 不要尝试模拟二进制构建编译器/解析器

如何:

  • 复制一份
  • 从专用代码文件中包含它,
    仅包含“#include”copy.h“;
    或直接预处理标头
    (这对我的习惯感觉很奇怪)
  • 删除除预处理器和编译指示以外的所有内容, 注意换行
  • 用“HaPoDefine”替换所有“#define”, 除了一个(例如第一个)
  • 重复
    • 预处理包含的代码文件 (大多数编译器都有一个开关来执行此操作)
    • 保存输出
    • 将另一个“HaPoDefine”转回“#define”
  • 直到没有留下“HaPoDefine”
  • 从中间保存的增量中收集所有宏扩展
  • 丢弃所有不相关的内容
  • 由于最终的实际数值很可能是编译器(而不是预处理器)的结果,因此请使用 bashs “expr”之类的工具来计算人眼可读性的值,
    小心不要冒与二进制构建过程不同的风险
  • 使用一些正则表达式魔法来实现任何所需的格式

【讨论】:

  • 澄清一下:至少现在,这是一个我必须解析的头文件,所以我不需要担心包含。 .h 文件被许多使用多个编译器(我知道的 GCC 和 MSVC)的团队使用和修改,并且应该保持对 C++/14 的抱怨。
  • 该文件只需要保持兼容二进制构建过程。对于文档构建过程,并且只是暂时的,它可以经历完全独立的内容的迭代。被操纵的版本也可以具有不同的名称和/或扩展名。实际上扩展名“.i”很可能。它只是一个文件,使事情变得更容易。但要小心考虑“一个标题”。始终从正在编译的代码文件的角度看到标头的内容。并且代码文件之间的内容可能不同(例如 AUTOSAR 概念)。如果你的系统更简单,那你就走运了。
【解决方案3】:

您可以将g++gcc 与-E 选项一起使用,并使用该输出吗?

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

有了这个,我想:

  1. 从源中创建所有 #define 键的列表
  2. 对源文件运行下面的适当命令,让 GNU 预处理器完成它的工作
  3. 从标准输出中获取预处理结果,过滤以仅采用整数形式,然后将其输出到您想要表示键/值对的任何位置

这两个命令之一:

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

【讨论】:

  • gcc -E 和 g++ -E 都没有对我帖子中的示例头文件进行宏扩展。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-11-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-19
相关资源
最近更新 更多