Por que C não permite a concatenação de strings ao usar o operador condicional?

95

O código a seguir é compilado sem problemas:

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

No entanto, isso não compila:

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

Qual é o motivo disso?

José D.
fonte
95
A concatenação de strings faz parte da fase inicial de lexing; não faz parte da sintaxe de expressão de C. Em outras palavras, não há valor do tipo "string literal". Em vez disso, os literais de string são elementos lexicais do código-fonte que formam valores.
Kerrek SB de
24
Apenas para esclarecer a resposta @KerrekSB - a concatenação das strings é uma parte do pré-processamento do texto do código antes de compilá-lo. Enquanto o operador ternário é avaliado em tempo de execução, após a compilação do código (ou caso tudo seja constante pode ser feito em tempo de compilação).
Eugene Sh.
2
Detalhe: neste post, "Hi"e "Bye"são strings literais , não strings como as usadas na biblioteca C padrão. Com literais de string , o compilador irá concatenar "H\0i" "B\0ye". Não é o mesmo comsprintf(buf,"%s%s", "H\0i" "B\0ye");
chux - Reintegrar Monica
15
Mais ou menos o mesmo motivo pelo qual você não pode fazera (some_condition ? + : - ) b
user253751
4
Observe que nem mesmo printf("Hi" ("Bye"));funciona - não requer o operador ternário; o parêntese é suficiente (embora printf("Hi" test ? "Bye" : "Goodbye")também não seja compilado). Há apenas um número limitado de tokens que podem seguir um literal de string. Vírgula ,, colchete aberto, colchete [fechado ](como em 1["abc"]- e sim, é horrível), colchete redondo ), colchete fechado }(em um inicializador ou contexto semelhante) e ponto-e-vírgula ;são legítimos (e outro literal de string); Não tenho certeza se há outros.
Jonathan Leffler

Respostas:

121

De acordo com o padrão C (5.1.1.2 fases de tradução)

1 A precedência entre as regras de sintaxe de tradução é especificada pelas seguintes fases.6)

  1. Os tokens literais de string adjacentes são concatenados.

E só depois disso

  1. Os caracteres de espaço em branco que separam os tokens não são mais significativos. Cada token de pré-processamento é convertido em um token. Os tokens resultantes são sintaticamente e semanticamente analisados ​​e traduzidos como uma unidade de tradução .

Nesta construção

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

não há tokens literais de string adjacentes. Portanto, esta construção é inválida.

Vlad de Moscou
fonte
43
Isso apenas repete a afirmação de que não é permitido em C. Não explica por que, qual era a questão. Não sei por que acumulou 26 votos positivos em 5 horas .... e o aceite, nada menos! Parabéns.
Lightness Races in Orbit
4
Tenho que concordar com @LightnessRacesinOrbit aqui. Por que não deveria (test ? "Bye" : "Goodbye")evaulate para qualquer um dos literais de string essencialmente fazendo "Hi" "Bye" ou "Hi Goodbye"? (minha pergunta é respondida nas outras respostas)
Insano
48
@LightnessRacesinOrbit, porque quando as pessoas normalmente perguntam por que algo não compila em C, elas estão pedindo esclarecimentos sobre qual regra ele quebra, e não por que os Autores de Padrões da Antiguidade escolheram que fosse assim.
user1717828
4
@LightnessRacesinOrbit A pergunta que você descreve provavelmente está fora do assunto. Não vejo nenhuma razão técnica pela qual não seria possível implementar isso, portanto, sem uma resposta definitiva dos autores da especificação, todas as respostas seriam baseadas em opiniões. E geralmente não se enquadram na categoria de perguntas "práticas" ou "respondíveis" (como a Central de Ajuda indica que exigimos).
jpmc26
12
@LightnessRacesinOrbit Isso explica o porquê : "porque o padrão C disse". A pergunta sobre por que essa regra é definida como definida seria fora do tópico.
user11153
135

De acordo com o padrão C11, capítulo §5.1.1.2, concatenação de literais de string adjacentes:

Os tokens literais de string adjacentes são concatenados.

acontece na fase de tradução . Por outro lado:

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

envolve o operador condicional, que é avaliado em tempo de execução . Portanto, em tempo de compilação, durante a fase de tradução, não há literais de string adjacentes presentes, portanto, a concatenação não é possível. A sintaxe é inválida e, portanto, relatada por seu compilador.


Para elaborar um pouco sobre a parte porque , durante a fase de pré-processamento, os literais de string adjacentes são concatenados e representados como um único literal de string (token). O armazenamento é alocado de acordo e o literal de string concatenado é considerado como uma entidade única (um literal de string).

Por outro lado, no caso de concatenação em tempo de execução, o destino deve ter memória suficiente para conter o literal de string concatenado , caso contrário, não haverá nenhuma maneira de acessar a saída concatenada esperada . Agora, no caso de strings literais , eles já estão alocados de memória em tempo de compilação e não pode ser estendido para caber em qualquer entrada de mais de entrada em ou anexado ao conteúdo original. Em outras palavras, não haverá como o resultado concatenado ser acessado (apresentado) como um único literal de string . Portanto, essa construção é inerentemente incorreta.

Apenas para sua informação, para concatenação de string em tempo de execução ( não literais ), temos a função de biblioteca strcat()que concatena duas strings . Observe, a descrição menciona:

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

A strcat()função anexa uma cópia da string apontada por s2(incluindo o caractere nulo de terminação) ao final da string apontada pors1 . O caractere inicial de s2substitui o caractere nulo no final de s1. [...]

Portanto, podemos ver que s1é uma string , não uma string literal . No entanto, como o conteúdo de s2não é alterado de forma alguma, pode muito bem ser um literal de string .

Sourav Ghosh
fonte
você pode querer adicionar uma explicação extra sobre strcat: o array de destino deve ser longo o suficiente para receber os caracteres de s2mais um terminador nulo após os caracteres já presentes lá.
chqrlie
39

A concatenação de literal de string é executada pelo pré-processador em tempo de compilação. Não há como essa concatenação reconhecer o valor de test, que não é conhecido até que o programa seja realmente executado. Portanto, esses literais de string não podem ser concatenados.

Como o caso geral é que você não teria uma construção como essa para valores conhecidos em tempo de compilação, o padrão C foi projetado para restringir o recurso de autocacatenação ao caso mais básico: quando os literais estão literalmente lado a lado .

Mas mesmo que essa restrição não seja expressa dessa forma, ou se a restrição for construída de forma diferente, seu exemplo ainda seria impossível de realizar sem tornar a concatenação um processo de tempo de execução. E, para isso, temos as funções de biblioteca como strcat.

Lightness Races in Orbit
fonte
3
Acabei de ler suposições. Embora o que você diga seja bastante válido, você não pode fornecer fontes para isso, pois não há nenhuma. A única fonte em relação a C é o documento padrão que (embora seja óbvio em muitos casos) não declara por que algumas coisas são do jeito que são, mas apenas afirma que devem ser daquela maneira específica. Portanto, ser tão exigente quanto à resposta de Vlad de Moscou é inadequado. Visto que o OP pode ser dividido em "Por que é assim?" -Onde a única resposta correta for "Porque é C, e essa é a forma como C é definido", essa é a única resposta correta literalmente direta.
dhein
1
Isso (admitido) carece de explicação. Mas aqui novamente está sendo dito que a resposta de Vlad servindo muito mais como uma explicação para o problema central do que a sua. Disse novamente: Embora as informações que você forneceu eu posso confirmar estejam relacionadas e estejam corretas, eu discordo de suas reclamações. e enquanto eu não consideraria seu offtopic também, do meu ponto de vista é mais offtopic do que Vlads realmente é.
dhein
11
@Zaibis: A fonte sou eu. A resposta de Vlad não é uma explicação; é apenas uma confirmação da premissa da questão. Certamente nenhum deles está "fora do assunto" (você pode querer pesquisar o que esse termo significa). Mas você tem direito à sua opinião.
Lightness Races in Orbit
Mesmo depois de ler os comentários acima, ainda me pergunto quem votou contra esta resposta ᶘ ᵒᴥᵒᶅ Acredito que esta é uma resposta perfeita, a menos que OP peça mais esclarecimentos sobre esta resposta.
Mohit Jain
2
Não consigo distinguir por que essa resposta é aceitável para você e a de @VladomMoscow não é, quando os dois dizem a mesma coisa e quando a dele é apoiada por uma citação e a sua não.
Marquês de Lorne
30

Porque C não tem stringtipo. Literais de string são compilados em chararrays, referenciados por um char*ponteiro.

C permite que literais adjacentes sejam combinados em tempo de compilação , como em seu primeiro exemplo. O próprio compilador C tem algum conhecimento sobre strings. Mas essas informações não estão presentes no tempo de execução e , portanto, a concatenação não pode acontecer.

Durante o processo de compilação, seu primeiro exemplo é "traduzido" para:

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

Observe como as duas strings são combinadas em um único array estático pelo compilador, antes que o programa seja executado.

No entanto, seu segundo exemplo é "traduzido" para algo assim:

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));
}

Deve ficar claro por que isso não compila. O operador ternário ?é avaliado em tempo de execução, não em tempo de compilação, quando as "strings" não existem mais como tais, mas apenas como charmatrizes simples , referenciadas por char*ponteiros. Ao contrário dos literais de string adjacentes , os ponteiros char adjacentes são simplesmente um erro de sintaxe.

Sem sinal
fonte
2
Excelente resposta, possivelmente a melhor aqui. "Deve ficar claro por que isso não compila." Você pode considerar expandir isso com "porque o operador ternário é um condicional avaliado em tempo de execução, não em tempo de compilação ".
cat
Não deveria static const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};ser static const char *char_ptr_1 = "HiBye";e da mesma forma para o resto dos ponteiros?
Spikatrix
@CoolGuy Quando você escreve, static const char *char_ptr_1 = "HiBye";o compilador traduz a linha para static const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};, então não, ela não deve ser escrita "como uma string". Como diz a resposta, as strings são compiladas para uma matriz de caracteres, e se você atribuísse uma matriz de caracteres em sua forma mais "bruta", você usaria uma lista de caracteres separada por vírgulas, exatamente comostatic const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};
Ankush
3
@Ankush Sim. Mas embora static const char str[] = {'t', 'e', 's', 't', '\0'};seja o mesmo que static const char str[] = "test";, nãostatic const char* ptr = "test"; é o mesmo que . O primeiro é válido e irá compilar, mas o último é inválido e faz o que você espera. static const char* ptr = {'t', 'e', 's', 't', '\0'};
Spikatrix
Completei o último parágrafo e corrigi os exemplos de código, obrigado!
Não assinado
12

Se você realmente deseja que ambas as ramificações produzam constantes de string de tempo de compilação a serem escolhidas em tempo de execução, você precisará de uma 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;
}
Eric
fonte
10

Qual é o motivo disso?

Seu código usando o operador ternário escolhe condicionalmente entre dois literais de string. Não importa a condição conhecida ou desconhecida, isso não pode ser avaliado em tempo de compilação, portanto, não pode ser compilado. Mesmo esta declaração printf("Hi" (1 ? "Bye" : "Goodbye"));não compilaria. O motivo é explicado em detalhes nas respostas acima. Outra possibilidade de fazer tal declaração usando o operador ternário válido para compilar também envolveria uma tag de formato e o resultado da declaração do operador ternário formatado como argumento adicional para printf. Mesmo assim, a printf()impressão daria a impressão de "ter concatenado" essas strings apenas no tempo de execução e logo no início .

#include <stdio.h>

int main() {
    int test = 0;
    printf("Hi %s\n", (test ? "Bye" : "Goodbye")); //specify format and print as result
}
user3078414
fonte
3
SO não é um site de tutorial. Você deve dar uma Resposta ao OP e não um tutorial.
Michi
1
Isso não responde à pergunta do OP. Pode ser uma tentativa de resolver o problema subjacente do OP, mas não sabemos realmente o que é.
Keith Thompson
1
printfnão requer um especificador de formato; se apenas a concatenação fosse feita em tempo de compilação (o que não é), o uso de printf pelo OP seria válido.
David Conrad
Obrigado por sua observação, @David Conrad. Minha formulação desleixada realmente faria parecer que declarar printf()exigiria uma tag de formatação, o que não é absolutamente verdade. Corrigido!
user3078414
Essa é uma formulação melhor. +1 Obrigado.
David Conrad
7

Em printf("Hi" "Bye");você tem dois arrays consecutivos de char que o compilador pode transformar em um único array.

Em printf("Hi" (test ? "Bye" : "Goodbye"));você tem uma matriz seguida por um ponteiro para char (uma matriz convertida em um ponteiro para seu primeiro elemento). O compilador não pode mesclar uma matriz e um ponteiro.

pmg
fonte
0

Para responder à pergunta - eu iria para a definição de printf. A função printf espera const char * como argumento. Qualquer string literal como "Hi" é um const char *; no entanto, uma expressão como (test)? "str1" : "str2"NÃO é const char * porque o resultado de tal expressão é encontrado apenas em tempo de execução e, portanto, é indeterminado em tempo de compilação, um fato que devidamente faz com que o compilador reclame. Por outro lado - isso funciona perfeitamente bemprintf("hi %s", test? "yes":"no")

Stats_Lover
fonte
* entretanto, uma expressão como (test)? "str1" : "str2"NÃO é um const char*... Claro que é! Não é uma expressão constante, mas seu tipo é const char * . Seria perfeitamente normal escrever printf(test ? "hi " "yes" : "hi " "no"). O problema do OP não tem nada a ver com printf, "Hi" (test ? "Bye" : "Goodbye")é um erro de sintaxe, não importa qual seja o contexto da expressão.
chqrlie
Acordado. Eu confundi a saída de uma expressão com a própria expressão
Stats_Lover
-4

Isso não compila porque a lista de parâmetros para a função printf é

(const char *format, ...)

e

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

não cabe na lista de parâmetros.

O gcc tenta dar sentido a isso imaginando que

(test ? "Bye" : "Goodbye")

é uma lista de parâmetros e reclama que "Hi" não é uma função.

Rodbots
fonte
6
Bem-vindo ao Stack Overflow. Você está certo ao dizer que não corresponde à printf()lista de argumentos, mas isso é porque a expressão não é válida em qualquer lugar - não apenas em uma printf()lista de argumentos. Em outras palavras, você escolheu um motivo muito especializado para o problema; o problema geral é que isso "Hi" (não é válido em C, muito menos em uma chamada para printf(). Eu sugiro que você exclua esta resposta antes que ela seja rejeitada.
Jonathan Leffler
Não é assim que C funciona. Isso não é analisado como uma tentativa de chamar um literal de string como o PHP.
gato