Designators in c89

339 Views Asked by At

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

4

There are 4 best solutions below

0
nebuch On BEST ANSWER

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.

5
Rudy Velthuis On

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.

2
Kaz On
 #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.

0
technosaurus On

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.