【问题标题】:Are there any compiler / preprocesser tricks to debug print an enum's name?是否有任何编译器/预处理器技巧来调试打印枚举名称?
【发布时间】:2013-03-05 22:04:09
【问题描述】:

我经常发现自己在编写帮助调试器方法,它返回一个可打印的字符串,给定一些枚举值。原因是当你通常记录一个枚举时,你得到的只是一个数字。我讨厌不得不回到我的源头来弄清楚那个枚举是什么。所以我会做类似的事情

typedef enum
{
   kOne = 1,
   kTwo,
   kThree,
}
MyEnum;

NSString *debugStringForEnum(MyEnum e)
{
    switch ( e )
        case kOne:
            return @"One";
        case kTwo:
            return @"Two";
        ....
}

....
NSLog(@"My debug log: %@", debugStringForEnum(someVariable));

所以我的问题是,有没有办法避免编写所有这些帮助代码,只是为了查看枚举的标签值?

谢谢

【问题讨论】:

  • 据我所知,没有办法做到这一点,除非像你所做的那样编写自己的方法。
  • 不幸的是这是不可能的,因为枚举名称在编译后不可用。
  • Convert objective-c typedef to its string equivalent 的可能副本。您可以使用其中一种建议的方法。
  • @ACB:该链接中的几乎每个建议都是我已经在做的同样糟糕的方法。
  • @darren,是的,这是因为正如我所提到的,无法访问这些枚举名称。所以你必须使用其中一种方式。

标签: c debugging clang


【解决方案1】:

如果您愿意编写让其他开发人员哭泣的“顽皮”代码,那么可以。试试这个:

#define ENUM(name, ...) typedef enum { M_FOR_EACH(ENUM_IDENTITY, __VA_ARGS__) } name; \
char * name ## _DEBUGSTRINGS [] = { M_FOR_EACH(ENUM_STRINGIZE, __VA_ARGS__) };

#define ENUM_IDENTITY(A) A,
#define ENUM_STRINGIZE(A) #A,

ENUM(MyEnum,
        foo, bar, baz, boo
        )

您显然需要一个 for-each 宏来完成这项工作。这是一个简单的:

#define M_NARGS(...) M_NARGS_(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define M_NARGS_(_10, _9, _8, _7, _6, _5, _4, _3, _2, _1, N, ...) N

#define M_CONC(A, B) M_CONC_(A, B)
#define M_CONC_(A, B) A##B

#define M_FOR_EACH(ACTN, ...) M_CONC(M_FOR_EACH_, M_NARGS(__VA_ARGS__)) (ACTN, __VA_ARGS__)

#define M_FOR_EACH_0(ACTN, E) E
#define M_FOR_EACH_1(ACTN, E) ACTN(E)
#define M_FOR_EACH_2(ACTN, E, ...) ACTN(E) M_FOR_EACH_1(ACTN, __VA_ARGS__)
#define M_FOR_EACH_3(ACTN, E, ...) ACTN(E) M_FOR_EACH_2(ACTN, __VA_ARGS__)
#define M_FOR_EACH_4(ACTN, E, ...) ACTN(E) M_FOR_EACH_3(ACTN, __VA_ARGS__)
#define M_FOR_EACH_5(ACTN, E, ...) ACTN(E) M_FOR_EACH_4(ACTN, __VA_ARGS__)
#define M_FOR_EACH_6(ACTN, E, ...) ACTN(E) M_FOR_EACH_5(ACTN, __VA_ARGS__)
#define M_FOR_EACH_7(ACTN, E, ...) ACTN(E) M_FOR_EACH_6(ACTN, __VA_ARGS__)
#define M_FOR_EACH_8(ACTN, E, ...) ACTN(E) M_FOR_EACH_7(ACTN, __VA_ARGS__)
#define M_FOR_EACH_9(ACTN, E, ...) ACTN(E) M_FOR_EACH_8(ACTN, __VA_ARGS__)
#define M_FOR_EACH_10(ACTN, E, ...) ACTN(E) M_FOR_EACH_9(ACTN, __VA_ARGS__)

应该很明显如何扩展该循环以具有更长的上限,但是...此答案的空间考虑。只要您愿意将额外的迭代复制并粘贴到该位中,循环就可能存在。

对于非调试版本,让#ifdef 选择不带第二行的 ENUM 版本。


编辑:为了从 teppic 窃取指定初始化器的想法,这里有一个更可怕的版本,它也适用于无序初始化器值:

#define ENUM(name, ...) typedef enum { M_FOR_EACH(ENUM_ENAME, __VA_ARGS__) } name; \
char * name ## _DEBUGSTRINGS [] = { M_FOR_EACH(ENUM_ELEM, __VA_ARGS__) };

#define ENUM_ENAME(A) M_IF(M_2ITEMS(M_ID A), (M_FIRST A = M_SECOND A), (A)),
#define ENUM_ELEM(A) M_IF(M_2ITEMS(M_ID A), ([M_FIRST A] = M_STR(M_FIRST A)), ([A] = M_STR(A))),

#define M_STR(A) M_STR_(A)
#define M_STR_(A) #A
#define M_IF(P, T, E) M_CONC(M_IF_, P)(T, E)
#define M_IF_0(T, E) M_ID E
#define M_IF_1(T, E) M_ID T
#define M_2ITEMS(...) M_2I_(__VA_ARGS__, 1, 0)
#define M_2I_(_2, _1, N, ...) N
#define M_FIRST(A, ...) A
#define M_SECOND(A, B, ...) B
#define M_ID(...) __VA_ARGS__

ENUM(MyEnum,
        foo, bar, baz, boo
        )

ENUM(NotherEnum,
        A, B, (C, 12), D, (E, 8)
        )

如果你在别人必须维护的代码中使用这种东西,我不能保证你的人身安全。

【讨论】:

  • Eww。不过,您会为该帖子赢得 +1 互联网 :)
  • 我在这里写了一个如何宏化函数部分的例子:stackoverflow.com/a/14931957/1162141
  • 好吧,如果人们愿意投票,我很想提交一个涉及检查可执行文件以获取调试信息的答案......
  • @darren:我确实做了一些实验。启动gdb 并尝试ptype MyEnum 作为概念证明。如果您愿意添加一个编译时步骤,您可以制作gdb info types 加上一个自动生成所有枚举解码的sed 脚本。由于某种原因,我正在测试的 Linux 系统没有显示 typedef names,尽管它可以在其他系统上运行。
【解决方案2】:

一种更简单的方法是设置一个字符串字面量数组,根据标签在数组中的位置复制标签,例如

char *enumlabels[] = { NULL, "KOne", "KTwo", "KThree"};

这里你需要填补空白以使枚举值与数组位置匹配。

或者,对于带有指定初始化程序的 C99,更好:

char *enumlabels[] = { [1] = "KOne", [2] = "KTwo", [3] = "KThree"};

在这种情况下,只要枚举声明在前,您可以直接将数组下标交换为枚举值以使其更清晰,例如{ [kOne] = "kOne", ... }.

那么对于MyEnum e,您可以只使用printf("%s\n", enumlabels[e]); 或类似的。

我编写了一些代码来更好地演示这一点:

typedef enum {
   white = 1,
   red   = 2,
   green = 4,
   blue  = 8,
   black = 16
} Colour;   // be sure to update array below!

char *enum_colours[] = { [white] = "white", [red] = "red", [green] = "green",
                         [blue] = "blue", [black] = "black" };

Colour c = blue;
printf("The label for %d is %s\n", c, enum_colours[c]);

Output: The label for 8 is blue

如果你有巨大的枚举常量(例如 32767),这显然不是一个理想的解决方案,因为需要数组的大小。如果没有指定的初始化程序,您可以直接分配数组值,如果更费力的话,可以使用 enum_colours[white] = "white"; 等,但只能在函数中。

【讨论】:

  • 我喜欢这个解决方案,除了我担心当有人向枚举添加另一个元素但忘记扩展描述数组时会发生什么......
  • 将它们放在一个结构中,以便它们都需要一起更新。
  • 这假设枚举值从零开始,但情况并非总是如此。它可以定义为从任意数字开始。
  • 除非另有说明,否则枚举值确实从零开始。如果枚举值以随机值分布,那就更麻烦了。
【解决方案3】:

您无法在运行时获取枚举名称,因为那时那些符号早已不复存在,但您可以使用多字符常量为您的枚举创建更有意义的值。

#import <Foundation/Foundation.h>

NSString* debugString(int en) {
    return [NSString stringWithFormat:@"%c%c%c%c",
            (en>>24) & 0xff,
            (en>>16) & 0xff,
            (en>>8) & 0xff,
            en & 0xff];
}

typedef enum {
    kOne = 'One.',
    kTwo = 'Two.',
    kThree = 'Thre',
} MyEnum;

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        NSLog(@"kOne = %@", debugString(kOne));
        NSLog(@"kTwo = %@", debugString(kTwo));
        NSLog(@"kThree = %@", debugString(kThree));
    }
    return 0;
}

将打印

kOne = 一。 kTwo = 二。 k三 = 三

在控制台上。

为了防止debugString 产生垃圾,每个枚举必须恰好是四个字符长(无论如何在 OSX 上)。这非常依赖于编译器和平台。它对调试很有用,但除此之外就没什么了。

当然,如果您需要枚举具有特定值或相互关联的值,这将不起作用。

【讨论】:

  • 这是个好主意,我不知道你为什么会有垃圾数据,除非你没有任何大于 16 位的成员......
【解决方案4】:

名称本身不可用,但通常给它们一个明确的数字就足够了:

enum{
   dog = 100,
   cat = 200,
   problem = dog+cat, //trailing comma legal in C and C++11
};

【讨论】:

  • 后面的逗号是合法的,C++也是。
猜你喜欢
  • 2020-05-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-17
  • 2019-10-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多