Pourquoi C n'autorise-t-il pas la concaténation de chaînes lors de l'utilisation de l'opérateur conditionnel?

95

Le code suivant se compile sans problème:

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

Cependant, cela ne compile pas:

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

Quelle est la raison de ceci?

José D.
la source
95
La concaténation de chaînes fait partie de la première phase de lexing; cela ne fait pas partie de l'expression synatx de C. En d'autres termes, il n'y a pas de valeur de type "string literal". Les littéraux de chaîne sont plutôt des éléments lexicaux du code source qui forment des valeurs.
Kerrek SB
24
Juste pour clarifier la réponse @KerrekSB - la concaténation des chaînes fait partie du prétraitement du texte de code avant de le compiler. Alors que l'opérateur ternaire est évalué au moment de l'exécution, une fois le code compilé (ou si tout est constant, cela peut être fait au moment de la compilation).
Eugene Sh.
2
Détail: Dans cet article, "Hi"et "Bye"sont des chaînes littérales , pas des chaînes telles qu'utilisées dans la bibliothèque standard C. Avec les chaînes littérales , le compilateur concaténera "H\0i" "B\0ye". Pas la même chose avecsprintf(buf,"%s%s", "H\0i" "B\0ye");
chux - Réintégrer Monica
15
Plus ou moins la même raison que vous ne pouvez pas fairea (some_condition ? + : - ) b
user253751
4
Notez que même printf("Hi" ("Bye"));ne fonctionnera pas - cela ne nécessite pas l'opérateur ternaire; la parenthèse est suffisante (mais printf("Hi" test ? "Bye" : "Goodbye")ne compilerait pas non plus). Il n'existe qu'un nombre limité de jetons pouvant suivre un littéral de chaîne. La virgule ,, le crochet ouvert, le crochet [fermé ](comme dans 1["abc"]- et oui, c'est horrible), le crochet rond )fermé, le crochet bouclé }(dans un initialiseur ou un contexte similaire) et le point ;- virgule sont légitimes (et une autre chaîne littérale); Je ne suis pas sûr qu'il y en ait d'autres.
Jonathan Leffler

Réponses:

121

Selon la norme C (5.1.1.2 Phases de traduction)

1 La priorité parmi les règles de syntaxe de traduction est spécifiée par les phases suivantes.6)

  1. Les jetons littéraux de chaîne adjacents sont concaténés.

Et seulement après ça

  1. Les espaces blancs séparant les jetons ne sont plus significatifs. Chaque jeton de prétraitement est converti en jeton. Les jetons résultants sont analysés syntaxiquement et sémantiquement et traduits en tant qu'unité de traduction .

Dans cette construction

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

il n'y a pas de jetons littéraux de chaîne adjacents. Cette construction est donc invalide.

Vlad de Moscou
la source
43
Cela ne fait que répéter l'affirmation selon laquelle ce n'est pas autorisé en C. Cela n'explique pas pourquoi , c'était la question. Je ne sais pas pourquoi il a accumulé 26 votes positifs en 5 heures ... et l'accepter, rien de moins! Toutes nos félicitations.
Courses de légèreté en orbite
4
Je dois être d'accord avec @LightnessRacesinOrbit ici. Pourquoi ne pas (test ? "Bye" : "Goodbye")évauler vers l'un des littéraux de chaîne faisant essentiellement "Hi" "Bye"ou "Hi Goodbye"? (ma question trouve une réponse dans les autres réponses)
Insane
48
@LightnessRacesinOrbit, parce que lorsque les gens demandent normalement pourquoi quelque chose ne se compile pas en C, ils demandent des éclaircissements sur la règle à laquelle il enfreint, pas sur la raison pour laquelle Standards Authors of Antiquity l'a choisi comme tel.
user1717828
4
@LightnessRacesinOrbit La question que vous décrivez serait probablement hors sujet. Je ne vois aucune raison technique pour laquelle il ne serait pas possible de mettre en œuvre cela, donc sans une réponse définitive des auteurs de la spécification, toutes les réponses seraient basées sur l'opinion. Et cela ne rentre généralement pas dans la catégorie des questions «pratiques» ou «auxquelles il faut répondre» (comme l' indique le centre d'aide ).
jpmc26
12
@LightnessRacesinOrbit Cela explique pourquoi : "parce que le standard C l'a dit". La question de savoir pourquoi cette règle est définie comme définie serait hors sujet.
user11153
135

Selon la norme C11, chapitre §5.1.1.2, concaténation de littéraux de chaîne adjacents:

Les jetons littéraux de chaîne adjacents sont concaténés.

se passe en phase de traduction . D'autre part:

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

implique l'opérateur conditionnel, qui est évalué au moment de l' exécution . Ainsi, au moment de la compilation, pendant la phase de traduction, il n'y a pas de littéraux de chaîne adjacents présents, par conséquent la concaténation n'est pas possible. La syntaxe est invalide et donc signalée par votre compilateur.


Pour élaborer un peu sur la partie pourquoi , pendant la phase de prétraitement, les littéraux de chaîne adjacents sont concaténés et représentés sous la forme d'un littéral de chaîne unique (jeton). Le stockage est alloué en conséquence et le littéral de chaîne concaténé est considéré comme une seule entité (un littéral de chaîne).

D'autre part, en cas de concaténation au moment de l'exécution, la destination doit avoir suffisamment de mémoire pour contenir la chaîne de caractères concaténée , sinon, il n'y aura aucun moyen d' accéder à la sortie concaténée attendue . Maintenant, en cas de littéraux de chaîne , ils sont déjà attribués mémoire à la compilation et ne peuvent être étendues pour tenir dans une entrée plus entrant dans ou ajoutés au contenu original. En d'autres termes, il n'y aura aucun moyen que le résultat concaténé puisse être accédé (présenté) comme une chaîne littérale unique . Donc, cette construction est intrinsèquement incorrecte.

Juste Pour votre information, pour l' exécution chaîne ( non littéraux ) concaténation, nous avons la fonction de bibliothèque strcat()qui concatène deux chaînes . Remarquez, la description mentionne:

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

La strcat()fonction ajoute une copie de la chaîne pointée par s2(y compris le caractère nul de fin) à la fin de la chaîne pointée pars1 . Le caractère initial de s2remplace le caractère nul à la fin de s1. [...]

Ainsi, nous pouvons voir que le s1est une chaîne , pas une chaîne littérale . Cependant, comme le contenu de s2n'est en aucun cas modifié, il peut très bien s'agir d'une chaîne littérale .

Sourav Ghosh
la source
vous voudrez peut-être ajouter une explication supplémentaire sur strcat: le tableau de destination doit être suffisamment long pour recevoir les caractères de s2plus un terminateur nul après les caractères déjà présents.
chqrlie
39

La concaténation de chaînes littérales est effectuée par le préprocesseur au moment de la compilation. Il n'y a aucun moyen pour cette concaténation de connaître la valeur de test, qui n'est connue que lorsque le programme s'exécute réellement. Par conséquent, ces littéraux de chaîne ne peuvent pas être concaténés.

Parce que le cas général est que vous n'auriez pas une construction comme celle-ci pour les valeurs connues au moment de la compilation, le standard C a été conçu pour restreindre la fonction d'auto-concaténation au cas le plus élémentaire: lorsque les littéraux sont littéralement côte à côte .

Mais même s'il ne formulait pas cette restriction de cette manière, ou si la restriction était construite différemment, votre exemple serait toujours impossible à réaliser sans faire de la concaténation un processus d'exécution. Et, pour cela, nous avons les fonctions de bibliothèque telles que strcat.

Courses de légèreté en orbite
la source
3
Je viens de lire des hypothèses. Bien que ce que vous dites soit à peu près valide, vous ne pouvez pas fournir de sources car il n'y en a pas. La seule source en ce qui concerne C est le document standard qui (bien qu'il soit dans de nombreux cas évident) n'indique pas pourquoi certaines choses sont telles qu'elles sont, mais indique simplement qu'elles doivent être de cette manière spécifique. Donc, être aussi pointilleux à propos de la réponse de Vlad de Moscou n'est pas approprié. Depuis OP peut être décomposé en "Pourquoi est-ce ainsi?" -Lorsque la seule réponse correcte est "Parce que c'est C, et c'est ainsi que C est défini", c'est la seule réponse correcte littéralement.
dhein
1
Ceci est (admis) sans explication. Mais là encore, on dit que la réponse de Vlad sert beaucoup plus d'explication au problème central que la vôtre. A nouveau dit: Bien que les informations que vous donnez que je peux confirmer soient liées et correctes, je ne suis pas d'accord avec vos plaintes. et bien que je ne vous considère pas non plus comme hors-sujet, c'est de mon point de vue plus hors-sujet que Vlads l'est en réalité.
dhein
11
@Zaibis: La source, c'est moi. La réponse de Vlad n'est pas du tout une explication; c'est simplement une confirmation de la prémisse de la question. Aucun d'eux n'est certainement «hors sujet» (vous voudrez peut-être chercher ce que ce terme signifie). Mais vous avez droit à votre opinion.
Courses de légèreté en orbite
Même après avoir lu les commentaires ci-dessus, je me demande toujours qui a voté contre cette réponse ᶘ ᵒᴥᵒᶅ Je crois que c'est une réponse parfaite à moins qu'OP ne demande des éclaircissements supplémentaires sur cette réponse.
Mohit Jain
2
Je suis incapable de distinguer pourquoi cette réponse est acceptable pour vous et celle de @ VladfromMoscow, quand ils disent tous les deux la même chose, et quand la sienne est soutenue par une citation et la vôtre ne l'est pas.
Marquis de Lorne
30

Parce que C n'a pas de stringtype. Les littéraux de chaîne sont compilés en chartableaux, référencés par un char*pointeur.

C permet aux littéraux adjacents d'être combinés au moment de la compilation , comme dans votre premier exemple. Le compilateur C lui-même a des connaissances sur les chaînes. Mais ces informations ne sont pas présentes au moment de l'exécution et la concaténation ne peut donc pas se produire.

Pendant le processus de compilation, votre premier exemple est "traduit" en:

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

Notez comment les deux chaînes sont combinées en un seul tableau statique par le compilateur, avant que le programme ne s'exécute.

Cependant, votre deuxième exemple est "traduit" en quelque chose comme ceci:

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

Il devrait être clair pourquoi cela ne compile pas. L'opérateur ternaire ?est évalué à l'exécution, non à la compilation, lorsque les "chaînes" n'existent plus en tant que telles, mais uniquement en tant que simples chartableaux, référencés par des char*pointeurs. Contrairement aux chaînes littérales adjacentes, les pointeurs char adjacents sont simplement une erreur de syntaxe.

Non signé
la source
2
Excellente réponse, peut-être la meilleure ici. "Il devrait être clair pourquoi cela ne compile pas." Vous pourriez envisager d'étendre cela avec "car l'opérateur ternaire est une condition évaluée au moment de l'exécution et non au moment de la compilation ".
cat
Ne devrait pas static const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};être static const char *char_ptr_1 = "HiBye";et de même pour le reste des pointeurs?
Spikatrix
@CoolGuy Lorsque vous écrivez, static const char *char_ptr_1 = "HiBye";le compilateur traduit la ligne en static const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};, donc non, elle ne doit pas être écrite "comme une chaîne". Comme le dit la réponse, les chaînes sont compilées en un tableau de caractères, et si vous affectiez un tableau de caractères dans sa forme la plus "brute", vous utiliseriez une liste de caractères séparés par des virgules, tout commestatic const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};
Ankush
3
@Ankush Oui. Mais bien que ce static const char str[] = {'t', 'e', 's', 't', '\0'};soit le même que static const char str[] = "test";, ce static const char* ptr = "test";n'est pas le même que static const char* ptr = {'t', 'e', 's', 't', '\0'};. Le premier est valide et compilera mais le second est invalide et fait ce que vous attendez.
Spikatrix
J'ai étoffé le dernier paragraphe et corrigé les exemples de code, merci!
Non signé
12

Si vous voulez vraiment que les deux branches produisent des constantes de chaîne au moment de la compilation à choisir au moment de l'exécution, vous aurez besoin d'une 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
la source
10

Quelle est la raison de ceci?

Votre code utilisant l'opérateur ternaire choisit conditionnellement entre deux littéraux de chaîne. Quelle que soit la condition connue ou inconnue, cela ne peut pas être évalué au moment de la compilation, donc il ne peut pas compiler. Même cette déclaration printf("Hi" (1 ? "Bye" : "Goodbye"));ne serait pas compilée. La raison est expliquée en profondeur dans les réponses ci-dessus. Une autre possibilité de rendre une telle déclaration en utilisant un opérateur ternaire valide à compiler , impliquerait également une balise de format et le résultat de l'instruction d'opérateur ternaire formaté comme argument supplémentaire à printf. Même dans ce cas, l' printf()impression donnerait l'impression d'avoir "concaténé" ces chaînes uniquement au moment de l' exécution et dès l' exécution .

#include <stdio.h>

int main() {
    int test = 0;
    printf("Hi %s\n", (test ? "Bye" : "Goodbye")); //specify format and print as result
}
utilisateur3078414
la source
3
SO n'est pas un site de tutoriels. Vous devez donner une réponse à l'OP et non un tutoriel.
Michi
1
Cela ne répond pas à la question du PO. Il peut s'agir d'une tentative de résoudre le problème sous-jacent du PO, mais nous ne savons pas vraiment ce que c'est.
Keith Thompson
1
printfne nécessite pas de spécificateur de format; si seulement la concaténation était faite au moment de la compilation (ce qui n'est pas le cas), l'utilisation de printf par OP serait valide.
David Conrad
Merci pour votre remarque, @David Conrad. Ma formulation bâclée donnerait en effet l'impression que la déclaration printf()nécessiterait une balise de format, ce qui n'est absolument pas vrai. Corrigée!
user3078414
C'est une meilleure formulation. +1 Merci.
David Conrad
7

Dans printf("Hi" "Bye");vous avez deux tableaux consécutifs de char que le compilateur peut transformer en un seul tableau.

Dans printf("Hi" (test ? "Bye" : "Goodbye"));vous avez un tableau suivi d'un pointeur vers char (un tableau converti en un pointeur vers son premier élément). Le compilateur ne peut pas fusionner un tableau et un pointeur.

pmg
la source
0

Pour répondre à la question - j'irais à la définition de printf. La fonction printf attend const char * comme argument. Toute chaîne littérale telle que "Hi" est un const char *; cependant une expression telle que (test)? "str1" : "str2"n'est PAS un const char * parce que le résultat d'une telle expression est trouvé uniquement au moment de l'exécution et est donc indéterminé au moment de la compilation, un fait qui amène dûment le compilateur à se plaindre. D'autre part - cela fonctionne parfaitement bienprintf("hi %s", test? "yes":"no")

Stats_Lover
la source
* cependant une expression telle que (test)? "str1" : "str2"n'est PAS un const char*... Bien sûr que ça l'est! Ce n'est pas une expression constante, mais son type l' est const char * . Ce serait parfaitement bien d'écrire printf(test ? "hi " "yes" : "hi " "no"). Le problème de l'OP n'a rien à voir avec printf, "Hi" (test ? "Bye" : "Goodbye")c'est une erreur de syntaxe quel que soit le contexte de l'expression.
chqrlie
D'accord. J'ai confondu la sortie d'une expression avec l'expression elle
Stats_Lover
-4

Cela ne compile pas car la liste des paramètres de la fonction printf est

(const char *format, ...)

et

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

ne rentre pas dans la liste des paramètres.

gcc essaie de lui donner un sens en imaginant que

(test ? "Bye" : "Goodbye")

est une liste de paramètres, et se plaint que "Hi" n'est pas une fonction.

Rodbots
la source
6
Bienvenue dans Stack Overflow. Vous avez raison de dire qu'elle ne correspond pas à la printf()liste d'arguments, mais c'est parce que l'expression n'est valide nulle part - pas seulement dans une printf()liste d'arguments. En d'autres termes, vous avez choisi une raison beaucoup trop spécialisée pour le problème; le problème général est que ce "Hi" (n'est pas valide en C, encore moins dans un appel à printf(). Je vous suggère de supprimer cette réponse avant qu'elle ne soit votée à la baisse.
Jonathan Leffler
Ce n'est pas ainsi que C fonctionne. Ceci n'est pas interprété comme une tentative d'appeler une chaîne littérale comme PHP.
cat