vlad_tepesch vlad_tepesch - 3 years ago 171
C Question

Compare preprocessor macros for equality

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.

Answer Source

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.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download