6

When doing embedded programming with C, many times I find myself doing maps with enum and array because they are fast and memory efficient.

enum {
    ID_DOG = 0,
    ID_SPIDER,
    ID_WORM,
    ID_COUNT
};
int const NumberOfEyes[ID_COUNT] = {
    2,
    8,
    0
};

Problem is that sometimes when adding/removing items, I make mistake and enum and array go out of sync. If initializer list is too long, compiler will detect it, but not other way around.

So is there reliable and portable compile time check that initializer list matches the length of the array?

Jens
  • 69,818
  • 15
  • 125
  • 179
user694733
  • 15,208
  • 2
  • 42
  • 68

2 Answers2

9

This is possibly a situation where X macros could be applied.

animals.x

X(DOG,    2)
X(SPIDER, 8)
X(WORM,   0)

foo.c

enum {
#define X(a,b) ID_##a,
#include "animals.x"
#undef X
};

int const numberOfEyes[] = {
#define X(a,b) b,
#include "animals.x"
#undef X
};

This not only guarantees that the lengths match, but also that the orders are always kept in sync.

Brooks Moses
  • 9,267
  • 2
  • 33
  • 57
Oliver Charlesworth
  • 267,707
  • 33
  • 569
  • 680
  • Excellent answer, this is the very problem I was struggling with before I discovered the power of x-macros. See http://stackoverflow.com/questions/6635851/real-world-use-of-x-macros – ACRL May 04 '12 at 12:50
  • I corrected an error in the answer; you had each of the lines in the animals.x file prefixed with #define, when they should be expanding the macro rather than (re)defining it. – Brooks Moses May 05 '12 at 19:48
  • Is it just me or do X-macros cause eye cancer to others as well? :-) How is `make` and auto-generation of dependencies affected? What about refactoring tools? Source code formatters like indent? – Jens May 05 '12 at 19:59
  • I am a big fan of x-macros but a colleague pointed out that they do make debugging a bit of a pain. If this is something where you're likely to need debug-ability, you could try using `cog` or similar instead. – Vicky May 05 '12 at 20:38
  • @Jens: X-macros are just a combination of header files and macros; if a tool can't cope with them, then it's arguably a failure of that tool. But I appreciate that debuggers or code IDE auto-completion often stumble over macros. That said, I haven't personally encountered a situation where this has been problematic (e.g. my auto-generated dependencies appear to work fine). – Oliver Charlesworth May 05 '12 at 20:40
  • @Vicky: I am not familiar with Cog, I'm going to look into that. Thanks for pointing it out! – Oliver Charlesworth May 05 '12 at 20:52
  • @Jens I agree with you and prefer the version used in your answer. Re-inventing the C language is almost always a bad idea. Your answer also has the advantage that it can be made MISRA-C-compliant, that isn't possible with X macros. – Lundin May 07 '12 at 08:37
  • +1 This is a really interesting approach. Will be useful for future projects. – user694733 May 10 '12 at 08:47
3

What about a compile time assertion like the following? (Yes, there are more elaborate CT_ASSERT macros; this is to illustrate the idea.)

#define CT_ASSERT(expr, name) typedef char name[(expr)?1:-1]

enum {
    ID_DOG = 0,
    ID_SPIDER,
    ID_WORM,
    ID_COUNT
};

int const NumberOfEyes[] = {
    2,
    8,
    0
};

CT_ASSERT (sizeof NumberOfEyes/sizeof *NumberOfEyes == ID_COUNT, foo);

Now when the NumberOfEyes array has more or less elements than ID_COUNT, this will cause an error along x.c:15: error: size of array 'foo' is negative. Negative array dimensions are a constraint violation that must be diagnosed by any C compiler out there.

Jens
  • 69,818
  • 15
  • 125
  • 179
  • The name parameter to the macro isn't necessary if you make sure the macro is declared at a local scope. `#define CT_ASSERT(expr) { typedef char ct_assert[(expr)?1:-1]; }`. – Lundin May 07 '12 at 08:33
  • True, but why restrict yourself to block scope? I have a number of file scope arrays that need checking as well. Using a well selected name will also give a clue in the compiler error message about what exactly is the problem, e.g. instead of `foo` I'd use `enum_mismatch_in_NumberOfEyes`. – Jens May 07 '12 at 09:21
  • 1
    Since the actual code using the array is not executed at file scope, simply place the assert in the code that is accessing the array. The advantages of doing so are many: the assert ends up encapsulated together with the relevant code, so only that code sees it. And you don't pollute the global namespace with the mysterious macro. Also, the compiler error message doesn't necessarily contain the variable name. In my compiler I just get something like "illegal constant expression", but it points out the exact line causing the problem, so it isn't really an issue. – Lundin May 07 '12 at 09:43
  • I chose this answer as it is reasonably easy to add to my existing code. – user694733 May 10 '12 at 08:44