4

I have some crude generated header from some .dbc files. Since a few of the messages represent elements from an array the structure is equal and so the generated Macros are equal. Since I fill some array of struct in the code I would like to save effort and use the same macro for all objects, but to ensure the definitions have not changed I would like to test at compile time if the macros are equal.

Example:

#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

Is this Possible? If there is some solution in C++ that may also help. But the macros are fixed.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
vlad_tepesch
  • 6,681
  • 1
  • 38
  • 80
  • No, because the preprocessor runs [in a separate phase](http://en.cppreference.com/w/c/language/translation_phases) from the rest of the compilation process, meaning it has no knowledge of things like variables or other run.-time things. – Some programmer dude Mar 02 '15 at 18:02
  • yes i know about the phases. i thought may be someone has some stringify hack. it is not important to correctly parse the expression. its ok if the comparisons fails if only white spaces are different since the generator will produce exact same code. – vlad_tepesch Mar 02 '15 at 18:06
  • No, because yours are function-style macros. You can do what you propose with the other kind, but you cannot expand function-style macros without the correct number of arguments. – John Bollinger Mar 02 '15 at 18:07
  • But if `buf` is filled in at runtime, how will the compile-time only preprocessor know about the data in it? – Some programmer dude Mar 02 '15 at 18:07
  • the content of the `buf` is not important. i want to test if the macros do the same with the `buf`, so that i can use the `PATTERN_01` macros on all the other `PATTERN_XX` messages. – vlad_tepesch Mar 02 '15 at 18:09
  • 5
    @JoachimPileborg, I think he wants to compare the macro definitions *without* substituting the argument. But no, you cannot do that. – John Bollinger Mar 02 '15 at 18:09
  • "if there is some solution in cpp that may also help" -- Is "cpp" supposed to refer to C++ or to the C preprocessor? – Keith Thompson Mar 02 '15 at 19:21
  • @KeithThompson sorry c++ edited – vlad_tepesch Mar 03 '15 at 08:12

2 Answers2

7

This is a horrible hack, but seems to work for your example for GCC and C11 at least:

#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);

You might need to pass -std=c11 or -std=gnu11, though the latter shouldn't be needed here.

Explanation:

  • STRINGIFY(x) returns the expansion of x as a string literal. We need to do the stringification in two steps using STRINGIFY_() because # suppresses macro expansion. (With one step we'd get "<x>" instead of "expanded version of <x>".)

  • GCC has a built-in version of strcmp() (__builtin_strcmp()) which is used here. It just happens to be able to compare constant strings at compile-time. The code breaks if you pass -fno-builtin (unless you explicitly use __builtin_strcmp()).

  • static_assert is a C11 compile-time assertion.

With the three ingredients above we can stringify the expanded macros (passing some dummy token that's likely to be unique for the argument) and compare the strings at compile-time.

Yes, this is a hack...

In C++11 there are safer ways to compare strings at compile time -- see e.g. this answer.

As a side note, you could do this at run-time too with zero overhead for GCC and Clang. (The version above won't work for Clang as it's pickier about strcmp(...) == 0 not being an integer constant expression as required by static_assert.) A run-time check like

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

gets completely optimized out when the macros are equal. Not even the strings are kept in the read-only data segment (just checked). It's a more robust approach if you can live with having to run the program to discover the problem.

Community
  • 1
  • 1
Ulfalizer
  • 4,664
  • 1
  • 21
  • 30
  • 1
    FYI, you can [use static assertions in pre-C11 standard C](http://stackoverflow.com/a/3385694/3079266) –  Mar 03 '15 at 09:52
  • @Mints97 Yeah, I'm aware that there's ways to hack compile-time assertions into earlier versions of C. Wanted to keep things reasonably simple. Maybe it would have been worth mentioning though. – Ulfalizer Mar 03 '15 at 09:58
  • 2
    While reading an unrelated answer I realized that just doing `STRINGIFY(m1(xxx)) == STRINGIFY(m2(xxx))` is likely to work in practice too. The reason is that compilers tend to merge identical string literals so that they end up at the same address, meaning you can just compare pointers. Don't use that though. :P – Ulfalizer Mar 03 '15 at 10:42
0

It possible to do this a bit better by using variadic macros to do the 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" )

This has two advantages: it works with macros which expand to tuples (e.g. #define FOO thing1, thing2), and it works with macros which take arguments (without a dummy token like xxx in the other solution). Note that the final expansions are compared, not the full expansion histories. So given these #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

All of these will trigger a compile-time error:

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);

While of these will compile ok:

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);
Britton Kerin
  • 427
  • 4
  • 9