2015-03-02 4 views
4

У меня есть грубый сгенерированный заголовок из некоторых файлов .dbc. Поскольку некоторые из сообщений представляют элементы из массива, структура равна и, следовательно, сгенерированные макросы равны. Поскольку я заполняю некоторый массив структуры в коде, я хотел бы сохранить усилия и использовать один и тот же макрос для всех объектов, но чтобы гарантировать, что определения не изменились, я бы хотел проверить во время компиляции, если макросы равны.Сравните макросы препроцессора для равенства

Пример:

#define GET_PATTERN_01_PATTERNPOINT02Y(buf) (0 \ 
    | (uint16)(-(uint16)((buf[7] >> 6) & 0x01) << 15) \ 
    | (uint8)(+(uint8)((buf[6] >> 0) & 0xff) << 0) \ 
    | (uint16)(+(uint16)((buf[7] >> 0) & 0x7f) << 8) \ 
) 

#define GET_PATTERN_02_PATTERNPOINT04Y(buf) (0 \ 
    | (uint16)(-(uint16)((buf[7] >> 6) & 0x01) << 15) \ 
    | (uint8)(+(uint8)((buf[6] >> 0) & 0xff) << 0) \ 
    | (uint16)(+(uint16)((buf[7] >> 0) & 0x7f) << 8) \ 
) 

#if GET_PATTERN_01_PATTERNPOINT02Y != GET_PATTERN_02_PATTERNPOINT04Y 
# error blah 
#endif 

Возможно ли это? Если в C++ есть какое-то решение, которое также может помочь. Но макросы фиксированы.

+0

Нет, потому что препроцессор запускает [в отдельной фазе] (http://en.cppreference.com/w/c/language/translation_phases) из остальной части процесса компиляции, то есть он не знает о таких вещах, как переменные или другие вещи времени выполнения. , –

+0

Да, я знаю о фазах. Я думал, может быть, у кого-то есть какой-то подрыв. не важно правильно разобрать выражение. его нормально, если сравнения не выполняются, если только белые пробелы различны, поскольку генератор будет выдавать точно такой же код. –

+0

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

ответ

7

Это ужасный хак, но, кажется, работает для вашего примера для GCC и С11, по крайней мере:

#include <assert.h> 
#include <string.h> 

... 

#define STRINGIFY(x) STRINGIFY_(x) 
#define STRINGIFY_(x) #x 

#define ASSERT_SAME(m1, m2)           \ 
    static_assert(strcmp(STRINGIFY(m1(xxx)), STRINGIFY(m2(xxx))) == 0, \ 
       #m1"() and "#m2"() differ!") 

ASSERT_SAME(GET_PATTERN_01_PATTERNPOINT02Y, GET_PATTERN_02_PATTERNPOINT04Y); 

Вам может понадобиться пройти -std=c11 или -std=gnu11, хотя последний не должен быть нужен здесь.

Объяснение:

  • STRINGIFY(x) возвращает расширение x как строковый литерал. Нам нужно сделать строение в два этапа, используя STRINGIFY_(), потому что # подавляет расширение макроса. (С одним шагом мы получим "<x>" вместо "expanded version of <x>".)

  • GCC имеет встроенную версию strcmp() (__builtin_strcmp()), который используется здесь. Просто бывает, что можно сравнивать постоянные строки во время компиляции. Код перерывается, если вы передаете -fno-builtin (если вы явно не используете __builtin_strcmp()).

  • static_assert - это утверждение времени компиляции C11.

С трех ингредиентов выше, мы можем stringify расширенные макросы (проходящие фиктивный маркер, который, вероятно, будет уникальным для аргумента) и сравните строки во время компиляции.

Да, это хак ...

В C++ 11 есть более безопасные способы для сравнения строк во время компиляции - смотри, например, this answer.

В качестве побочного примечания вы можете сделать это во время выполнения с нулевыми накладными расходами для GCC и Clang. (Версия выше, не будет работать на Clang, как это разборчивее о strcmp(...) == 0 не будучи постоянным выражением числа в соответствии с требованиями static_assert.) Проверкой времени выполнения, как

if (strcmp(STRINGIFY(m1(xxx)), STRINGIFY(m2(xxx))) != 0) { 
    *report error and exit* 
} 

получает полностью оптимизированный, когда макросы равны. Даже строки не хранятся в сегменте данных только для чтения (только что отмечен). Это более надежный подход, если вы можете жить с необходимостью запускать программу, чтобы обнаружить проблему.

+1

FYI, вы можете использовать статические утверждения в стандарте C до C11 (http://stackoverflow.com/a/3385694/3079266) – Mints97

+0

@ Mints97 Да, я знаю, что есть способы взломать утверждения во время компиляции в более ранние версии C. Требуется держать вещи достаточно простыми. Возможно, это было бы достойным упоминания. – Ulfalizer

+1

При чтении несвязанного ответа я понял, что просто выполнение 'STRINGIFY (m1 (xxx)) == STRINGIFY (m2 (xxx))', вероятно, также будет работать на практике. Причина в том, что компиляторы имеют тенденцию объединять идентичные строковые литералы, чтобы они попадали по одному и тому же адресу, то есть вы могли просто сравнить указатели. Не используйте это. :П – Ulfalizer

0

Это можно сделать немного лучше с помощью VARIADIC макросов сделать stringification:

#define STRINGIFY_VARIADIC(...) #__VA_ARGS__ 
#define EXPAND_AND_STRINGIFY_VARIADIC(...) STRINGIFY_VARIADIC (__VA_ARGS__) 

#define STATIC_ASSERT_IDENTICAL_EXPANSIONS(macro_a, macro_b) \ 
    _Static_assert (           \ 
     (             \ 
     __builtin_strcmp (         \ 
      EXPAND_AND_STRINGIFY_VARIADIC (macro_a),  \ 
      EXPAND_AND_STRINGIFY_VARIADIC (macro_b))  \ 
     == 0            \ 
     ),             \ 
     "expansions of " #macro_a " and " #macro_b " differ") 

Это имеет два преимущества: она работает с макросами, которые расширяют наборы (например #define FOO thing1, thing2), и он работает с макросами, которые принимают аргументы (без фиктивного маркера как xxx в другом решении). Обратите внимание, что сравниваются окончательные разложения, а не полные истории расширений. Поэтому, учитывая эти #defines:

#define FOO foo 
#define BAR bar 
#define ARG_DOUBLER(arg) arg, arg 
#define ARG_ITSELF(arg) arg 
#define OTHER_ARG_DOUBLER(arg) ARG_ITSELF (arg), ARG_ITSELF (arg) 
#define SECOND_ARG_NUKER(arg1, arg2) arg1 

Все это вызовет ошибку компиляции:

STATIC_ASSERT_IDENTICAL_EXPANSIONS (FOO, BAR); 
STATIC_ASSERT_IDENTICAL_EXPANSIONS (ARG_DOUBLER (x), ARG_DOUBLER (y)); 
STATIC_ASSERT_IDENTICAL_EXPANSIONS (x, ARG_ITSELF (y)); 
STATIC_ASSERT_IDENTICAL_EXPANSIONS (SECOND_ARG_NUKER (x, y), y); 

Хотя эти компилирует ОК:

STATIC_ASSERT_IDENTICAL_EXPANSIONS (FOO, foo); 
STATIC_ASSERT_IDENTICAL_EXPANSIONS (ARG_DOUBLER (x), ARG_DOUBLER (x)); 
STATIC_ASSERT_IDENTICAL_EXPANSIONS (x, ARG_ITSELF (x)); 
STATIC_ASSERT_IDENTICAL_EXPANSIONS (SECOND_ARG_NUKER (x, y), x); 
Смежные вопросы