简体   繁体   中英

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


EDIT: To steal the designated initialisers idea from teppic, here's an even more horrific version that also works with non-ordered initialiser values:

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

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", ... } .

then for MyEnum e you could just use 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. Without designated initialisers, you could assign the array values directly, if more laboriously, with 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). 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
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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