Como comparar strings em diretivas de pré-processador condicional C


92

Tenho que fazer algo assim em C. Funciona apenas se eu usar um char, mas preciso de uma string. Como posso fazer isso?

#define USER "jack" // jack or queen

#if USER == "jack"
#define USER_VS "queen"
#elif USER == "queen"
#define USER_VS "jack"
#endif

Por que você não pode simplesmente usar strcmp?

@Brian: Sim, também li a pergunta :-). Só queria ter certeza de que ele sabia que strcmp existe, e a resposta pode ser esclarecedora, já que não consigo pensar em uma razão para fazer isso #define.

2
Queria apenas mencionar que a mesma coisa vale para o código normal, não apenas para os pré-processadores. Nunca use uma string quando um valor simples for suficiente. Strings têm muito mais overhead do que inteiros ou enums e se você não precisa fazer nada além de compará-los, então, strings são a solução errada.
swestrup

Seria útil se a pergunta incluísse um pouco mais de informação sobre o comportamento desejado versus o comportamento real.
Brent Bradburn

Respostas:


69

Não acho que haja uma maneira de fazer comparações de strings de comprimento variável completamente em diretivas de pré-processador. Você talvez pudesse fazer o seguinte:

#define USER_JACK 1
#define USER_QUEEN 2

#define USER USER_JACK 

#if USER == USER_JACK
#define USER_VS USER_QUEEN
#elif USER == USER_QUEEN
#define USER_VS USER_JACK
#endif

Ou você pode refatorar um pouco o código e usar o código C.


3
Ou ele poderia #define USER_VS (3 - USER), neste caso específico. :)
Jesse Chisholm

17

[ATUALIZAÇÃO: 03.05.2018]

CAVEAT : Nem todos os compiladores implementam a especificação C ++ 11 da mesma maneira. O código a seguir funciona no compilador que testei, enquanto muitos comentadores usaram um compilador diferente.

Citando a resposta de Shafik Yaghmour em: Calculando o comprimento de uma string C em tempo de compilação. Este é realmente um constexpr?

Expressões constantes não são garantidas para serem avaliadas em tempo de compilação, nós temos apenas uma citação não normativa do rascunho da seção padrão C ++ 5.19 Expressões constantes que dizem o seguinte:

[...]> [Nota: As expressões constantes podem ser avaliadas durante a tradução. — nota final]

Aquela palavra can faz toda a diferença do mundo.

Então, YMMV nesta (ou qualquer) resposta envolvendo constexpr , dependendo da interpretação do escritor do compilador das especificações.

[ATUALIZADO em 31.01.2016]

Como alguns não gostaram da minha resposta anterior, porque evitou o todocompile time string compare aspecto do OP ao cumprir a meta sem a necessidade de comparações de strings, aqui está uma resposta mais detalhada.

Você não pode! Não em C98 ou C99. Nem mesmo no C11. Nenhuma quantidade de manipulação MACRO mudará isso.

A definição de const-expressionusado no#if não permite strings.

Ele permite caracteres, portanto, se você se limitar a eles, poderá usar o seguinte:

#define JACK 'J'
#define QUEEN 'Q'

#define CHOICE JACK     // or QUEEN, your choice

#if 'J' == CHOICE
#define USER "jack"
#define USER_VS "queen"
#elif 'Q' == CHOICE
#define USER "queen"
#define USER_VS "jack"
#else
#define USER "anonymous1"
#define USER_VS "anonymous2"
#endif

#pragma message "USER    IS " USER
#pragma message "USER_VS IS " USER_VS

Você pode! Em C ++ 11. Se você definir uma função auxiliar de tempo de compilação para a comparação.

// compares two strings in compile time constant fashion
constexpr int c_strcmp( char const* lhs, char const* rhs )
{
    return (('\0' == lhs[0]) && ('\0' == rhs[0])) ? 0
        :  (lhs[0] != rhs[0]) ? (lhs[0] - rhs[0])
        : c_strcmp( lhs+1, rhs+1 );
}
// some compilers may require ((int)lhs[0] - (int)rhs[0])

#define JACK "jack"
#define QUEEN "queen"

#define USER JACK       // or QUEEN, your choice

#if 0 == c_strcmp( USER, JACK )
#define USER_VS QUEEN
#elif 0 == c_strcmp( USER, QUEEN )
#define USER_VS JACK
#else
#define USER_VS "unknown"
#endif

#pragma message "USER    IS " USER
#pragma message "USER_VS IS " USER_VS

Então, em última análise, você terá que mudar a maneira como você realiza seu objetivo de escolher os valores finais da string para USER e USER_VS.

Você não pode fazer comparações de strings em tempo de compilação no C99, mas pode fazer a escolha de strings em tempo de compilação.

Se você realmente deve fazer comparações de tempo de compilação, então você precisa mudar para C ++ 11 ou variantes mais recentes que permitam esse recurso.

[SEGUE RESPOSTA ORIGINAL]

Tentar:

#define jack_VS queen
#define queen_VS jack

#define USER jack          // jack    or queen, your choice
#define USER_VS USER##_VS  // jack_VS or queen_VS

// stringify usage: S(USER) or S(USER_VS) when you need the string form.
#define S(U) S_(U)
#define S_(U) #U

ATUALIZAÇÃO: a colagem do token ANSI às vezes não é tão óbvia. ;-D

Colocando um único # antes de uma macro faz com que seja alterado para uma string de seu valor, em vez de seu valor vazio.

Colocando um duplo ## entre dois tokens faz com que eles sejam concatenados em um único token.

Então, a macro USER_VStem a expansão jack_VSou queen_VS, dependendo de como você definirUSER .

A macro stringifyS(...) usa indireção de macro para que o valor da macro nomeada seja convertido em uma string. em vez do nome da macro.

Assim, USER##_VStorna-se jack_VS(ou queen_VS), dependendo de como você define USER.

Posteriormente, quando a macro stringify é usada, S(USER_VS)o valor de USER_VS( jack_VSneste exemplo) é passado para a etapa de indireção S_(jack_VS)que converte seu valor ( queen) em uma string "queen".

Se você definir USERcomo queen, o resultado final será a string "jack".

Para concatenação de token, consulte: https://gcc.gnu.org/onlinedocs/cpp/Concatenation.html

Para conversão de string de token, consulte: https://gcc.gnu.org/onlinedocs/cpp/Stringification.html#Stringification

[ATUALIZADO EM 15/02/2015 para corrigir um erro de digitação.]


5
@JesseChisholm, você verificou sua versão do C ++ 11? Não consigo fazer funcionar no GCC 4.8.1, 4.9.1, 5.3.0. Diz {{operador binário ausente antes do token "("}} em {{#if 0 == c_strmp / * aqui * / (USUÁRIO, QUEEN)}}
Dmitriy Elisov

3
@JesseChisholm Consegui compilar seu exemplo C ++ 11 se eu mudar #if 0 == c_strcmp( USER, JACK )paraconstexpr int comp1 = c_strcmp( USER, JACK ); #if 0 == comp1
Dmitriy Elisov

4
@JesseChisholm, hmm, ainda sem sorte. Qualquer variável constexpr é igual a zero em. #ifSeu exemplo funciona apenas porque USER é JACK. Se USER fosse QUEEN, diria USER IS QUEENeUSER_VS IS QUEEN
Dmitriy Elisov

9
Esta parte c ++ 11 desta resposta está errada. Você não pode chamar funções (nem mesmo constexpr) de diretivas de pré-processador.
intervalo de

8
Essa resposta totalmente errada já enganou alguém que a referiu. Você não pode chamar uma função constexpr do pré-processador; constexpr nem mesmo é reconhecido como uma palavra-chave até a fase de tradução 7. O pré-processamento é feito na fase de tradução 4.
H Walters

9

O seguinte funcionou para mim com clang. Permite o que aparece como comparação simbólica de valores macro. #error xxx é apenas para ver o que o compilador realmente faz. Substituir a definição de gato por #define cat (a, b) a ## b quebra as coisas.

#define cat(a,...) cat_impl(a, __VA_ARGS__)
#define cat_impl(a,...) a ## __VA_ARGS__

#define xUSER_jack 0
#define xUSER_queen 1
#define USER_VAL cat(xUSER_,USER)

#define USER jack // jack or queen

#if USER_VAL==xUSER_jack
  #error USER=jack
  #define USER_VS "queen"
#elif USER_VAL==xUSER_queen
  #error USER=queen
  #define USER_VS "jack"
#endif

Não tenho certeza se isso era mau, brilhante ou ambos, mas era exatamente o que eu estava procurando - obrigado! Outro truque útil é # definir suas macros xUSER_ começando em 1. Então você pode adicionar uma cláusula #else ao final de sua lista #elsif para detectar casos em que USER é acidentalmente definido como algo que você não sabe como lidar. (Caso contrário, se você numerar de 0, o caso 0 se tornará seu apanhador, porque esse é o valor numérico padrão do pré-processador para símbolos indefinidos.)
sclamage

8

Use valores numéricos em vez de strings.

Finalmente, para converter as constantes JACK ou QUEEN em uma string, use os operadores stringize (e / ou tokenize).


2

Como já foi dito acima, o pré-processador ISO-C11 não suporta comparação de strings. No entanto, o problema de atribuir a uma macro o “valor oposto” pode ser resolvido com “colagem de token” e “acesso à tabela”. A macro-solução concatenate / stringify simples de Jesse falha com gcc 5.4.0 porque a stringização é feita antes da avaliação da concatenação (em conformidade com ISO C11). No entanto, pode ser corrigido:

#define P_(user) user ## _VS
#define VS(user) P_ (user)
#define S(U) S_(U)
#define S_(U) #U

#define jack_VS  queen
#define queen_VS jack

S (VS (jack))
S (jack)
S (VS (queen))
S (queen)

#define USER jack          // jack    or queen, your choice
#define USER_VS USER##_VS  // jack_VS or queen_VS
S (USER)
S (USER_VS)

A primeira linha (macro P_()) adiciona uma indireção para permitir que a próxima linha (macro VS()) termine a concatenação antes da stringização (consulte Por que preciso de uma camada dupla de indireção para macros? ). As macros de stringização ( S()e S_()) são de Jesse.

A tabela (macros jack_VSequeen_VS ), que é muito mais fácil de manter do que a construção if-then-else do OP, é de Jesse.

Finalmente, o próximo bloco de quatro linhas invoca as macros de estilo de função. O último bloco de quatro linhas é da resposta de Jesse.

Armazenar o código foo.ce invocar o pré-processador gcc -nostdinc -E foo.cproduz:

# 1 "foo.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "foo.c"
# 9 "foo.c"
"queen"
"jack"
"jack"
"queen"



"jack"
"USER_VS"

A saída é a esperada. A última linha mostra que a USER_VSmacro não é expandida antes da stringização.


Isso funciona bem, até que eu tente realmente comparar a string gerada, para fazer uma compilação condicional: #if (S(USER)=="jack")- Recebo um erro de pré-processador ao usar o "- error: invalid token at start of a preprocessor expression.
ysap

1

Se suas strings são constantes de tempo de compilação (como no seu caso), você pode usar o seguinte truque:

#define USER_JACK strcmp(USER, "jack")
#define USER_QUEEN strcmp(USER, "queen")
#if $USER_JACK == 0
#define USER_VS USER_QUEEN
#elif USER_QUEEN == 0
#define USER_VS USER_JACK
#endif

O compilador pode informar o resultado do strcmp com antecedência e irá substituir o strcmp com seu resultado, dando a você um #define que pode ser comparado com as diretivas do pré-processador. Não sei se há alguma variação entre compiladores / dependência de opções do compilador, mas funcionou para mim no GCC 4.7.2.

EDITAR: após uma investigação mais aprofundada, parece que esta é uma extensão do conjunto de ferramentas, não uma extensão do GCC, então leve isso em consideração ...


7
Certamente não é o C padrão e não vejo como funcionaria com qualquer compilador. O compilador às vezes pode informar os resultados das expressões (até mesmo chamadas de função, se forem embutidas), mas não o pré-processador. Você usa $algum tipo de extensão de pré-processador?
ugoren

3
Parece que a sintaxe '#if $ USER_JACK == 0' funciona, pelo menos com GNU C ++ usado para construir código Android nativo (JNI) ... Eu não sabia disso, mas é muito útil, obrigado por nos contar sobre isto!
gregko 01 de

6
Eu tentei isso no GCC 4.9.1, e não acredito que fará o que você pensa. Embora o código seja compilado, ele não fornecerá o resultado esperado. '$' é tratado como um nome de variável. Então o pré-processador está procurando pela variável '$ USER_JACK', não a encontrando e dando a ela o valor padrão de 0. Assim, você sempre terá USER_VS definido como USER_QUEEN independente de strcmp
Vitali

1

A resposta de Patrick e Jesse Chisholm me fez fazer o seguinte:

#define QUEEN 'Q'
#define JACK 'J'

#define CHECK_QUEEN(s) (s==QUEEN)
#define CHECK_JACK(s) (s==JACK)

#define USER 'Q'

[... later on in code ...]

#if CHECK_QUEEN(USER)
  compile_queen_func();
#elif CHECK_JACK(USER)
  compile_jack_func();
#elif
#error "unknown user"
#endif

Ao invés de #define USER 'Q' #define USER QUEEN também deve funcionar, mas não foi testado também funciona e pode ser mais fácil de manusear.

EDIT: De acordo com o comentário de @Jean-François Fabre adaptei minha resposta.


mudar (s==QUEEN?1:0)por (s==QUEEN)você não precisa do ternário, o resultado já é um booleano
Jean-François Fabre

0
#define USER_IS(c0,c1,c2,c3,c4,c5,c6,c7,c8,c9)\
ch0==c0 && ch1==c1 && ch2==c2 && ch3==c3 && ch4==c4 && ch5==c5 && ch6==c6 && ch7==c7 ;

#define ch0 'j'
#define ch1 'a'
#define ch2 'c'
#define ch3 'k'

#if USER_IS('j','a','c','k',0,0,0,0)
#define USER_VS "queen"
#elif USER_IS('q','u','e','e','n',0,0,0)
#define USER_VS "jack"
#endif

é basicamente um array de char estático de comprimento fixo inicializado manualmente em vez de um array de char estático de comprimento variável inicializado automaticamente sempre terminando com um char nulo de terminação


0

Você não pode fazer isso se USER for definido como uma string entre aspas.

Mas você pode fazer isso se USER for apenas JACK ou QUEEN ou Joker ou o que for.

Existem dois truques para usar:

  1. Splicing de token, onde você combina um identificador com outro identificador apenas concatenando seus caracteres. Isso permite que você compare com o JACK sem ter que #define JACKfazer nada
  2. expansão variável de macro, que permite lidar com macros com números variáveis ​​de argumentos. Isso permite que você expanda identificadores específicos em vários números de vírgulas, que se tornarão sua comparação de strings.

Então, vamos começar com:

#define JACK_QUEEN_OTHER(u) EXPANSION1(ReSeRvEd_, u, 1, 2, 3)

Agora, se eu escrever JACK_QUEEN_OTHER(USER)e USER for JACK, o pré-processador transformará isso emEXPANSION1(ReSeRvEd_, JACK, 1, 2, 3)

A segunda etapa é a concatenação:

#define EXPANSION1(a, b, c, d, e) EXPANSION2(a##b, c, d, e)

Agora JACK_QUEEN_OTHER(USER)se tornaEXPANSION2(ReSeRvEd_JACK, 1, 2, 3)

Isso dá a oportunidade de adicionar várias vírgulas de acordo com a correspondência de uma string:

#define ReSeRvEd_JACK x,x,x
#define ReSeRvEd_QUEEN x,x

Se USER é JACK, JACK_QUEEN_OTHER(USER)torna-seEXPANSION2(x,x,x, 1, 2, 3)

Se USER é QUEEN, JACK_QUEEN_OTHER(USER)torna-seEXPANSION2(x,x, 1, 2, 3)

Se USER for outro, JACK_QUEEN_OTHER(USER)torna-seEXPANSION2(ReSeRvEd_other, 1, 2, 3)

Nesse ponto, algo crítico aconteceu: o quarto argumento para a macro EXPANSION2 é 1, 2 ou 3, dependendo se o argumento original aprovado foi valete, rainha ou qualquer outra coisa. Portanto, tudo o que temos a fazer é escolher. Por razões prolixas, precisaremos de duas macros para a última etapa; eles serão EXPANSION2 e EXPANSION3, embora um pareça desnecessário.

Juntando tudo, temos estas 6 macros:

#define JACK_QUEEN_OTHER(u) EXPANSION1(ReSeRvEd_, u, 1, 2, 3)
#define EXPANSION1(a, b, c, d, e) EXPANSION2(a##b, c, d, e)
#define EXPANSION2(a, b, c, d, ...) EXPANSION3(a, b, c, d)
#define EXPANSION3(a, b, c, d, ...) d
#define ReSeRvEd_JACK x,x,x
#define ReSeRvEd_QUEEN x,x

E você pode usá-los assim:

int main() {
#if JACK_QUEEN_OTHER(USER) == 1
  printf("Hello, Jack!\n");
#endif
#if JACK_QUEEN_OTHER(USER) == 2
  printf("Hello, Queen!\n");
#endif
#if JACK_QUEEN_OTHER(USER) == 3
  printf("Hello, who are you?\n");
#endif
}

Link Godbolt obrigatório: https://godbolt.org/z/8WGa19


-5

É simples, acho que você pode apenas dizer

#define NAME JACK    
#if NAME == queen 
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.