Como converter nomes de enum em string em c

92

Existe a possibilidade de converter nomes de enumeradores em string em C?

Misha
fonte

Respostas:

184

Uma maneira, fazer o pré-processador fazer o trabalho. Ele também garante que seus enums e strings estejam sincronizados.

#define FOREACH_FRUIT(FRUIT) \
        FRUIT(apple)   \
        FRUIT(orange)  \
        FRUIT(grape)   \
        FRUIT(banana)  \

#define GENERATE_ENUM(ENUM) ENUM,
#define GENERATE_STRING(STRING) #STRING,

enum FRUIT_ENUM {
    FOREACH_FRUIT(GENERATE_ENUM)
};

static const char *FRUIT_STRING[] = {
    FOREACH_FRUIT(GENERATE_STRING)
};

Depois que o pré-processador estiver pronto, você terá:

enum FRUIT_ENUM {
    apple, orange, grape, banana,
};

static const char *FRUIT_STRING[] = {
    "apple", "orange", "grape", "banana",
};

Então você poderia fazer algo como:

printf("enum apple as a string: %s\n",FRUIT_STRING[apple]);

Se o caso de uso for literalmente apenas imprimir o nome enum, adicione as seguintes macros:

#define str(x) #x
#define xstr(x) str(x)

Então faça:

printf("enum apple as a string: %s\n", xstr(apple));

Nesse caso, pode parecer que a macro de dois níveis é supérflua, no entanto, devido ao modo como a estringificação funciona em C, ela é necessária em alguns casos. Por exemplo, digamos que queremos usar um #define com um enum:

#define foo apple

int main() {
    printf("%s\n", str(foo));
    printf("%s\n", xstr(foo));
}

O resultado seria:

foo
apple

Isso ocorre porque str irá restringir a entrada foo em vez de expandi-la para ser apple. Usando xstr, a expansão da macro é feita primeiro e, em seguida, o resultado é codificado.

Consulte Stringificação para obter mais informações.

Terrence M
fonte
1
Isso é perfeito, mas não consigo entender o que realmente está acontecendo. : O
p0lAris
Além disso, como converter uma string em um enum no caso acima?
p0lAris
Existem algumas maneiras de fazer isso, dependendo do que você está tentando alcançar.
Terrence M
5
Se você não quiser poluir o namespace com maçã e laranja ... você pode #define GENERATE_ENUM(ENUM) PREFIX##ENUM,
prefixá-
1
Para quem leu esta postagem, este método de usar uma lista de macros para enumerar vários itens em um programa é informalmente chamado de "macros X".
Lundin,
27

Em uma situação em que você tem:

enum fruit {
    apple, 
    orange, 
    grape,
    banana,
    // etc.
};

Gosto de colocar isso no arquivo de cabeçalho onde o enum é definido:

static inline char *stringFromFruit(enum fruit f)
{
    static const char *strings[] = { "apple", "orange", "grape", "banana", /* continue for rest of values */ };

    return strings[f];
}
Richard J. Ross III
fonte
4
Pela minha vida, não consigo ver como isso ajuda. Você poderia expandir um pouco para tornar isso mais óbvio.
David Heffernan
2
OK, como isso ajuda? Você está dizendo que é mais fácil digitar enumToString(apple)do que digitar "apple"? Não é como se houvesse qualquer tipo de segurança em qualquer lugar. A menos que eu esteja perdendo algo, o que você sugere aqui é inútil e apenas consegue ofuscar o código.
David Heffernan
2
OK, entendo agora. A macro é falsa em minha opinião e eu sugiro que você a exclua.
David Heffernan
2
comentários falam sobre macro. Cadê?
mk ..
2
Isso também é inconveniente de manter. Se eu inserir um novo enum, tenho que lembrar de duplicar isso também no array, na posição correta.
Fabio
14

Não existe uma maneira simples de fazer isso diretamente. Mas o P99 tem macros que permitem criar esse tipo de função automaticamente:

 P99_DECLARE_ENUM(color, red, green, blue);

em um arquivo de cabeçalho, e

 P99_DEFINE_ENUM(color);

em uma unidade de compilação (arquivo .c) deve, então, fazer o truque, nesse exemplo a função seria chamada color_getname.

Jens Gustedt
fonte
Como faço para puxar essa lib?
JohnyTex
14

Eu encontrei um truque do pré-processador C que está fazendo o mesmo trabalho sem declarar uma string de array dedicada (Fonte: http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/c_preprocessor_applications_en ).

Enums sequenciais

Seguindo a invenção de Stefan Ram, enums sequenciais (sem declarar explicitamente o índice, por exemplo enum {foo=-1, foo1 = 1}) podem ser realizados como este truque genial:

#include <stdio.h>

#define NAMES C(RED)C(GREEN)C(BLUE)
#define C(x) x,
enum color { NAMES TOP };
#undef C

#define C(x) #x,    
const char * const color_name[] = { NAMES };

Isso dá o seguinte resultado:

int main( void )  { 
    printf( "The color is %s.\n", color_name[ RED ]);  
    printf( "There are %d colors.\n", TOP ); 
}

A cor é VERMELHA.
Existem 3 cores.

Enums não sequenciais

Como eu queria mapear as definições dos códigos de erro para strings de array, para que eu pudesse anexar a definição de erro bruta ao código de erro (por exemplo "The error is 3 (LC_FT_DEVICE_NOT_OPENED)."), estendi o código de forma que você possa determinar facilmente o índice necessário para os respectivos valores enum :

#define LOOPN(n,a) LOOP##n(a)
#define LOOPF ,
#define LOOP2(a) a LOOPF a LOOPF
#define LOOP3(a) a LOOPF a LOOPF a LOOPF
#define LOOP4(a) a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP5(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP6(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP7(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP8(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP9(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF


#define LC_ERRORS_NAMES \
    Cn(LC_RESPONSE_PLUGIN_OK, -10) \
    Cw(8) \
    Cn(LC_RESPONSE_GENERIC_ERROR, -1) \
    Cn(LC_FT_OK, 0) \
    Ci(LC_FT_INVALID_HANDLE) \
    Ci(LC_FT_DEVICE_NOT_FOUND) \
    Ci(LC_FT_DEVICE_NOT_OPENED) \
    Ci(LC_FT_IO_ERROR) \
    Ci(LC_FT_INSUFFICIENT_RESOURCES) \
    Ci(LC_FT_INVALID_PARAMETER) \
    Ci(LC_FT_INVALID_BAUD_RATE) \
    Ci(LC_FT_DEVICE_NOT_OPENED_FOR_ERASE) \
    Ci(LC_FT_DEVICE_NOT_OPENED_FOR_WRITE) \
    Ci(LC_FT_FAILED_TO_WRITE_DEVICE) \
    Ci(LC_FT_EEPROM_READ_FAILED) \
    Ci(LC_FT_EEPROM_WRITE_FAILED) \
    Ci(LC_FT_EEPROM_ERASE_FAILED) \
    Ci(LC_FT_EEPROM_NOT_PRESENT) \
    Ci(LC_FT_EEPROM_NOT_PROGRAMMED) \
    Ci(LC_FT_INVALID_ARGS) \
    Ci(LC_FT_NOT_SUPPORTED) \
    Ci(LC_FT_OTHER_ERROR) \
    Ci(LC_FT_DEVICE_LIST_NOT_READY)


#define Cn(x,y) x=y,
#define Ci(x) x,
#define Cw(x)
enum LC_errors { LC_ERRORS_NAMES TOP };
#undef Cn
#undef Ci
#undef Cw
#define Cn(x,y) #x,
#define Ci(x) #x,
#define Cw(x) LOOPN(x,"")
static const char* __LC_errors__strings[] = { LC_ERRORS_NAMES };
static const char** LC_errors__strings = &__LC_errors__strings[10];

Neste exemplo, o pré-processador C gerará o seguinte código :

enum LC_errors { LC_RESPONSE_PLUGIN_OK=-10,  LC_RESPONSE_GENERIC_ERROR=-1, LC_FT_OK=0, LC_FT_INVALID_HANDLE, LC_FT_DEVICE_NOT_FOUND, LC_FT_DEVICE_NOT_OPENED, LC_FT_IO_ERROR, LC_FT_INSUFFICIENT_RESOURCES, LC_FT_INVALID_PARAMETER, LC_FT_INVALID_BAUD_RATE, LC_FT_DEVICE_NOT_OPENED_FOR_ERASE, LC_FT_DEVICE_NOT_OPENED_FOR_WRITE, LC_FT_FAILED_TO_WRITE_DEVICE, LC_FT_EEPROM_READ_FAILED, LC_FT_EEPROM_WRITE_FAILED, LC_FT_EEPROM_ERASE_FAILED, LC_FT_EEPROM_NOT_PRESENT, LC_FT_EEPROM_NOT_PROGRAMMED, LC_FT_INVALID_ARGS, LC_FT_NOT_SUPPORTED, LC_FT_OTHER_ERROR, LC_FT_DEVICE_LIST_NOT_READY, TOP };

static const char* __LC_errors__strings[] = { "LC_RESPONSE_PLUGIN_OK", "" , "" , "" , "" , "" , "" , "" , "" "LC_RESPONSE_GENERIC_ERROR", "LC_FT_OK", "LC_FT_INVALID_HANDLE", "LC_FT_DEVICE_NOT_FOUND", "LC_FT_DEVICE_NOT_OPENED", "LC_FT_IO_ERROR", "LC_FT_INSUFFICIENT_RESOURCES", "LC_FT_INVALID_PARAMETER", "LC_FT_INVALID_BAUD_RATE", "LC_FT_DEVICE_NOT_OPENED_FOR_ERASE", "LC_FT_DEVICE_NOT_OPENED_FOR_WRITE", "LC_FT_FAILED_TO_WRITE_DEVICE", "LC_FT_EEPROM_READ_FAILED", "LC_FT_EEPROM_WRITE_FAILED", "LC_FT_EEPROM_ERASE_FAILED", "LC_FT_EEPROM_NOT_PRESENT", "LC_FT_EEPROM_NOT_PROGRAMMED", "LC_FT_INVALID_ARGS", "LC_FT_NOT_SUPPORTED", "LC_FT_OTHER_ERROR", "LC_FT_DEVICE_LIST_NOT_READY", };

Isso resulta nos seguintes recursos de implementação:

LC_errors__strings [-1] ==> LC_errors__strings [LC_RESPONSE_GENERIC_ERROR] ==> "LC_RESPONSE_GENERIC_ERROR"

Maschina
fonte
Agradável. Isso é exatamente o que eu estava procurando e usando. Mesmos erros :)
mrbean
5

Você não precisa depender do pré-processador para garantir que seus enums e strings estejam sincronizados. Para mim, o uso de macros torna o código mais difícil de ler.

Usando Enum e uma série de strings

enum fruit                                                                   
{
    APPLE = 0, 
    ORANGE, 
    GRAPE,
    BANANA,
    /* etc. */
    FRUIT_MAX                                                                                                                
};   

const char * const fruit_str[] =
{
    [BANANA] = "banana",
    [ORANGE] = "orange",
    [GRAPE]  = "grape",
    [APPLE]  = "apple",
    /* etc. */  
};

Nota: as strings na fruit_strmatriz não precisam ser declaradas na mesma ordem que os itens enum.

Como usá-lo

printf("enum apple as a string: %s\n", fruit_str[APPLE]);

Adicionando uma verificação de tempo de compilação

Se você tem medo de esquecer uma string, pode adicionar a seguinte verificação:

#define ASSERT_ENUM_TO_STR(sarray, max) \                                       
  typedef char assert_sizeof_##max[(sizeof(sarray)/sizeof(sarray[0]) == (max)) ? 1 : -1]

ASSERT_ENUM_TO_STR(fruit_str, FRUIT_MAX);

Um erro seria relatado em tempo de compilação se a quantidade de itens enum não corresponder à quantidade de strings na matriz.

Jyvet
fonte
2

Uma função como essa sem validar o enum é um pouco perigosa. Eu sugiro usar uma instrução switch. Outra vantagem é que isso pode ser usado para enums que possuem valores definidos, por exemplo, para sinalizadores onde os valores são 1,2,4,8,16 etc.

Além disso, coloque todas as strings de enum juntas em uma matriz: -

static const char * allEnums[] = {
    "Undefined",
    "apple",
    "orange"
    /* etc */
};

definir os índices em um arquivo de cabeçalho: -

#define ID_undefined       0
#define ID_fruit_apple     1
#define ID_fruit_orange    2
/* etc */

Isso facilita a produção de diferentes versões, por exemplo, se você deseja fazer versões internacionais de seu programa com outros idiomas.

Usando uma macro, também no arquivo de cabeçalho: -

#define CASE(type,val) case val: index = ID_##type##_##val; break;

Faça uma função com uma instrução switch, isso deve retornar um const char *porque as strings consts estáticas: -

const char * FruitString(enum fruit e){

    unsigned int index;

    switch(e){
        CASE(fruit, apple)
        CASE(fruit, orange)
        CASE(fruit, banana)
        /* etc */
        default: index = ID_undefined;
    }
    return allEnums[index];
}

Se estiver programando com Windows, os valores ID_ podem ser valores de recursos.

(Se estiver usando C ++, todas as funções podem ter o mesmo nome.

string EnumToString(fruit e);

)

QuentinUK
fonte
2

Uma alternativa mais simples para a resposta de "enums não sequenciais" de Hokyo, com base no uso de designadores para instanciar a matriz de string:

#define NAMES C(RED, 10)C(GREEN, 20)C(BLUE, 30)
#define C(k, v) k = v,
enum color { NAMES };
#undef C

#define C(k, v) [v] = #k,    
const char * const color_name[] = { NAMES };
Lars
fonte
-2

Eu normalmente faço isso:

#define COLOR_STR(color)                            \
    (RED       == color ? "red"    :                \
     (BLUE     == color ? "blue"   :                \
      (GREEN   == color ? "green"  :                \
       (YELLOW == color ? "yellow" : "unknown"))))   
gigilibala
fonte
1
Isso é simplesmente ridículo
Massimo Callegari
Esta não é uma resposta ruim. É claro, simples e fácil de entender. Se você estiver trabalhando em sistemas em que outras pessoas precisam ler e entender seu código rapidamente, a clareza é muito importante. Eu não recomendaria usar truques de pré-processador, a menos que eles sejam completamente comentados ou descritos em um padrão de codificação.
Nielsen