c89 中的指示符

Designators in c89

C99 允许数组初始值设定项(以及其他)指定要使用正整数指示符设置数组的哪个元素($6.7.8.6、$6.7.8.17),例如:

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

我以前用它来制作一个枚举到字符串 table,如下所示:

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

但是,我现在的工作要求是我的代码符合 c89。

我研究了预处理器魔法(例如 here),但我需要字符串是任意的,而不是枚举符号的副本。

光做是不够的

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

因为我以后需要添加枚举元素。使用 c99 方法,这将导致 table 中出现 NULL 指针,如果它们成为问题,则很容易调试。如果我忘记使用此方法更新字符串 table,我会得到更难调试的段错误。如果我无论如何都必须记住偏移量,它也会破坏具有符号的意义。

如果声明在函数中,我可以像这样达到预期的效果:

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";

    /* ... */
}

但是,至少对于 gcc,这 不会 得到优化。

有没有办法在c89中声明这样一个字符串table? (组装没问题。)

简单老套的怎么样

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

换句话说,只要按正确的顺序排列即可。

char *foo_string = table[FOO];

当然,这只适用于像上面这样的简单枚举,不适用于样式中的枚举

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

但为此,您必须创建一个至少包含 313 个元素的数组,其中大部分元素无论如何都是 NULL,这将是一个非常浪费的构造。在这种情况下,当您使用 switch 结构时,编译器可以为您优化。


也看看S.O。 @Leandros 指出的问题:How to convert enum names to string in c。那里的答案使用宏来生成数组,确保条目的顺序正确。

或者,正如那个答案所说:

#define enum_str(s) #s

这完全摆脱了数组。

 #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");

符号和字符串是成对的。您不会轻易添加没有字符串的新符号,反之亦然。要添加一个元素,您必须向宏添加两个新参数——E3, S3——等等。那里没有什么可以保持同步的,只是 enum 有所有的 E-s 而数组有所有的 S-s。这几乎是不可能搞砸的。

在尝试了几种不同的技术之后,这个最容易维护:

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

这里聚集了一个条目的所有信息。要插入一个元素,你只需要修改它周围的东西。例如插入 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"
}

它有点丑(有点可爱,你知道吗?)但效果很好。

您可以使用 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

现在您的新数据可以作为一组枚举和字符串添加。如果您稍后决定根据枚举添加其他内容,则可以使用 'z' 参数轻松扩展到 OP() 甚至 ...__VA_ARGS__ 用于多个但数量不同的参数。