1

C99 allows array initializers (among others) to specify which element of the array is being set with a positive integer designator ($6.7.8.6, $6.7.8.17), for example like so:

const char *foo[] = {[2] = "foo", [1] = "bar", [0] = "baz"};

I have previously used this to make an enum-to-string table like so:

enum {THING_FOO = 0, THING_BAR, THING_BAZ};
const char *table[] = {
    [THING_FOO] = "foo",
    [THING_BAR] = "bar",
    [THING_BAZ] = "baz"
}

However, I am now working under the requirement that my code is c89 compliant.

I have looked into preprocessor magic (as in here, for example) but I need the strings to be arbitrary, not copies of the enum symbols.

It isn't sufficient to just do

enum {THING_FOO = 0, THING_BAR, THING_BAZ};
const char *table[] = {"foo", "bar", "baz"};

because I will need to add enum elements in the future. Using the c99 method, this would result in NULL pointers in the table which are acceptably easy to debug if they become problems. If I forgot to update the string table using this method, I'd get segfaults which are harder to debug. Also it defeats the point of having symbols if I have to remember offsets anyway.

If the declaration were in a function, I could achieve the desired effect like this:

enum {THING_FOO = 0, THING_BAR, THING_BAZ, NUM_THINGS};
void foo(void)
{
    static const char *table[NUM_THINGS];
    table[THING_FOO] = "foo";
    table[THING_BAR] = "bar";
    table[THING_BAZ] = "baz";

    /* ... */
}

However, at least with gcc, this does not get optimized.

Is there any way of declaring such a string table in c89? (It's no problem in assembly.)

Community
  • 1
  • 1
nebuch
  • 6,475
  • 4
  • 20
  • 39
  • Possible duplicate of [How to convert enum names to string in c](http://stackoverflow.com/questions/9907160/how-to-convert-enum-names-to-string-in-c) – Leandros Jul 23 '16 at 21:25
  • 1
    There are many ways, chose the way which you think is the least ugly way. There are many, many ways, one worse than the other. – Leandros Jul 23 '16 at 21:26
  • To your question, nothing as elegant as C99 or later will leave the gaps you exploit between enum values. I am a bit confused, however. Since you clearly have to change the code anyway, which did you think would take *less* time: a "huge switch statement", or posting this question, and *still* having to change the code everywhere regardless? – WhozCraig Jul 23 '16 at 21:32
  • if this array `const char *foo[] = {"foo", [9999] = "baz"};` will have 10000 elements allocated then your method is not practically suitable for enum-to-string cases - you need to come up with something else anyway. – c-smile Jul 23 '16 at 21:40
  • @c-smile: I know, I was just demonstrating the designator for clarity. The enum-string table is always densely packed in my use case. – nebuch Jul 23 '16 at 22:35
  • @Leandros: I've seen that; it's what I meant by preprocessor magic. – nebuch Jul 23 '16 at 22:36
  • Basically, designated initializers are one of the best features of C99. I'd point you to an answer where they're discussed, but it seems to have gone AWOL, likely because the question wasn't really suited to modern SO standards. There isn't a simple way around their absence; you simply have to get it right, somehow. That's why they're such a great feature; they make that which was hard before much, much easier. – Jonathan Leffler Jul 24 '16 at 01:37
  • 1
    To avoid the problem of forgetting to update the string table, use a compile-time assert that the dimension of the table matches the number of enumerators – M.M Aug 24 '16 at 03:32
  • It sounds like you are looking for X-Macros. – technosaurus Aug 24 '16 at 04:48

4 Answers4

3
 #define DEF_FOO_ENUM(E0, S0, E1, S1, E2, S2) \
   enum foo                { E0, E1, E2 };    \
   const char *foo_str   = { S0, S1, S2 };

 DEF_FOO_ENUM(THING_FOO, "foo",
              THING_BAR, "bar",
              THING_BAZ, "baz");

The symbols and strings are paired. You're not easily going to add a new symbol without a string, or vice versa. To add an element, you have to two new arguments to the macro—E3, S3—and so on. There is nothing to keep in sync there, just that the enum has all the E-s and the array has all the S-s. This is almost impossible to screw up.

Kaz
  • 55,781
  • 9
  • 100
  • 149
  • Some of the tables have upwards of 100 elements. I like this solution but that'll work out to a lot of Es and Ss. – nebuch Jul 23 '16 at 23:34
  • 3
    I would either do code generation, or else validation with some external tool. That is to say, a text processing utility which specifically understands these declarations (which are hand-maintained) and checks their integrity. – Kaz Jul 24 '16 at 00:48
2

What about the simple, old fashioned

const char* table[] = { "foo", "bar", "baz" };

In other words, just put them in the correct order.

char *foo_string = table[FOO];

Of course, that only works for simple enums like the above, not for enums in the style

enum { FOO = 13; BAR = 15, BAZ = 312 };

But for that, you would have to create an array with at least 313 elements, most of which are NULL anyway, which would be a pretty wasteful construct. In such cases, the compiler can optimize this for you, when you use a switch construct.


Also take a look at the S.O. question @Leandros pointed to: How to convert enum names to string in c. The answer there uses macros to generate the array, which ensures the entries are in the correct order.

Or, as that answer says:

#define enum_str(s) #s

Which gets rid of the array altogether.

Community
  • 1
  • 1
Rudy Velthuis
  • 28,387
  • 5
  • 46
  • 94
  • The problem with the other answer is it doesn't allow one to assign an arbitrary string to each enum element; I'll clarify in the question. – nebuch Jul 23 '16 at 22:38
  • Ok, you only get the names in the source code. So FOO is not linked to "foo", it is automatically "FOO". Then arrays are one solution. Or, say, hash tables. But the latter are not so simple anymore. Then a switch construct is probably easier, especially since enums seldom have hundreds of elements or more. – Rudy Velthuis Jul 23 '16 at 23:41
  • I just saw you have these huge enums. Then things get either complicated or hard to handle. Why are these enums so large? – Rudy Velthuis Jul 23 '16 at 23:52
  • They are used to classify memory layouts, of which there are many. I'll probably end up restructuring things just so I can avoid this problem, :L – nebuch Jul 23 '16 at 23:59
  • Ok. In all the years I have been programming (since approx. 1980) I have never seen enums with more than, say, 40 elements. That is why I wondered about the sizes you mention. Most of the ones I know have fewer than 10 elements. – Rudy Velthuis Jul 24 '16 at 00:02
2

You can keep them together by using X-Macros:

#define MYXMACRO(OP) \
   OP(ENUM_FOO, "foo") \
   OP(ENUM_BAR, " bar") \
   OP(ENUM_BAZ, "baz")

/* use the first parameter to set up your enums*/
enum {
#define AS_ENUMS(x,y) x,
MYXMACRO(AS_ENUMS)
#undef AS_ENUMS  /*not required, just playing nice*/
NUMTHINGS
};

/* use the 2nd parameter to set up your strings*/
const char *strings[] = {
#define AS_STRINGS(x,y) y,
MYXMACRO(AS_STRINGS)
#undef AS_STRINGS
};
#undef MYXMACRO

Now your new data can be added as a set of enum and string. If you later decided to add something else based on the enums it's easily extended with a 'z' parameter to OP() or even ... and __VA_ARGS__ for multiple but varying number of parameters.

technosaurus
  • 7,676
  • 1
  • 30
  • 52
1

After trying a few different techniques, this one is easiest to maintain:

const char *table[] = {
#define FOO 0
    "foo",
#define BAR (FOO + 1)
    "bar",
#define BAZ (BAR + 1)
    "baz"
}

Here all the information about an entry is clustered. To insert an element you only have to modify stuff right around it. For example to insert qux:

const char *table[] = {
#define FOO 0
    "foo",
#define QUX (FOO + 1)    /* new */
    "qux",               /* new */
#define BAR (QUX + 1)    /* modified */
    "bar",
#define BAZ (BAR + 1)
    "baz"
}

It's a bit ugly (kind of endearing, you know?) but it works good.

nebuch
  • 6,475
  • 4
  • 20
  • 39