Macro to generate an enum and an array of strings
Most macro solutions are not readable nor maintainable. You should avoid macro solutions like this.

The best way is to not use macros at all:

typedef enum
{
  ZERO,
  ONE, 
  TWO, 
  THREE,
  NUMBERS_N
} numbers_t;

static const char* NUMBER_STR[NUMBERS_N] = 
{
  [ZERO]  = "ZERO",
  [ONE]   = "ONE",
  [TWO]   = "TWO",
  [THREE] = "THREE",
};

puts(NUMBER_STR[1]); // prints ONE


This code is perfectly readable and it maintains the integrity between the enum and the array well. It only has one small problem and that is code repetition. 

Code repetition should be avoided, but programmers tend to exaggerate how bad it is. It is rarely ever so bad that it justifies turning your whole program into "macro hell". 

The reasons why code repetition should be avoided is that is leads to typo-like bugs and problems with maintenance. However, while a "macro hell" solution might rule out the typos, it makes the code difficult to maintain and the increased complexity increases the chance of other more serious bugs.

Famous quote by Brian Kernighan:


  Everyone knows that debugging is twice as hard as writing a program in the first place. So if you're as clever as you can be when you write it, how will you ever debug it?


That being said, there are valid cases where you must centralize data in one place, particularly when maintaining existing code that shouldn't be changed more than necessary. The macro you posted is a flavour of "X macros", which is the preferable way to write such macros - "messy in a standardized way" so to speak. X macros is about defining a list of data as you do, but to pass on that data to localized macros embedded into the code itself. To rewrite the above code with X macros, you'd do:

#define NUMBER_LIST \
  X(ZERO)           \
  X(ONE)            \
  X(TWO)            \
  X(THREE)          \

typedef enum
{
  #define X(name) name,
    NUMBER_LIST
  #undef X
  NUMBERS_N
} numbers_t;

static const char* NUMBER_STR[NUMBERS_N] = 
{
  #define X(name) [name] = #name,
    NUMBER_LIST
  #undef X
};

puts(NUMBER_STR[1]); // prints ONE


The advantage here is the flexibility to apply the same data in different ways, on case-by-case basis. A macro such as #define X(name) [name] = #name, is cryptic by itself, but when given the context of the surrounding array initializer list, one can easier understand the meaning. "X macros" can also be used to manually unroll loops:

#define X(name) puts(NUMBER_STR[name]);
  NUMBER_LIST
#undef X


This is equivalent to iterating over the array and printing all items, but the loop is unrolled and we end up with a number of puts calls.