简体   繁体   English

是否有任何编译器/预处理器技巧来调试打印枚举的名称?

[英]Are there any compiler / preprocesser tricks to debug print an enum's name?

I often find myself writing helper debugger methods that return a printable string, given some enum value.我经常发现自己编写了一些辅助调试器方法,这些方法返回一个可打印的字符串,给定一些枚举值。 The reason for this is when you typically log an enum, all you get is a number really.原因是当您通常记录枚举时,您得到的只是一个数字。 I hate having to go back to my source to figure out what that enum is.我讨厌不得不回到我的来源来弄清楚那个枚举是什么。 So I would do something like所以我会做类似的事情

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

So my question is, is there any way to avoid writing all this helper code, just to see the label value of enum?所以我的问题是,有什么方法可以避免编写所有这些帮助程序代码,只是为了查看枚举的标签值?

Thanks谢谢

If you're willing to write "naughty" code that makes other developers cry, then yes.如果您愿意编写让其他开发人员哭泣的“顽皮”代码,那么可以。 Try this:试试这个:

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

You obviously need a for-each macro to make this work.您显然需要一个 for-each 宏来完成这项工作。 Here's a simple one:这是一个简单的:

#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__)

It should be obvious how to extend that loop to have a longer upper limit, but... space considerations for this answer.如何扩展该循环以获得更长的上限应该是显而易见的,但是......这个答案的空间考虑因素。 The loop can potentially be as long as you're willing to copy-and-paste extra iterations into this bit.只要您愿意将额外的迭代复制并粘贴到这一位,循环就可能存在。

For the non-debug build, have an #ifdef choose a version of ENUM without the second line.对于非调试版本,使用 #ifdef 选择一个没有第二行的 ENUM 版本。


EDIT: To steal the designated initialisers idea from teppic, here's an even more horrific version that also works with non-ordered initialiser values:编辑:为了从 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)
        )

I cannot guarantee your personal safety if you use this kind of thing in code someone else has to maintain.如果您在其他人必须维护的代码中使用这种东西,我无法保证您的人身安全。

A simpler method is to set up an array of string literals that replicate the labels according to their position in the array , eg一个更简单的方法是设置一个字符串文字数组,根据它们在数组中的位置复制标签,例如

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

Here you'd need to fill in the gaps to make the enum values match the array positions.在这里,您需要填补空白以使枚举值与数组位置匹配。

Or, better, for C99 with designated initialisers:或者,对于带有指定初始化程序的 C99 更好:

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

In this case, as long as the enum declaration comes first, you can swap the array subscripts directly for the enum values to make it much clearer, eg { [kOne] = "kOne", ... } .在这种情况下,只要 enum 声明在前,您就可以直接将数组下标替换为 enum 值以使其更加清晰,例如{ [kOne] = "kOne", ... }

then for MyEnum e you could just use printf("%s\\n", enumlabels[e]);那么对于MyEnum e你可以只使用printf("%s\\n", enumlabels[e]); or some such.或一些这样的。

I've written a bit of code to demonstrate this much better:我写了一些代码来更好地证明这一点:

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

If you have huge enum constants (eg 32767) this is obviously not an ideal solution, due to the size of array required.如果您有巨大的枚举常量(例如 32767),这显然不是理想的解决方案,因为需要数组的大小。 Without designated initialisers, you could assign the array values directly, if more laboriously, with enum_colours[white] = "white";如果没有指定的初始化程序,您可以直接分配数组值,如果更费力的话,使用enum_colours[white] = "white"; and so on, but only in a function.等等,但仅限于一个函数。

You can't get to the enum names at run-time since those symbols are long gone by then, but you can use multi-character constants to create more meaningful values for your enums.您无法在运行时获得枚举名称,因为那时这些符号早已不复存在,但您可以使用多字符常量为您的枚举创建更有意义的值。

#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;
}

will print将打印

kOne = One.
kTwo = Two.
kThree = Thre

on the console.在控制台上。

To keep debugString from generating garbage, each enum must be exactly four characters long (on OSX anyway).为了防止debugString产生垃圾,每个枚举必须正好是四个字符长(无论如何在 OSX 上)。 This is extremely compiler and platform dependent.这非常依赖于编译器和平台。 It's good for debugging, but not much else.它有利于调试,但除此之外别无他法。

Of course this won't work if you need the enums to have specific values or values that relate to each other.当然,如果您需要枚举具有特定值或相互关联的值,这将不起作用。

The names themselves wont be available, but it is usually enough to give them an explicit number:名称本身不可用,但通常给它们一个明确的数字就足够了:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM