Cómo comparar cadenas en directivas de preprocesador condicionales de C


92

Tengo que hacer algo como esto en C. Funciona solo si uso un char, pero necesito una cadena. ¿Cómo puedo hacer esto?

#define USER "jack" // jack or queen

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

¿Por qué no puedes usar strcmp?

@Brian: Sí, también leí la pregunta :-). Solo quería asegurarme de que sabía que strcmp existe, y la respuesta podría ser esclarecedora, ya que no puedo pensar en una razón para hacer esto #define.

2
Solo quería mencionar que lo mismo ocurre con el código regular, no solo con los preprocesadores. Nunca use una cadena cuando un valor simple sea suficiente. Las cadenas tienen mucha más sobrecarga que los enteros o las enumeraciones y si no necesita hacer nada más que compararlas, las cadenas son la solución incorrecta.
swestrup

Sería útil si la pregunta incluyera un poco más de información sobre el comportamiento deseado frente al real.
Brent Bradburn

Respuestas:


71

No creo que haya una manera de hacer comparaciones de cadenas de longitud variable completamente en las directivas del preprocesador. Sin embargo, quizás puedas hacer lo siguiente:

#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

O puede refactorizar un poco el código y usar código C en su lugar.


3
O podría #define USER_VS (3 - USER)en este caso específico. :)
Jesse Chisholm

17

[ACTUALIZACIÓN: 2018.05.03]

AVISO : No todos los compiladores implementan la especificación C ++ 11 de la misma manera. El siguiente código funciona en el compilador en el que probé, mientras que muchos comentaristas usaron un compilador diferente.

Citando la respuesta de Shafik Yaghmour en: Calcular la longitud de una cadena C en tiempo de compilación. ¿Es esto realmente un constexpr?

No se garantiza que las expresiones constantes se evalúen en tiempo de compilación, solo tenemos una cita no normativa del borrador del estándar C ++ sección 5.19 Expresiones constantes que dice esto:

[...]> [Nota: las expresiones constantes se pueden evaluar durante la traducción. — nota final]

Esa palabra canmarca la diferencia en el mundo.

Entonces, YMMV en esta (o cualquier) respuesta que involucre constexpr, dependiendo de la interpretación de la especificación del escritor del compilador.

[ACTUALIZADO 2016.01.31]

Como a algunos no les gustó mi respuesta anterior porque evitaba todo el compile time string compareaspecto del OP al lograr el objetivo sin necesidad de comparar cadenas, aquí hay una respuesta más detallada.

¡No puedes! No en C98 o C99. Ni siquiera en C11. Ninguna cantidad de manipulación MACRO cambiará esto.

La definición de const-expressionusado en #ifno permite cadenas.

Sí permite caracteres, por lo que si se limita a los caracteres, puede usar esto:

#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

¡Usted puede! En C ++ 11. Si define una función auxiliar de tiempo de compilación para la comparación.

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

Entonces, en última instancia, tendrá que cambiar la forma en que logra su objetivo de elegir valores de cadena finales para USERy USER_VS.

No puede comparar cadenas de tiempo de compilación en C99, pero puede elegir cadenas de tiempo de compilación.

Si realmente debe hacer comparaciones de tiempo de compilación, debe cambiar a C ++ 11 o variantes más nuevas que permitan esa característica.

[SIGUE LA RESPUESTA ORIGINAL]

Tratar:

#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

ACTUALIZACIÓN: el pegado de tokens ANSI a veces es menos que obvio. ;-RE

Poner un solo #antes de una macro hace que se cambie a una cadena de su valor, en lugar de su valor simple .

Poner un doble ##entre dos tokens hace que se concatenen en un solo token.

Entonces, la macro USER_VStiene la expansión jack_VSo queen_VS, dependiendo de cómo lo establezca USER.

La macro stringifyS(...) usa macro indirección para que el valor de la macro nombrada se convierta en una cadena. en lugar del nombre de la macro.

Así se USER##_VSconvierte en jack_VS(o queen_VS), dependiendo de cómo lo establezca USER.

Más tarde, cuando se usa la macro stringify , S(USER_VS)el valor de USER_VS( jack_VSen este ejemplo) se pasa al paso de indirección S_(jack_VS)que convierte su valor ( queen) en una cadena "queen".

Si establece USERen queen, el resultado final es la cadena "jack".

Para la concatenación de tokens, consulte: https://gcc.gnu.org/onlinedocs/cpp/Concatenation.html

Para la conversión de cadenas de tokens, consulte: https://gcc.gnu.org/onlinedocs/cpp/Stringification.html#Stringification

[ACTUALIZADO 2015.02.15 para corregir un error tipográfico.]


5
@JesseChisholm, ¿revisaste tu versión de C ++ 11? No puedo hacer que funcione en GCC 4.8.1, 4.9.1, 5.3.0. Dice {{falta el operador binario antes del token "("}} en {{#if 0 == c_strmp / * aquí * / (USER, QUEEN)}}
Dmitriy Elisov

3
@JesseChisholm Así que logré compilar su ejemplo de C ++ 11 si cambio #if 0 == c_strcmp( USER, JACK )aconstexpr int comp1 = c_strcmp( USER, JACK ); #if 0 == comp1
Dmitriy Elisov

4
@JesseChisholm, hmm, todavía sin suerte. Cualquier variable constexpr es igual a cero en. #ifSu ejemplo solo funciona porque USUARIO es JACK. Si el USUARIO fuera REINA, diría USER IS QUEENyUSER_VS IS QUEEN
Dmitriy Elisov

9
Esta parte de c ++ 11 de esta respuesta es incorrecta. No puede llamar a funciones (incluso constexpr) desde las directivas del preprocesador.
interjay

8
Esta respuesta totalmente incorrecta ya ha engañado a alguien que la hizo referencia. No puede llamar a una función constexpr desde el preprocesador; constexpr ni siquiera se reconoce como palabra clave hasta la fase de traducción 7. El procesamiento previo se realiza en la fase de traducción 4.
H Walters

10

Lo siguiente funcionó para mí con clang. Permite lo que aparece como una comparación de valores macro simbólicos. #error xxx es solo para ver qué hace realmente el compilador. Reemplazar la definición de gato con #define cat (a, b) a ## b rompe cosas.

#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

No estoy seguro de si esto era malvado, brillante o ambos, pero era exactamente lo que estaba buscando, ¡gracias! Un truco más útil es #definir sus macros xUSER_ comenzando desde 1. Luego puede agregar una cláusula #else al final de su lista #elsif para detectar casos en los que USER se establece accidentalmente en algo que no sabe cómo manejar. (De lo contrario, si el número del 0 0 entonces el caso se convierte en su cajón de sastre, porque eso es valor numérico predeterminado del preprocesador de símbolos no definidos.)
sclamage

8

Utilice valores numéricos en lugar de cadenas.

Finalmente, para convertir las constantes JACK o QUEEN en una cadena, use los operadores de cadena (y / o tokenización).


2

Como ya se indicó anteriormente, el preprocesador ISO-C11 no admite la comparación de cadenas. Sin embargo, el problema de asignar una macro con el "valor opuesto" se puede resolver con "pegar tokens" y "acceso a la tabla". La macro-solución de concatenar / encadenar simple de Jesse falla con gcc 5.4.0 porque la cadena se realiza antes de la evaluación de la concatenación (conforme a ISO C11). Sin embargo, se puede arreglar:

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

La primera línea (macro P_()) agrega una indirección para permitir que la siguiente línea (macro VS()) termine la concatenación antes de la secuenciación (consulte ¿Por qué necesito una doble capa de indirección para macros? ). Las macros de encadenamiento ( S()y S_()) son de Jesse.

La tabla (macros jack_VSy queen_VS) que es mucho más fácil de mantener que la construcción if-then-else del OP es de Jesse.

Finalmente, el siguiente bloque de cuatro líneas invoca las macros de estilo de función. El último bloque de cuatro líneas es de la respuesta de Jesse.

Almacenar el código foo.ce invocar el preprocesador gcc -nostdinc -E foo.cproduce:

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



"jack"
"USER_VS"

La salida es la esperada. La última línea muestra que la USER_VSmacro no se expande antes de la cadena.


Esto funciona bien, hasta que trato de comparar realmente la cadena generada, para hacer una compilación condicional: #if (S(USER)=="jack")- Recibo un error de preprocesador cuando uso el "- error: invalid token at start of a preprocessor expression.
ysap

1

Si sus cadenas son constantes de tiempo de compilación (como en su caso), puede usar el siguiente truco:

#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

El compilador puede decirle el resultado de strcmp de antemano y reemplazará strcmp con su resultado, dándole así una #define que se puede comparar con las directivas del preprocesador. No sé si hay alguna variación entre los compiladores / dependencia de las opciones del compilador, pero funcionó para mí en GCC 4.7.2.

EDITAR: después de una mayor investigación, parece que esta es una extensión de cadena de herramientas, no una extensión de GCC, así que téngalo en cuenta ...


7
Esto ciertamente no es C estándar, y no veo cómo funcionaría con ningún compilador. El compilador a veces puede decir los resultados de las expresiones (incluso las llamadas a funciones, si están en línea), pero no el preprocesador. ¿Utiliza $algún tipo de extensión de preprocesador?
ugoren

3
Parece que la sintaxis '#if $ USER_JACK == 0' funciona, al menos con GNU C ++ usado para construir código nativo de Android (JNI) ... No lo sabía, pero es muy útil, gracias por contarnos sobre ¡eso!
Gregko

6
Probé esto en GCC 4.9.1, y no creo que esto haga lo que crees que hace. Si bien el código se compilará, no le dará el resultado esperado. '$' se trata como un nombre de variable. Entonces, el preprocesador está buscando la variable '$ USER_JACK', no la encuentra y le da el valor predeterminado de 0. Por lo tanto, siempre tendrá USER_VS definido como USER_QUEEN independientemente de strcmp
Vitali

1

La respuesta de Patrick y Jesse Chisholm me hizo hacer lo siguiente:

#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

En vez de #define USER 'Q' #define USER QUEEN también debería funcionar pero no fue probado también funciona y podría ser más fácil de manejar.

EDITAR: Según el comentario de @ Jean-François Fabre, adapté mi respuesta.


cambiar (s==QUEEN?1:0)por (s==QUEEN)no necesita el ternario, el resultado ya es un 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

es básicamente una matriz de caracteres estáticos de longitud fija inicializada manualmente en lugar de una matriz de caracteres estáticos de longitud variable inicializada automáticamente y siempre termina con un carácter nulo de terminación


0

No puede hacer eso si USUARIO se define como una cadena entre comillas.

Pero usted puede hacer eso si el USUARIO es sólo JACK o reina o Joker o lo que sea.

Hay dos trucos para usar:

  1. Empalme de tokens, donde se combina un identificador con otro identificador simplemente concatenando sus caracteres. Esto te permite comparar con JACK sin tener que #define JACKhacerlo.
  2. expansión de macro variadic, que le permite manejar macros con números variables de argumentos. Esto le permite expandir identificadores específicos en diferentes números de comas, que se convertirán en su comparación de cadenas.

Así que comencemos con:

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

Ahora, si escribo JACK_QUEEN_OTHER(USER), y USER es JACK, el preprocesador lo convierte enEXPANSION1(ReSeRvEd_, JACK, 1, 2, 3)

El segundo paso es la concatenación:

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

Ahora se JACK_QUEEN_OTHER(USER)convierte enEXPANSION2(ReSeRvEd_JACK, 1, 2, 3)

Esto da la oportunidad de agregar una serie de comas según si una cadena coincide o no:

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

Si USUARIO es JACK, se JACK_QUEEN_OTHER(USER)convierte enEXPANSION2(x,x,x, 1, 2, 3)

Si USUARIO es REINA, se JACK_QUEEN_OTHER(USER)convierte enEXPANSION2(x,x, 1, 2, 3)

Si USUARIO es otro, se JACK_QUEEN_OTHER(USER)convierte enEXPANSION2(ReSeRvEd_other, 1, 2, 3)

En este punto, ha sucedido algo crítico: el cuarto argumento de la macro EXPANSION2 es 1, 2 o 3, dependiendo de si el argumento original pasado fue jota, reina o cualquier otra cosa. Así que todo lo que tenemos que hacer es elegirlo. Por razones extensas, necesitaremos dos macros para el último paso; serán EXPANSION2 y EXPANSION3, aunque una parezca innecesaria.

Poniéndolo todo junto, tenemos 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

Y podrías usarlos así:

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
}

Enlace obligatorio de Godbolt: https://godbolt.org/z/8WGa19


Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.