Оценить все макросы в заголовочном файле C++

У меня есть требование построить автоматизированную систему для анализа файла C++ .h с большим количеством операторов #define и сделать что-то со значением, которое работает каждый #define. В файле .h помимо операторов #define есть много другого мусора.

Цель состоит в том, чтобы создать список ключ-значение, где ключами являются все ключевые слова, определенные операторами #define, а значениями являются оценки макросов, которые соответствуют определениям. #defines определяют ключевые слова с помощью ряда вложенных макросов, которые в конечном итоге разрешаются в целочисленные константы времени компиляции. Некоторые из них не разрешаются в целочисленные константы времени компиляции, и их следует пропустить.

Файл .h со временем будет развиваться, поэтому инструмент не может быть длинной жестко закодированной программой, которая создает экземпляр переменной, равной каждому ключевому слову. У меня нет контроля над содержимым файла .h. Единственная гарантия состоит в том, что его можно собрать с помощью стандартного компилятора C++, и что дополнительные #defines будут добавлены, но никогда не будут удалены. Формулы макросов могут измениться в любое время.

Варианты, которые я вижу для этого, следующие:

  1. Реализуйте частичный (или подключитесь к существующему) компилятору C++ и перехватите значение макросов на этапе препроцессора.
  2. Используйте регулярные выражения для динамического создания исходного файла, который будет использовать все макросы, определенные в настоящее время, затем скомпилируйте и выполните исходный файл, чтобы получить оцененную форму всех макросов. Каким-то образом (?) пропустить макросы, которые не оцениваются как целочисленные константы времени компиляции. (Кроме того, не уверен, что регулярное выражение достаточно выразительно, чтобы захватить все возможные определения многострочных макросов)

Оба этих подхода значительно усложнили бы процесс сборки этого проекта, чего я хотел бы избежать. Есть ли лучший способ оценить все макросы #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

На самом деле не имеет значения, в каком формате этот вывод, пока я могу работать с ним как со списком пар ключ-значение дальше по конвейеру.


person Techrocket9    schedule 16.03.2017    source источник
comment
Просто используйте существующий препроцессор C, чтобы помочь вам. Обычный GNU cpp с опцией -dU должен приблизить вас к желаемому результату.   -  person Matteo Italia    schedule 16.03.2017
comment
Почему CONSTANT_1 и CONSTANT_2 в выводе, а MAGIC_NUMBER_BASE, MAGIC_NUMBER, MORE_MAGIC_1, MORE_MAGIC_2 нет? Похоже, они соответствуют вашим критериям (определяют, какие из них разрешают целочисленные константы времени компиляции), по крайней мере, так же, как и два других.   -  person Ben Voigt    schedule 16.03.2017
comment
@BenVoigt Они должны быть в выводе, я сейчас исправлю.   -  person Techrocket9    schedule 16.03.2017
comment
@MatteoItalia Я не получаю ожидаемых выходных значений, когда использую флаг -dU в cpp. Мне нужен еще один флаг?   -  person Techrocket9    schedule 17.03.2017
comment
Извините, он выводит только макросы, которые используются. Вам понадобится что-то вроде -dM, но без предопределенных макросов (-dD делает это, но также печатает обработанный вывод).   -  person Matteo Italia    schedule 17.03.2017
comment
@MatteoItalia Я мог бы отфильтровать предопределенные макросы, но даже с -dM он не расширяет #define для CONSTANT_1 и 2. Он просто выводит всю строку #define с MAKE_CONSTANT и всем остальным.   -  person Techrocket9    schedule 17.03.2017
comment
Да, это прискорбно, я помнил, что эти варианты были немного умнее.   -  person Matteo Italia    schedule 17.03.2017
comment
@ Techrocket9: Проблема в том, что правила препроцессора делают совсем не то, что вы хотите. Макросы, используемые макросами, расширяются, когда используется внешний макрос, а не там, где он определен. Таким образом, CONSTANT_1 не является интегральным выражением времени компиляции, пока оно не используется... и возможно, что некоторые из них используются, а некоторые нет.   -  person Ben Voigt    schedule 17.03.2017
comment
@BenVoigt, хорошо, так что настройте определение так, чтобы оно разрешало целочисленную константу времени компиляции при использовании в выражении вида 'int n = CONSTANT_N;'   -  person Techrocket9    schedule 17.03.2017
comment
поэтому инструмент не может быть длинной жестко закодированной программой, которая создает экземпляр переменной, равной каждому ключевому слову Я не понимаю, почему это такая плохая идея.   -  person R Sahu    schedule 21.03.2017
comment
@RSahu Потому что жестко закодированный список быстро устареет при изменении файла .h. Если список генерируется динамически, то этот подход подходит (это более или менее вариант 2 выше), но параметры этого проекта не допускают вмешательства человека при изменении файла .h.   -  person Techrocket9    schedule 21.03.2017
comment
@Techrocket9, теоретически это проблема. На практике может и не быть. В конце концов, вы не добавляете макросы в заголовочный файл на регулярной основе.   -  person R Sahu    schedule 21.03.2017
comment
@RSahu Перед тем, как сделать этот пост, я представил это решение команде проекта, но оно было отклонено из-за того, что человеку требовалось обновить жестко закодированный файл. Мне нужно полностью автоматизированное решение.   -  person Techrocket9    schedule 21.03.2017
comment
@Techrocket9, достаточно честно. Надеюсь, вы сможете найти решение. Удачи.   -  person R Sahu    schedule 21.03.2017
comment
Описанные вами варианты кажутся (в сочетании) частью работоспособного решения вашего вопроса, как задано. Я бы не беспокоился о том, что эти параметры делают процесс сборки более сложным или ненадежным - требование делать такие вещи делает это само по себе. Меня беспокоит то, что этот вопрос является примером проблемы XY (необходимо сделать X, кто-то решает, что необходимо сделать Y для достижения X, возникает вопрос о том, как сделать Y, никто не может дать стоящий ответ или предложить полезные альтернативы, потому что фактического упоминания X в вопросе нет).   -  person Peter    schedule 24.03.2017
comment
@Peter X заполняет и обновляет таблицу поиска в БД, чтобы, когда мы получаем запись с числовым идентификатором, ее можно было соединить с данными в БД, предоставленными другими командами, которые связаны только с именем. Соглашение состоит в том, что имя является #define в этом заголовочном файле с определением, разрешающим идентификатор. Единственным авторитетным источником в компании пар имен и идентификаторов является этот заголовочный файл, который используется и модифицируется несколькими командами по всему миру.   -  person Techrocket9    schedule 24.03.2017
comment
Тогда я бы сказал, что вам нужно изменить подход. Разрешите командам заполнять БД и создайте программу, которая генерирует заголовочный файл из имен в БД. В make-файле все, что вам нужно сделать, это установить зависимость между файлом заголовка и базой данных, чтобы при изменении базы данных заголовок создавался заново. Если другие зависимости установлены соответствующим образом, все объекты, зависящие от заголовка, будут перестроены.   -  person Peter    schedule 24.03.2017
comment
@Peter, я согласен с вами в том, что то, как это делается, далеко не идеально, как и команда, которой принадлежит заголовочный файл. Однако, когда мы попросили их рассмотреть альтернативные подходы к этой проблеме с авторитетом данных, нам сказали, что они хотели бы решить эту проблему, но у них слишком много более приоритетных задач, и эта проблема находится в списке невыполненных работ по техническому долгу, а дата ее выполнения неизвестна. Таким образом, моя команда застряла с этой неортодоксальной проблемой синтаксического анализа.   -  person Techrocket9    schedule 24.03.2017
comment
Boost Wave — это препроцессор, предназначенный для встраивания в другое программное обеспечение, поэтому, если вы не хотите вызывать для этого компилятор, вы можете использовать Boost Wave как отдельную программу.   -  person Jerry Jeremiah    schedule 27.03.2017


Ответы (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 requiredcout`-операторов. Компиляция и выполнение сгенерированной программы 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, &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.txt и defines.cpp не нужны; Однако для демонстрационных целей они полезны. Применительно к вашему заголовочному файлу содержимое defines.txt и defines.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?
person Stephan Lechner    schedule 22.03.2017
comment
Это довольно близко к реализации, над которой я работал в powershell и MSVC. Есть ли существенное преимущество в печати macroValue в каждом операторе печати, а не только в macroName снова? - person Techrocket9; 28.03.2017
comment
Нет существенного преимущества; еще немного документации в файле cpp. - person Stephan Lechner; 28.03.2017

Вот концепция, основанная на предположениях из поясняющего комментария.

  • только один заголовок
  • не включает
  • нет зависимости от включаемого файла кода
  • нет зависимости от ранее включенных заголовков
  • нет зависимости от порядка включения

В противном случае основные требования:

  • не рискуют повлиять на процесс сборки бинарного файла (будучи частью, из которой состоит реальный программный продукт)
  • не пытайтесь эмулировать компилятор/парсер бинарной сборки

Как:

  • сделать копию
  • включить его из специального файла с кодом,
    который содержит только "#include "copy.h";
    или напрямую предварительно обработать заголовок
    (это просто странно противоречит моим привычкам)
  • удалить все, кроме препроцессора и прагм, обращая внимание на продолжение строки
  • замените все "#define" на "HaPoDefine", кроме одного (например, первого)
  • repeat
    • preprocess the including code file (most compiler have a switch to do this)
    • сохранить вывод
    • превратить еще один "HaPoDefine" обратно в "#define"
  • пока не останется "HaPoDefine"
  • собрать все расширения макросов из дельт промежуточных сохранений
  • отбросить все, что не имеет значения
  • поскольку окончательное фактическое числовое значение, скорее всего, является результатом работы компилятора (а не препроцессора), используйте такой инструмент, как bashs "expr", для вычисления значений, понятных человеческому глазу,
    будьте осторожны, чтобы не рисковать различиями в процессе сборки двоичных файлов.
  • используйте некоторую магию регулярных выражений для достижения любого желаемого формата
person Yunnosch    schedule 22.03.2017
comment
Чтобы уточнить: по крайней мере, на данный момент это один файл заголовка, который мне нужно разобрать, поэтому мне не нужно беспокоиться о включении. Файл .h используется и модифицируется многими командами, использующими несколько компиляторов (GCC и MSVC, о которых я знаю), и должен оставаться жалобой на C++/14. - person Techrocket9; 22.03.2017
comment
Файл должен оставаться совместимым только для процесса двоичной сборки. Для процесса создания документа, и только временно, он может проходить через итерации совершенно индивидуального контента. Обработанные версии также могут иметь другое имя и/или расширение. На самом деле расширение .i весьма вероятно. Это всего лишь один файл, что упрощает задачу. Но будьте осторожны, думая об одном заголовке. Содержимое заголовка всегда рассматривается с точки зрения компилируемого файла кода. И содержимое может различаться между файлами кода (например, концепции AUTOSAR). Вам повезло, если ваши системы проще. - person Yunnosch; 22.03.2017

Можете ли вы использовать g++ или gcc с опцией -E и работать с этим выводом?

-E Остановить после этапа предварительной обработки; не запускайте собственно компилятор. Вывод представляет собой предварительно обработанный исходный код, который отправляется на стандартный вывод. Входные файлы, не требующие предварительной обработки, игнорируются.

При этом я представляю:

  1. Создайте список всех ключей #define из источника
  2. Запустите соответствующую команду ниже для исходного файла (файлов) и позвольте препроцессору 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

person kmiklas    schedule 24.03.2017
comment
ни gcc -E, ни g++ -E на самом деле не расширяют макрос в заголовочном файле примера в моем посте. - person Techrocket9; 27.03.2017