Perché C non consente la concatenazione di stringhe quando si utilizza l'operatore condizionale?


95

Il codice seguente viene compilato senza problemi:

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

Tuttavia, questo non compila:

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

Qual è il motivo?


95
La concatenazione di stringhe fa parte della prima fase di lexing; non fa parte della sintassi dell'espressione di C. In altre parole, non esiste alcun valore di tipo "stringa letterale". Piuttosto, i letterali stringa sono elementi lessicali del codice sorgente che formano valori.
Kerrek SB

24
Solo per chiarire la risposta di @KerrekSB: la concatenazione delle stringhe fa parte della pre-elaborazione del testo del codice prima di compilarlo. Mentre l'operatore ternario viene valutato in runtime, dopo che il codice è stato compilato (o nel caso in cui tutto sia costante, può essere fatto in fase di compilazione).
Eugene Sh.

2
Dettaglio: in questo post, "Hi"e "Bye"sono stringhe letterali , non stringhe come usate nella libreria standard C. Con i valori letterali stringa , il compilatore si concatenerà "H\0i" "B\0ye". Non è la stessa cosa consprintf(buf,"%s%s", "H\0i" "B\0ye");
chux - Ripristina Monica

15
Più o meno lo stesso motivo per cui non puoi farea (some_condition ? + : - ) b
user253751

4
Nota che anche printf("Hi" ("Bye"));non funzionerà - non richiede l'operatore ternario; la parentesi è sufficiente ( printf("Hi" test ? "Bye" : "Goodbye")anche se non viene compilata). È disponibile solo un numero limitato di token che può seguire una stringa letterale. Virgola ,, parentesi quadra aperta, parentesi quadra [chiusa ](come in 1["abc"]- e sì, è raccapricciante), parentesi quadra chiusa, parentesi graffa )chiusa }(in un inizializzatore o contesto simile) e punto e virgola ;sono legittimi (e un'altra stringa letterale); Non sono sicuro che ce ne siano altri.
Jonathan Leffler

Risposte:


121

Secondo lo Standard C (5.1.1.2 Fasi di traduzione)

1 La precedenza tra le regole di sintassi della traduzione è specificata dalle seguenti fasi 6)

  1. I token letterali stringa adiacenti vengono concatenati.

E solo dopo

  1. I caratteri spazi bianchi che separano i token non sono più significativi. Ogni token di pre-elaborazione viene convertito in un token. I token risultanti vengono analizzati sintatticamente e semanticamente e tradotti come un'unità di traduzione .

In questa costruzione

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

non ci sono token letterali stringa adiacenti. Quindi questa costruzione non è valida.


43
Questo ripete solo l'affermazione che non è consentito in C. Non spiega perché , che era la domanda. Non so perché ha accumulato 26 voti positivi in ​​5 ore ... e l'accettazione, nientemeno! Congratulazioni.
Gare di leggerezza in orbita

4
Devo essere d'accordo con @LightnessRacesinOrbit qui. Perché non dovrebbe (test ? "Bye" : "Goodbye")evaulare uno dei letterali stringa essenzialmente facendo "Hi" "Bye" o "Hi Goodbye"? (la mia domanda trova risposta nelle altre risposte)
Insane

48
@LightnessRacesinOrbit, perché quando le persone normalmente chiedono perché qualcosa non si compila in C, chiedono chiarimenti su quale regola infrange, non perché Standards Authors of Antiquity ha scelto che sia così.
user1717828

4
@LightnessRacesinOrbit La domanda che descrivi sarebbe probabilmente fuori tema. Non vedo alcun motivo tecnico per cui non sarebbe possibile implementarlo, quindi senza una risposta definitiva da parte degli autori della specifica, tutte le risposte sarebbero basate sull'opinione. E generalmente non rientra nella categoria delle domande "pratiche" o "a cui è possibile rispondere" (come indica il centro assistenza che richiediamo).
jpmc26

12
@LightnessRacesinOrbit Questo spiega perché : "perché lo standard C lo diceva". La domanda sul perché questa regola è definita come definita sarebbe fuori tema.
user11153

135

Secondo lo standard C11, capitolo §5.1.1.2, concatenazione di stringhe letterali adiacenti:

I token letterali stringa adiacenti vengono concatenati.

avviene in fase di traduzione . D'altro canto:

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

coinvolge l'operatore condizionale, che viene valutato in fase di esecuzione . Quindi, in fase di compilazione, durante la fase di traduzione, non sono presenti stringhe letterali adiacenti, quindi la concatenazione non è possibile. La sintassi non è valida e quindi segnalata dal compilatore.


Per approfondire un po 'la parte del perché , durante la fase di preelaborazione, le stringhe letterali adiacenti vengono concatenate e rappresentate come una singola stringa letterale (token). La memoria viene allocata di conseguenza e il valore letterale stringa concatenato viene considerato come una singola entità (un valore letterale stringa).

D'altra parte, in caso di concatenazione in fase di esecuzione, la destinazione dovrebbe avere memoria sufficiente per contenere la stringa concatenata letterale , altrimenti non sarà possibile accedere all'output concatenato previsto . Ora, in caso di stringhe letterali , sono già allocate in memoria in fase di compilazione e non possono essere estese per adattarsi a ulteriori input in ingresso o aggiunti al contenuto originale. In altre parole, non sarà possibile accedere (presentare) al risultato concatenato come una singola stringa letterale . Quindi, questo costrutto è intrinsecamente errato.

Cordiali saluti, per run-time stringa ( non letterali ) concatenazione, abbiamo la funzione di libreria strcat()che concatena due stringhe . Avviso, la descrizione menziona:

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

La strcat()funzione aggiunge una copia della stringa a cui punta s2(incluso il carattere null di terminazione) alla fine della stringa a cui puntas1 . Il carattere iniziale di s2sovrascrive il carattere null alla fine di s1. [...]

Quindi, possiamo vedere, s1è una stringa , non una stringa letterale . Tuttavia, poiché il contenuto di s2non viene alterato in alcun modo, può benissimo essere una stringa letterale .


potresti voler aggiungere una spiegazione extra su strcat: l'array di destinazione deve essere abbastanza lungo da ricevere i caratteri da s2più un terminatore nullo dopo i caratteri già presenti.
chqrlie

39

La concatenazione di stringhe letterali viene eseguita dal preprocessore in fase di compilazione. Non c'è modo per questa concatenazione di essere a conoscenza del valore di test, che non è noto finché il programma non viene effettivamente eseguito. Pertanto, questi valori letterali stringa non possono essere concatenati.

Poiché il caso generale è che non avresti una costruzione come questa per i valori noti in fase di compilazione, lo standard C è stato progettato per limitare la funzionalità di auto-concatenazione al caso più semplice: quando i letterali sono letteralmente uno accanto all'altro .

Ma anche se non definisse questa restrizione in quel modo, o se la restrizione fosse costruita in modo diverso, il tuo esempio sarebbe comunque impossibile da realizzare senza rendere la concatenazione un processo di runtime. E, per questo, abbiamo le funzioni di libreria come strcat.


3
Ho appena letto le ipotesi. Anche se quello che dici è più o meno valido, non puoi fornire fonti per esso poiché non ce ne sono. L'unica fonte per quanto riguarda il C è il documento standard che (sebbene in molti casi sia ovvio) non afferma perché alcune cose sono come sono, ma afferma semplicemente che devono essere in quel modo specifico. Quindi essere così pignoli riguardo a Vlad dalla risposta di Mosca è inappropriato. Poiché OP può essere suddiviso in "Perché è così?" -Dove l'unica risposta corretta è "Perché è C, e questo è il modo in cui è definito C", questa è l'unica risposta letteralmente corretta.
dhein

1
Questo è (ammesso) privo di spiegazioni. Ma anche qui essere detto è che la risposta di Vlad serve molto di più come spiegazione al problema centrale rispetto al tuo. Di nuovo ha detto: sebbene le informazioni che fornisci posso confermare sono correlate e corrette, non sono d'accordo con i tuoi reclami. e anche se non ti considererei offtopico, è dal mio punto di vista più offtopico di quanto lo sia Vlads.
dhein

11
@Zaibis: La fonte sono io. La risposta di Vlad non è affatto una spiegazione; è solo una conferma della premessa della domanda. Certamente nessuno dei due è "fuori tema" (potresti voler cercare cosa significa questo termine). Ma hai diritto alla tua opinione.
Gare di leggerezza in orbita

Anche dopo aver letto i commenti sopra, mi chiedo ancora chi abbia votato negativamente questa risposta ᶘ ᵒᴥᵒᶅ Credo che questa sia una risposta perfetta a meno che OP non chieda ulteriori chiarimenti su questa risposta.
Mohit Jain

2
Non riesco a distinguere perché questa risposta sia accettabile per te e per @VladfromMoscow non lo è, quando entrambi dicono la stessa cosa, e quando la sua è sostenuta da una citazione e la tua no.
Marchese di Lorne

30

Perché C non ha stringtipo. I valori letterali stringa vengono compilati in charmatrici, a cui fa riferimento un char*puntatore.

C consente di combinare letterali adiacenti in fase di compilazione , come nel primo esempio. Il compilatore C stesso ha una certa conoscenza delle stringhe. Ma queste informazioni non sono presenti in fase di esecuzione e quindi la concatenazione non può avvenire.

Durante il processo di compilazione, il tuo primo esempio viene "tradotto" in:

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

Nota come le due stringhe vengono combinate in un singolo array statico dal compilatore, prima che il programma venga eseguito.

Tuttavia, il tuo secondo esempio è "tradotto" in qualcosa di simile:

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

Dovrebbe essere chiaro il motivo per cui questo non viene compilato. L'operatore ternario ?viene valutato a runtime, non in fase di compilazione, quando le "stringhe" non esistono più in quanto tali, ma solo come semplici chararray, referenziati da char*puntatori. A differenza dei valori letterali stringa adiacenti , i puntatori char adiacenti sono semplicemente un errore di sintassi.


2
Ottima risposta, forse la migliore qui. "Dovrebbe essere chiaro il motivo per cui non viene compilato." Potresti considerare di estenderlo con "perché l'operatore ternario è un condizionale valutato in fase di esecuzione non in fase di compilazione ".
gatto

Non dovrebbe static const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};essere static const char *char_ptr_1 = "HiBye";e allo stesso modo per il resto degli indicatori?
Spikatrix

@CoolGuy Quando scrivi static const char *char_ptr_1 = "HiBye";il compilatore traduce la riga in static const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};, quindi no, non dovrebbe essere scritta "come una stringa". Come dice la risposta, le stringhe vengono compilate in un array di caratteri e se assegnassi un array di caratteri nella sua forma più "grezza", useresti un elenco di caratteri separato da virgole, proprio comestatic const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};
Ankush

3
@Ankush Sì. Ma sebbene static const char str[] = {'t', 'e', 's', 't', '\0'};sia lo stesso di static const char str[] = "test";, nonstatic const char* ptr = "test"; è lo stesso di . Il primo è valido e verrà compilato, ma il secondo non è valido e fa quello che ti aspetti. static const char* ptr = {'t', 'e', 's', 't', '\0'};
Spikatrix

Ho arricchito l'ultimo paragrafo e corretto gli esempi di codice, grazie!
Non firmato

12

Se vuoi davvero che entrambi i rami producano costanti di stringa in fase di compilazione da scegliere in fase di esecuzione, avrai bisogno di 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

Qual è il motivo?

Il codice che utilizza l'operatore ternario sceglie in modo condizionale tra due stringhe letterali. Indipendentemente dalle condizioni note o sconosciute, non possono essere valutate in fase di compilazione, quindi non possono essere compilate. Anche questa dichiarazione printf("Hi" (1 ? "Bye" : "Goodbye"));non si compilerebbe. Il motivo è spiegato in profondità nelle risposte sopra. Un'altra possibilità di rendere valida per la compilazione una dichiarazione del genere utilizzando l'operatore ternario , coinvolgerebbe anche un tag di formato e il risultato dell'istruzione dell'operatore ternario formattato come argomento aggiuntivo per printf. Anche in questo caso, la printf()stampa darebbe l'impressione di "aver concatenato" quelle stringhe solo durante e fin dal runtime .

#include <stdio.h>

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

3
SO non è un sito di tutorial. Dovresti dare una risposta all'OP e non un tutorial.
Michi

1
Questo non risponde alla domanda del PO. Potrebbe essere un tentativo di risolvere il problema di fondo dell'OP, ma non sappiamo davvero cosa sia.
Keith Thompson

1
printfnon richiede un identificatore di formato; se solo la concatenazione fosse fatta in fase di compilazione (cosa che non è), l'uso di printf da parte di OP sarebbe valido.
David Conrad

Grazie per il tuo commento, @David Conrad. La mia formulazione sciatta farebbe sembrare che l'affermazione printf()richiedesse un tag di formato, il che non è assolutamente vero. Corretto!
user3078414

È una formulazione migliore. +1 Grazie.
David Conrad

7

In printf("Hi" "Bye");hai due array consecutivi di char che il compilatore può trasformare in un singolo array.

In printf("Hi" (test ? "Bye" : "Goodbye"));hai un array seguito da un puntatore a char (un array convertito in un puntatore al suo primo elemento). Il compilatore non può unire una matrice e un puntatore.


0

Per rispondere alla domanda, andrei alla definizione di printf. La funzione printf si aspetta const char * come argomento. Qualsiasi stringa letterale come "Hi" è un const char *; tuttavia un'espressione come (test)? "str1" : "str2"NON è un const char * perché il risultato di tale espressione si trova solo in fase di esecuzione e quindi è indeterminato in fase di compilazione, un fatto che causa debitamente il reclamo del compilatore. D'altra parte, funziona perfettamenteprintf("hi %s", test? "yes":"no")


* tuttavia un'espressione come (test)? "str1" : "str2"NON è un const char*... Certo che lo è! Non è un'espressione costante, ma il suo tipo lo è const char * . Sarebbe perfetto scrivere printf(test ? "hi " "yes" : "hi " "no"). Il problema dell'OP non ha nulla a che fare con printf, "Hi" (test ? "Bye" : "Goodbye")è un errore di sintassi, non importa quale sia il contesto dell'espressione.
chqrlie

Concordato. Ho confuso l'output di un'espressione con l'espressione stessa
Stats_Lover

-4

Questo non viene compilato perché l'elenco dei parametri per la funzione printf è

(const char *format, ...)

e

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

non si adatta all'elenco dei parametri.

gcc cerca di dargli un senso immaginandolo

(test ? "Bye" : "Goodbye")

è un elenco di parametri e si lamenta del fatto che "Hi" non è una funzione.


6
Benvenuto in Stack Overflow. Hai ragione sul fatto che non corrisponde printf()all'elenco degli argomenti, ma è perché l'espressione non è valida da nessuna parte, non solo in un printf()elenco di argomenti. In altre parole, hai scelto una ragione troppo specifica per il problema; il problema generale è che "Hi" (non è valido in C, figuriamoci in una chiamata a printf(). Ti suggerisco di eliminare questa risposta prima che venga votata.
Jonathan Leffler

Non è così che funziona C. Questo non viene analizzato come un tentativo di chiamare una stringa letterale come PHP.
gatto
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.