2017-03-16 62 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