Warum erlaubt C keine Verkettung von Zeichenfolgen, wenn der bedingte Operator verwendet wird?

95

Der folgende Code wird problemlos kompiliert:

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

Dies wird jedoch nicht kompiliert:

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

Was ist der Grund dafür?

José D.
quelle
95
Die Verkettung von Zeichenfolgen ist Teil der frühen Lexierungsphase. Es ist nicht Teil des Ausdrucks Synatx von C. Mit anderen Worten, es gibt keinen Wert vom Typ "String-Literal". String-Literale sind vielmehr lexikalische Elemente des Quellcodes, die Werte bilden.
Kerrek SB
24
Nur um die @ KerrekSB-Antwort zu verdeutlichen: Die Verkettung der Zeichenfolgen ist Teil der Vorverarbeitung des Codetextes vor dessen Kompilierung. Während der ternäre Operator zur Laufzeit ausgewertet wird, kann der Code nach dem Kompilieren in der Kompilierungszeit ausgeführt werden (oder wenn alles konstant ist).
Eugene Sh.
2
Detail: In diesem Beitrag "Hi"und "Bye"sind Stringliterale , nicht Strings , wie in der C - Standardbibliothek verwendet. Bei String-Literalen wird der Compiler verkettet "H\0i" "B\0ye". Nicht dasselbe mitsprintf(buf,"%s%s", "H\0i" "B\0ye");
chux
15
Mehr oder weniger der gleiche Grund, den Sie nicht tun könnena (some_condition ? + : - ) b
user253751
4
Beachten Sie, dass dies printf("Hi" ("Bye"));nicht einmal funktioniert - es ist kein ternärer Operator erforderlich. Die Klammer ist ausreichend ( printf("Hi" test ? "Bye" : "Goodbye")würde aber auch nicht kompiliert werden). Es gibt nur eine begrenzte Anzahl von Token, die einem Zeichenfolgenliteral folgen können. Komma ,, offene eckige Klammer [, enge eckige Klammer ](wie in 1["abc"]- und ja, es ist grausam), enge runde Klammer ), enge geschweifte Klammer }(in einem Initialisierer oder einem ähnlichen Kontext) und Semikolon ;sind legitim (und ein anderes Zeichenfolgenliteral); Ich bin mir nicht sicher, ob es noch andere gibt.
Jonathan Leffler

Antworten:

121

Nach dem C-Standard (5.1.1.2 Übersetzungsphasen)

1 Die Priorität unter den Syntaxregeln für die Übersetzung wird in den folgenden Phasen festgelegt.6)

  1. Benachbarte String-Literal-Token werden verkettet.

Und erst danach

  1. Leerzeichen, die Token trennen, sind nicht mehr von Bedeutung. Jedes Vorverarbeitungstoken wird in ein Token konvertiert. Die resultierenden Token werden syntaktisch und semantisch analysiert und als Übersetzungseinheit übersetzt .

In dieser Konstruktion

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

Es gibt keine benachbarten String-Literal-Token. Diese Konstruktion ist also ungültig.

Vlad aus Moskau
quelle
43
Dies wiederholt nur die Behauptung, dass es in C nicht erlaubt ist. Es erklärt nicht warum , was die Frage war. Ich weiß nicht, warum es in 5 Stunden 26 Upvotes gesammelt hat ... und das Akzeptieren, nicht weniger! Herzliche Glückwünsche.
Leichtigkeitsrennen im Orbit
4
Muss hier mit @LightnessRacesinOrbit übereinstimmen. Warum sollte man sich nicht (test ? "Bye" : "Goodbye")einem der String-Literale entziehen, die im Wesentlichen machen "Hi" "Bye" oder "Hi Goodbye"? (Meine Frage wird in den anderen Antworten beantwortet)
Wahnsinn
48
@LightnessRacesinOrbit, denn wenn Leute normalerweise fragen, warum etwas in C nicht kompiliert wird, fragen sie nach einer Klärung, gegen welche Regel es verstößt, und nicht, warum Standardautoren der Antike dies so gewählt haben.
user1717828
4
@LightnessRacesinOrbit Die Frage, die Sie beschreiben, wäre wahrscheinlich nicht zum Thema. Ich kann keinen technischen Grund erkennen, warum es nicht möglich wäre, dies umzusetzen. Ohne eine endgültige Antwort der Autoren der Spezifikation wären alle Antworten meinungsbasiert. Und es würde im Allgemeinen nicht in die Kategorie "praktische" oder "beantwortbare" Fragen fallen (wie das Hilfezentrum angibt, dass wir dies benötigen).
jpmc26
12
@LightnessRacesinOrbit Es erklärt, warum : "weil C-Standard dies sagte". Die Frage, warum diese Regel als definiert definiert ist, ist nicht thematisch.
user11153
135

Gemäß dem C11-Standard, Kapitel §5.1.1.2, Verkettung benachbarter String-Literale:

Benachbarte String-Literal-Token werden verkettet.

geschieht in der Übersetzungsphase . Andererseits:

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

bezieht den bedingten Operator mit ein, der zur Laufzeit ausgewertet wird . Während der Übersetzungsphase sind während der Übersetzungsphase keine benachbarten Zeichenfolgenliterale vorhanden, daher ist die Verkettung nicht möglich. Die Syntax ist ungültig und wird daher von Ihrem Compiler gemeldet.


Um den Warum- Teil etwas näher zu erläutern , werden während der Vorverarbeitungsphase die benachbarten Zeichenfolgenliterale verkettet und als einzelnes Zeichenfolgenliteral (Token) dargestellt. Der Speicher wird entsprechend zugewiesen und das verkettete Zeichenfolgenliteral wird als eine einzelne Entität (ein Zeichenfolgenliteral) betrachtet.

Auf der anderen Seite sollte das Ziel im Falle einer Laufzeitverkettung über genügend Speicher verfügen, um das verkettete Zeichenfolgenliteral aufzunehmen. Andernfalls kann auf die erwartete verkettete Ausgabe nicht zugegriffen werden. Bei Zeichenfolgenliteralen wird ihnen bereits zur Kompilierungszeit Speicher zugewiesen, und sie können nicht erweitert werden , um weitere eingehende Eingaben in den ursprünglichen Inhalt aufzunehmen oder an diesen anzuhängen . Mit anderen Worten, es gibt keine Möglichkeit, auf das verkettete Ergebnis als einzelnes Zeichenfolgenliteral zuzugreifen (dargestellt) . Dieses Konstrukt ist also von Natur aus falsch.

Nur zur Info, für Laufzeit Zeichenfolge ( nicht Literale ) Verkettung, haben wir die Library - Funktion , strcat()die zwei verkettet Strings . Beachten Sie, dass in der Beschreibung Folgendes erwähnt wird:

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

Die strcat()Funktion hängt eine Kopie der Zeichenfolge, auf die von zeigt s2(einschließlich des abschließenden Nullzeichens), an das Ende der Zeichenfolge an, auf die von gezeigt wirds1 . Das Anfangszeichen von s2überschreibt das Nullzeichen am Ende von s1. [...]

Wir können also sehen, dass dies s1eine Zeichenfolge ist , kein Zeichenfolgenliteral . Da der Inhalt von s2jedoch in keiner Weise geändert wird, kann es sich durchaus um ein String-Literal handeln .

Sourav Ghosh
quelle
Möglicherweise möchten Sie eine zusätzliche Erklärung hinzufügen zu strcat: Das Zielarray muss lang genug sein, um die Zeichen von s2plus einem Nullterminator nach den dort bereits vorhandenen Zeichen zu empfangen .
Chqrlie
39

Die Verkettung von Zeichenfolgenliteralen wird vom Präprozessor zur Kompilierungszeit durchgeführt. Es gibt keine Möglichkeit für diese Verkettung, den Wert von zu kennen test, der erst bekannt ist, wenn das Programm tatsächlich ausgeführt wird. Daher können diese Zeichenfolgenliterale nicht verkettet werden.

Da der allgemeine Fall ist, dass Sie für Werte, die zur Kompilierungszeit bekannt sind, keine solche Konstruktion haben würden, wurde der C-Standard entwickelt, um die automatische Verkettungsfunktion auf den grundlegendsten Fall zu beschränken: wenn die Literale buchstäblich direkt nebeneinander liegen .

Aber selbst wenn diese Einschränkung nicht auf diese Weise formuliert würde oder wenn die Einschränkung anders aufgebaut wäre, wäre Ihr Beispiel immer noch nicht zu realisieren, ohne die Verkettung zu einem Laufzeitprozess zu machen. Und dafür haben wir die Bibliotheksfunktionen wie strcat.

Leichtigkeitsrennen im Orbit
quelle
3
Ich habe nur Annahmen gelesen. Während das, was Sie sagen, ziemlich gültig ist, können Sie keine Quellen dafür angeben, da es keine gibt. Die einzige Quelle in Bezug auf C ist das Standarddokument, das (obwohl es in vielen Fällen offensichtlich ist) nicht angibt, warum manche Dinge so sind, wie sie sind, sondern nur angibt, dass sie so spezifisch sein müssen. Es ist unangemessen, so wählerisch in Bezug auf Vlad aus Moskaus Antwort zu sein. Da OP auf "Warum ist es so?" -Wo die einzig richtige Antwort lautet "Weil es C ist und so C definiert ist", ist dies die einzig wörtlich richtige Antwort.
Dhein
1
Dies ist (zugegeben) keine Erklärung. Aber auch hier wird gesagt, dass Vlads Antwort viel mehr als eine Erklärung für das Kernproblem dient als Ihre. Nochmals gesagt: Obwohl die Informationen, die Sie mir geben, bestätigt und korrekt sind, bin ich mit Ihren Beschwerden nicht einverstanden. und obwohl ich dich auch nicht als offtopisch betrachten würde, ist es von meinem POV eher offtopisch als Vlads tatsächlich ist.
Dhein
11
@ Zaibis: Die Quelle bin ich. Vlads Antwort ist überhaupt keine Erklärung; es ist lediglich eine Bestätigung der Prämisse der Frage. Sicherlich ist keiner von ihnen "off topic" (vielleicht möchten Sie nachschlagen, was dieser Begriff bedeutet). Aber Sie haben ein Recht auf Ihre Meinung.
Leichtigkeitsrennen im Orbit
Selbst nachdem ich die obigen Kommentare gelesen habe, frage ich mich immer noch, wer diese Antwort abgelehnt hat. Ich glaube, dies ist eine perfekte Antwort, es sei denn, OP bittet um weitere Klarstellungen zu dieser Antwort.
Mohit Jain
2
Ich kann nicht unterscheiden, warum diese Antwort für Sie akzeptabel ist und @ VladfromMoscow nicht, wenn beide dasselbe sagen und wenn seine durch ein Zitat gestützt wird und Ihre nicht.
Marquis von Lorne
30

Weil C keinen stringTyp hat. String-Literale werden zu charArrays kompiliert , auf die durch einen char*Zeiger verwiesen wird .

Mit C können benachbarte Literale wie in Ihrem ersten Beispiel zur Kompilierungszeit kombiniert werden . Der C-Compiler selbst hat einige Kenntnisse über Zeichenfolgen. Diese Informationen sind jedoch zur Laufzeit nicht vorhanden, sodass keine Verkettung auftreten kann.

Während des Kompilierungsprozesses wird Ihr erstes Beispiel "übersetzt" in:

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

Beachten Sie, wie die beiden Zeichenfolgen vom Compiler zu einem einzigen statischen Array kombiniert werden, bevor das Programm jemals ausgeführt wird.

Ihr zweites Beispiel wird jedoch in etwa so "übersetzt":

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

Es sollte klar sein, warum dies nicht kompiliert wird. Der ternäre Operator ?wird zur Laufzeit und nicht zur Kompilierungszeit ausgewertet, wenn die "Zeichenfolgen" nicht mehr als solche existieren, sondern nur noch als einfache charArrays, auf die durch char*Zeiger verwiesen wird . Im Gegensatz zu benachbarten Zeichenfolgenliteralen sind benachbarte Zeichenzeiger einfach ein Syntaxfehler.

Ohne Vorzeichen
quelle
2
Ausgezeichnete Antwort, möglicherweise die beste hier. "Es sollte klar sein, warum dies nicht kompiliert wird." Sie können dies mit "erweitern, da der ternäre Operator eine zur Laufzeit ausgewertete Bedingung ist, die nicht kompiliert wird " erweitern.
Katze
Sollte nicht static const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};sein static const char *char_ptr_1 = "HiBye";und in ähnlicher Weise für den Rest der Zeiger?
Spikatrix
@CoolGuy Wenn Sie schreiben, static const char *char_ptr_1 = "HiBye";übersetzt der Compiler die Zeile in static const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};, also nein, sie sollte nicht "wie eine Zeichenfolge" geschrieben werden. Wie die Antwort sagt, werden Zeichenfolgen zu einem Array von Zeichen kompiliert, und wenn Sie ein Array von Zeichen in seiner "rohesten" Form static const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};
zuweisen
3
@ Ankush Ja. Aber obwohl static const char str[] = {'t', 'e', 's', 't', '\0'};es dasselbe ist wie static const char str[] = "test";, static const char* ptr = "test";ist es nicht dasselbe wie static const char* ptr = {'t', 'e', 's', 't', '\0'};. Ersteres ist gültig und wird kompiliert, letzteres ist jedoch ungültig und macht das, was Sie erwarten.
Spikatrix
Ich habe den letzten Absatz ausgearbeitet und die Codebeispiele korrigiert, danke!
Unsigniert
12

Wenn beide Zweige wirklich Zeichenfolgenkonstanten zur Kompilierungszeit erzeugen sollen, die zur Laufzeit ausgewählt werden sollen, benötigen Sie ein Makro.

#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
quelle
10

Was ist der Grund dafür?

Ihr Code, der den ternären Operator verwendet, wählt bedingt zwischen zwei Zeichenfolgenliteralen. Unabhängig davon, ob eine Bedingung bekannt oder unbekannt ist, kann dies zum Zeitpunkt der Kompilierung nicht ausgewertet werden, sodass keine Kompilierung möglich ist. Auch diese Aussage printf("Hi" (1 ? "Bye" : "Goodbye"));würde nicht kompiliert. Der Grund wird in den obigen Antworten ausführlich erläutert. Eine andere Möglichkeit , eine solche Anweisung unter Verwendung eines ternären Operators zum Kompilieren gültig zu machen , würde auch ein Format-Tag und das Ergebnis der ternären Operator-Anweisung beinhalten, die als zusätzliches Argument für formatiert sind printf. Selbst dann printf()würde der Ausdruck den Eindruck erwecken, diese Zeichenfolgen erst zur Laufzeit und bereits zur Laufzeit "verkettet" zu haben .

#include <stdio.h>

int main() {
    int test = 0;
    printf("Hi %s\n", (test ? "Bye" : "Goodbye")); //specify format and print as result
}
user3078414
quelle
3
SO ist keine Tutorial-Site. Sie sollten dem OP eine Antwort geben und kein Tutorial.
Michi
1
Dies beantwortet nicht die Frage des OP. Es mag ein Versuch sein, das zugrunde liegende Problem des OP zu lösen, aber wir wissen nicht wirklich, was das ist.
Keith Thompson
1
printfnicht benötigen einen Formatbezeichner; Wenn nur die Verkettung zur Kompilierungszeit erfolgen würde (was nicht der Fall ist), wäre die Verwendung von printf durch OP gültig.
David Conrad
Vielen Dank für Ihre Bemerkung, @ David Conrad. Mein schlampiger Wortlaut würde in der Tat so aussehen, als würde für die Angabe printf()ein Format-Tag erforderlich sein, was absolut nicht wahr ist. Korrigiert!
user3078414
Das ist eine bessere Formulierung. +1 Danke.
David Conrad
7

In haben printf("Hi" "Bye");Sie zwei aufeinanderfolgende Arrays von char, die der Compiler zu einem einzigen Array machen kann.

In haben printf("Hi" (test ? "Bye" : "Goodbye"));Sie ein Array, gefolgt von einem Zeiger auf char (ein Array, das in einen Zeiger auf sein erstes Element konvertiert wurde). Der Compiler kann ein Array und einen Zeiger nicht zusammenführen .

pmg
quelle
0

Um die Frage zu beantworten, würde ich zur Definition von printf gehen. Die Funktion printf erwartet const char * als Argument. Jedes String-Literal wie "Hi" ist ein const char *; Ein Ausdruck wie z. B. (test)? "str1" : "str2"ist KEIN const char *, da das Ergebnis eines solchen Ausdrucks nur zur Laufzeit gefunden wird und daher zur Kompilierungszeit unbestimmt ist. Dies führt dazu, dass sich der Compiler ordnungsgemäß beschwert. Auf der anderen Seite - das funktioniert einwandfreiprintf("hi %s", test? "yes":"no")

Stats_Lover
quelle
* aber ein Ausdruck wie (test)? "str1" : "str2"ist NICHT ein const char*... Natürlich ist es! Es ist kein konstanter Ausdruck, aber sein Typ ist const char * . Es wäre vollkommen in Ordnung zu schreiben printf(test ? "hi " "yes" : "hi " "no"). Das Problem des OP hat nichts damit zu tun printf, es "Hi" (test ? "Bye" : "Goodbye")ist ein Syntaxfehler, unabhängig vom Ausdruckskontext.
Chqrlie
Einverstanden. Ich habe die Ausgabe eines Ausdrucks mit dem Ausdruck selbst verwechselt
Stats_Lover
-4

Dies wird nicht kompiliert, da die Parameterliste für die Funktion printf lautet

(const char *format, ...)

und

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

passt nicht in die Parameterliste.

gcc versucht es zu verstehen, indem es sich das vorstellt

(test ? "Bye" : "Goodbye")

ist eine Parameterliste und beschwert sich, dass "Hi" keine Funktion ist.

Rodbots
quelle
6
Willkommen bei Stack Overflow. Sie haben Recht, dass es nicht mit der printf()Argumentliste übereinstimmt , aber das liegt daran, dass der Ausdruck nirgendwo gültig ist - nicht nur in einer printf()Argumentliste. Mit anderen Worten, Sie haben einen viel zu speziellen Grund für das Problem ausgewählt. Das allgemeine Problem ist, dass "Hi" (es in C nicht gültig ist, geschweige denn in einem Aufruf von printf(). Ich schlage vor, dass Sie diese Antwort löschen, bevor sie abgelehnt wird.
Jonathan Leffler
So funktioniert C nicht. Dies wird nicht als Versuch analysiert, ein String-Literal wie PHP aufzurufen.
Katze