¿Por qué C no permite concatenar cadenas cuando se usa el operador condicional?


95

El siguiente código se compila sin problemas:

int main() {
    printf("Hi" "Bye");
}

Sin embargo, esto no compila:

int main() {
    int test = 0;
    printf("Hi" (test ? "Bye" : "Goodbye"));
}

¿Cuál es la razón para eso?


95
La concatenación de cadenas es parte de la fase inicial de lexing; no forma parte de la expresión synatx de C. En otras palabras, no hay ningún valor de tipo "literal de cadena". Más bien, los literales de cadena son elementos léxicos del código fuente que forman valores.
Kerrek SB

24
Solo para aclarar la respuesta de @KerrekSB: la concatenación de las cadenas es parte del preprocesamiento del texto del código antes de compilarlo. Mientras que el operador ternario se evalúa en el tiempo de ejecución, después de compilar el código (o en caso de que todo sea constante, se puede hacer en el tiempo de compilación).
Eugene Sh.

2
Detalle: en esta publicación, "Hi"y "Bye"son cadenas literales , no cadenas como se usa en la biblioteca estándar de C. Con cadenas literales , el compilador concatenará "H\0i" "B\0ye". No es lo mismo consprintf(buf,"%s%s", "H\0i" "B\0ye");
chux - Reincorporar a Monica

15
Más o menos la misma razón por la que no puedes hacerloa (some_condition ? + : - ) b
user253751

4
Tenga en cuenta que ni siquiera printf("Hi" ("Bye"));funcionará, no requiere el operador ternario; el paréntesis es suficiente (aunque printf("Hi" test ? "Bye" : "Goodbye")tampoco se compilaría). Solo hay un número limitado de tokens que pueden seguir a un literal de cadena. La coma ,, el corchete abierto, el corchete [cerrado ](como en 1["abc"]- y sí, es espantoso), el corchete redondo )cerrado, el corchete cerrado }(en un inicializador o contexto similar) y el punto y coma ;son legítimos (y otro literal de cadena); No estoy seguro de que haya otros.
Jonathan Leffler

Respuestas:


121

Según el Estándar C (5.1.1.2 Fases de traducción)

1 La precedencia entre las reglas sintácticas de la traducción se especifica en las siguientes fases: 6)

  1. Los tokens literales de cadenas adyacentes están concatenados.

Y solo despues de eso

  1. Los caracteres de espacio en blanco que separan las fichas ya no son significativos. Cada token de preprocesamiento se convierte en un token. Los tokens resultantes se analizan sintáctica y semánticamente y se traducen como una unidad de traducción .

En esta construcción

"Hi" (test ? "Bye" : "Goodbye")

no hay tokens literales de cadena adyacentes. Entonces esta construcción no es válida.


43
Esto solo repite la afirmación de que no está permitido en C. No explica por qué , cuál era la pregunta. No sé por qué acumuló 26 votos a favor en 5 horas ... ¡y los acepto, nada menos! Felicidades.
Lightness Races in Orbit

4
Tengo que estar de acuerdo con @LightnessRacesinOrbit aquí. ¿Por qué no debería (test ? "Bye" : "Goodbye")evaluarse a cualquiera de los literales de cadena que esencialmente hacen "Hi" "Bye" o "Hi Goodbye"? (mi pregunta se responde en las otras respuestas)
Insane

48
@LightnessRacesinOrbit, porque cuando las personas normalmente preguntan por qué algo no se compila en C, piden una aclaración sobre qué regla se rompe, no por qué los Autores de Normas de la Antigüedad eligieron que fuera así.
user1717828

4
@LightnessRacesinOrbit La pregunta que describe probablemente estaría fuera de tema. No veo ninguna razón técnica por la que no sería posible implementar eso, por lo que sin una respuesta definitiva de los autores de la especificación, todas las respuestas se basarían en opiniones. Y, en general, no entraría en la categoría de preguntas "prácticas" o "que se pueden responder" (como indica el centro de ayuda que requerimos).
jpmc26

12
@LightnessRacesinOrbit Explica por qué : "porque el estándar C lo dice". La pregunta sobre por qué esta regla se define como está definida no estaría relacionada con el tema.
user11153

135

Según el estándar C11, capítulo §5.1.1.2, concatenación de cadenas literales adyacentes:

Los tokens literales de cadenas adyacentes están concatenados.

ocurre en la fase de traducción . Por otra parte:

printf("Hi" (test ? "Bye" : "Goodbye"));

involucra al operador condicional, que se evalúa en tiempo de ejecución . Entonces, en tiempo de compilación, durante la fase de traducción, no hay presentes literales de cadena adyacentes, por lo que la concatenación no es posible. La sintaxis no es válida y, por lo tanto, su compilador la informa.


Para desarrollar un poco la parte del por qué , durante la fase de preprocesamiento, los literales de cadena adyacentes se concatenan y representan como un literal de cadena única (token). El almacenamiento se asigna en consecuencia y el literal de cadena concatenado se considera como una sola entidad (un literal de cadena).

Por otro lado, en caso de concatenación en tiempo de ejecución, el destino debe tener suficiente memoria para contener la cadena literal concatenada; de lo contrario, no habrá forma de que se pueda acceder a la salida concatenada esperada . Ahora, en el caso de los literales de cadena , que están ya asignados de memoria en tiempo de compilación y no pueden ser extendidos a presión en cualquier entrada más entrante en o añadidos a los contenidos originales. En otras palabras, no habrá forma de que se pueda acceder (presentar) al resultado concatenado como un solo literal de cadena . Entonces, esta construcción es intrínsecamente incorrecta.

Solo para su información, para la concatenación de cadenas en tiempo de ejecución ( no literales ), tenemos la función de biblioteca strcat()que concatena dos cadenas . Aviso, la descripción menciona:

char *strcat(char * restrict s1,const char * restrict s2);

La strcat()función agrega una copia de la cadena apuntada por s2(incluido el carácter nulo de terminación) al final de la cadena apuntada pors1 . El carácter inicial de s2sobrescribe el carácter nulo al final de s1. [...]

Entonces, podemos ver que s1es una cadena , no una cadena literal . Sin embargo, dado que el contenido de s2no se modifica de ninguna manera, puede ser un literal de cadena .


es posible que desee agregar una explicación adicional sobre strcat: la matriz de destino debe ser lo suficientemente larga para recibir los caracteres de s2más un terminador nulo después de los caracteres presentes allí ya.
chqrlie

39

El preprocesador realiza la concatenación literal de cadena en tiempo de compilación. No hay forma de que esta concatenación tenga en cuenta el valor de test, que no se conoce hasta que el programa se ejecuta realmente. Por lo tanto, estos literales de cadena no se pueden concatenar.

Debido a que el caso general es que no tendría una construcción como esta para valores conocidos en tiempo de compilación, el estándar C fue diseñado para restringir la función de concatenación automática al caso más básico: cuando los literales están literalmente uno al lado del otro. .

Pero incluso si no dijera esta restricción de esa manera, o si la restricción se construyera de manera diferente, su ejemplo aún sería imposible de realizar sin hacer de la concatenación un proceso en tiempo de ejecución. Y, para eso, tenemos las funciones de biblioteca como strcat.


3
Acabo de leer suposiciones. Si bien lo que dice es bastante válido, no puede proporcionar fuentes para ello ya que no hay ninguna. La única fuente en lo que respecta a C es el documento estándar que (aunque en muchos casos es obvio) no indica por qué algunas cosas son como son, sino que simplemente indica que tienen que ser de esa forma específica. Entonces, ser tan quisquilloso con Vlad de la respuesta de Moscú es inapropiado. Dado que OP se puede dividir en "¿Por qué es así?" -Donde la única respuesta correcta es "Porque es C, y esa es la forma en que se define C", esa es la única respuesta literalmente correcta.
dhein

1
Esto es (admitido) falta de explicación. Pero aquí nuevamente se dice que la respuesta de Vlad sirve mucho más como explicación del problema central que la tuya. Nuevamente dijo: Si bien la información que usted da que puedo confirmar está relacionada y es correcta, no estoy de acuerdo con sus quejas. y aunque yo no consideraría el tuyo fuera de tema también, es por mi punto de vista más fuera de tema de lo que realmente es Vlads.
dhein

11
@Zaibis: La fuente soy yo. La respuesta de Vlad no es una explicación en absoluto; es simplemente una confirmación de la premisa de la pregunta. Ciertamente, ninguno de ellos está "fuera de tema" (es posible que desee buscar qué significa ese término). Pero tienes derecho a tu opinión.
Lightness Races in Orbit

Incluso después de leer los comentarios anteriores, todavía me pregunto quién votó en contra de esta respuesta ᶘ ᵒᴥᵒᶅ Creo que esta es una respuesta perfecta a menos que OP solicite más aclaraciones sobre esta respuesta.
Mohit Jain

2
No puedo distinguir por qué esta respuesta es aceptable para usted y la de @ VladfromMoscow no lo es, cuando ambos dicen lo mismo, y cuando la suya está respaldada por una cita y la suya no.
Marqués de Lorne

30

Porque C no tiene stringtipo. Los literales de cadena se compilan en charmatrices, referenciadas por un char*puntero.

C permite combinar literales adyacentes en tiempo de compilación , como en su primer ejemplo. El propio compilador de C tiene algunos conocimientos sobre cadenas. Pero esta información no está presente en tiempo de ejecución y , por lo tanto, no puede ocurrir la concatenación.

Durante el proceso de compilación, su primer ejemplo se "traduce" a:

int main() {
    static const char char_ptr_1[] = {'H', 'i', 'B', 'y', 'e', '\0'};
    printf(char_ptr_1);
}

Observe cómo el compilador combina las dos cadenas en una única matriz estática, antes de que el programa se ejecute.

Sin embargo, su segundo ejemplo se "traduce" a algo como esto:

int main() {
    static const char char_ptr_1[] = {'H', 'i', '\0'};
    static const char char_ptr_2[] = {'B', 'y', 'e', '\0'};
    static const char char_ptr_3[] = {'G', 'o', 'o', 'd', 'b', 'y', 'e', '\0'};
    int test = 0;
    printf(char_ptr_1 (test ? char_ptr_2 : char_ptr_3));
}

Debe quedar claro por qué esto no se compila. El operador ternario ?se evalúa en tiempo de ejecución, no en tiempo de compilación, cuando las "cadenas" ya no existen como tales, sino solo como charmatrices simples , referenciadas por char*punteros. A diferencia de los literales de cadena adyacentes , los punteros de caracteres adyacentes son simplemente un error de sintaxis.


2
Excelente respuesta, posiblemente la mejor aquí. "Debe quedar claro por qué esto no se compila". Podría considerar expandir eso con "porque el operador ternario es un condicional evaluado en tiempo de ejecución, no en tiempo de compilación ".
gato

¿No debería static const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};ser así static const char *char_ptr_1 = "HiBye";y de manera similar para el resto de las sugerencias?
Spikatrix

@CoolGuy Cuando escribes, static const char *char_ptr_1 = "HiBye";el compilador traduce la línea a static const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};, así que no, no debe escribirse "como una cadena". Como dice la Respuesta, las cadenas se compilan en una matriz de caracteres, y si estuviera asignando una matriz de caracteres en su forma más "cruda", usaría una lista de caracteres separados por comas, comostatic const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};
Ankush

3
@Ankush Sí. Pero aunque static const char str[] = {'t', 'e', 's', 't', '\0'};es lo mismo que static const char str[] = "test";, nostatic const char* ptr = "test"; es lo mismo que static const char* ptr = {'t', 'e', 's', 't', '\0'};. El primero es válido y se compilará, pero el segundo no es válido y hace lo que espera.
Spikatrix

He desarrollado el último párrafo y he corregido los ejemplos de código, ¡gracias!
firmar

12

Si realmente desea que ambas ramas produzcan constantes de cadena en tiempo de compilación para elegirlas en tiempo de ejecución, necesitará una macro.

#include <stdio.h>
#define ccat(s, t, a, b) ((t)?(s a):(s b))

int
main ( int argc, char **argv){
  printf("%s\n", ccat("hello ", argc > 2 , "y'all", "you"));
  return 0;
}

10

¿Cuál es la razón para eso?

Su código que usa el operador ternario elige condicionalmente entre dos literales de cadena. Independientemente de la condición conocida o desconocida, esto no se puede evaluar en tiempo de compilación, por lo que no se puede compilar. Incluso esta declaración printf("Hi" (1 ? "Bye" : "Goodbye"));no se compilaría. La razón se explica en profundidad en las respuestas anteriores. Otra posibilidad de hacer una declaración de este tipo utilizando un operador ternario válido para compilar , también implicaría una etiqueta de formato y el resultado de la declaración del operador ternario formateado como argumento adicional para printf. Incluso entonces, la printf()impresión daría la impresión de "haber concatenado" esas cadenas solo en el tiempo de ejecución y tan pronto como éste .

#include <stdio.h>

int main() {
    int test = 0;
    printf("Hi %s\n", (test ? "Bye" : "Goodbye")); //specify format and print as result
}

3
SO no es un sitio de tutoriales. Debe dar una respuesta al OP y no un tutorial.
Michi

1
Esto no responde a la pregunta del OP. Puede ser un intento de resolver el problema subyacente del OP, pero realmente no sabemos qué es eso.
Keith Thompson

1
printfno requiere un especificador de formato; si solo se hiciera la concatenación en tiempo de compilación (que no lo es), el uso de printf de OP sería válido.
David Conrad

Gracias por tu comentario, @David Conrad. Mi redacción descuidada de hecho parecería que la declaración printf()requeriría una etiqueta de formato, lo cual es absolutamente falso. ¡Corregido!
user3078414

Esa es una mejor redacción. +1 gracias.
David Conrad

7

En printf("Hi" "Bye");tiene dos matrices consecutivas de char que el compilador puede convertir en una sola matriz.

En printf("Hi" (test ? "Bye" : "Goodbye"));tiene una matriz seguida de un puntero a char (una matriz convertida en un puntero a su primer elemento). El compilador no puede combinar una matriz y un puntero.


0

Para responder a la pregunta, iría a la definición de printf. La función printf espera const char * como argumento. Cualquier literal de cadena como "Hola" es un carácter constante *; sin embargo, una expresión como (test)? "str1" : "str2"NO es un carácter constante * porque el resultado de dicha expresión se encuentra solo en tiempo de ejecución y, por lo tanto, es indeterminado en tiempo de compilación, un hecho que hace que el compilador se queje. Por otro lado, esto funciona perfectamente bienprintf("hi %s", test? "yes":"no")


* sin embargo, una expresión como (test)? "str1" : "str2"NO es un const char*... ¡Por supuesto que lo es! No es una expresión constante, pero su tipo sí lo es const char * . Estaría perfectamente bien escribir printf(test ? "hi " "yes" : "hi " "no"). El problema del OP no tiene nada que ver printf, "Hi" (test ? "Bye" : "Goodbye")es un error de sintaxis sin importar cuál sea el contexto de la expresión.
chqrlie

Convenido.
Confundí

-4

Esto no se compila porque la lista de parámetros para la función printf es

(const char *format, ...)

y

("Hi" (test ? "Bye" : "Goodbye"))

no se ajusta a la lista de parámetros.

gcc intenta darle sentido imaginando que

(test ? "Bye" : "Goodbye")

es una lista de parámetros y se queja de que "Hola" no es una función.


6
Bienvenido a Stack Overflow. Tiene razón en que no coincide con la printf()lista de argumentos, pero eso se debe a que la expresión no es válida en ningún lugar, no solo en una printf()lista de argumentos. En otras palabras, ha elegido una razón demasiado especializada para el problema; el problema general es que "Hi" (no es válido en C, mucho menos en una llamada a printf(). Le sugiero que elimine esta respuesta antes de que sea rechazada.
Jonathan Leffler

No es así como funciona C. Esto no se analiza como un intento de llamar a una cadena literal como PHP.
gato
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.