Оцените все макросы в заголовочном файле C++
У меня есть требование создать автоматизированную систему для анализа файла C++ .h с большим количеством #define
заявления в нем и сделать что-то со значением, которое каждый #define
работает до. Файл.h содержит много другого мусора, кроме #define
заявления.
Цель состоит в том, чтобы создать список значений ключей, где ключами являются все ключевые слова, определенные #define
операторы и значения являются оценками макросов, которые соответствуют определениям. #defines
Определите ключевые слова с помощью ряда вложенных макросов, которые в конечном итоге преобразуются в целочисленные константы времени компиляции. Есть некоторые, которые не разрешают целочисленные константы времени компиляции, и они должны быть пропущены.
Файл.h со временем будет развиваться, поэтому инструмент не может быть длинной жестко запрограммированной программой, которая создает переменную, равную каждому ключевому слову. У меня нет контроля над содержимым.h файла. Единственными гарантиями является то, что он может быть построен с помощью стандартного компилятора C++, и что #defines
будут добавлены, но никогда не будут удалены. Макро-формулы могут измениться в любое время.
Варианты, которые я вижу для этого:
- Реализовать частичный (или подключиться к существующему) компилятор C++ и перехватить значение макросов на этапе препроцессора.
- Используйте регулярные выражения для динамического построения исходного файла, который будет использовать все макросы, определенные в данный момент, затем скомпилируйте и выполните исходный файл, чтобы получить оцененную форму всех макросов. Каким-то образом (?) Пропустить макросы, которые не оцениваются как целочисленные константы времени компиляции. (Кроме того, не уверен, что регулярное выражение достаточно выразительно, чтобы охватить все возможные определения многострочных макросов)
Оба эти подхода добавили бы существенную сложность и хрупкость процессу сборки для этого проекта, которого я хотел бы избежать. Есть ли лучший способ оценить все #define
макросы в файле C++ .h?
Ниже приведен пример того, что я ищу для разбора:
#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
Неважно, в каком формате находится этот вывод, пока я могу работать с ним как со списком пар ключ-значение дальше по трубе.
3 ответа
Подход может состоять в том, чтобы написать "генератор программ", который генерирует программу (программу 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 затем приводит к окончательному выводу. Это разрешит все макросы, включая те, которые сами по себе используют другие макросы.
Посмотрите следующий скрипт оболочки и следующий код генератора программ, которые вместе реализуют этот подход:
Скрипт распечатывает значения #define-операторов в "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, ¯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?
Вот концепция, основанная на предположениях из пояснительного комментария.
- только один заголовок
- не включает
- нет зависимости от включаемого файла кода
- нет зависимости от ранее включенных заголовков
- нет зависимости от порядка включения
Основные требования в противном случае:
- не рисковать влиянием на двоичный процесс сборки (являясь частью, которая делает настоящий программный продукт)
- не пытайтесь эмулировать двоичный компилятор / парсер сборки
Как:
- сделать копию
- включить его из специального файла кода,
который содержит только "#include" copy.h ";
или непосредственно обработать заголовок
(это просто странно против моих привычек) - удалите все, кроме препроцессора и прагм, обращая внимание на продолжение строки
- заменить все "#define" на "HaPoDefine", кроме одного (например, первого)
- повторение
- Предварительная обработка включающего файла кода (большинство компиляторов имеют переключатель для этого)
- сохранить вывод
- превратить другой "HaPoDefine" обратно в "#define"
- пока не останется "HaPoDefine"
- собрать все макроразложения из дельт промежуточных сейвов
- откажитесь от всего, что не имеет отношения к делу
- поскольку последнее фактическое числовое значение, скорее всего, является результатом компилятора (а не препроцессора), используйте такой инструмент, как bashs "expr", чтобы вычислить значения для читабельности человеческого глаза,
будьте осторожны, чтобы не рисковать различиями в процессе двоичной сборки - использовать магию регулярных выражений для достижения любого желаемого формата
Вы можете использовать g++
или же gcc
с опцией -E, и работать с этим выводом?
-E остановка после этапа предварительной обработки; не запускайте компилятор должным образом. Вывод выполняется в виде предварительно обработанного исходного кода, который отправляется на стандартный вывод. Входные файлы, которые не требуют предварительной обработки, игнорируются.
С этим я представляю:
- Создать список всех
#define
ключи от источника - Запустите соответствующую команду ниже для исходного файла (ов), и пусть препроцессор GNU сделает свое дело
- Возьмите предварительно обработанный результат из 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