web-dev-qa-db-fra.com

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

Le code suivant compile sans problèmes:

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?

95
José D.

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

  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 et traduits syntaxiquement et sémantiquement comme une unité de traduction.

Dans cette construction

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

il n'y a pas de jetons littéraux de chaîne adjacents. Donc, cette construction est invalide.

119
Vlad from Moscow

Conformément à 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 dans phase de traduction . D'autre part:

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

implique l'opérateur conditionnel, qui est évalué à à 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, ce qui rend la concaténation impossible. La syntaxe n'est pas valide et donc rapportée par votre compilateur.


Pour élaborer un peu sur la partie pourquoi, au cours de 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 seul chaîne littérale (jeton). Le stockage est alloué en conséquence et le littéral de chaîne concaténé est considéré comme un entité unique (un littéral de chaîne).

Par contre, en cas de concaténation au moment de l’exécution, la destination doit disposer de suffisamment de mémoire pour contenir le concaténé chaîne littérale sinon, il n’y aura aucun moyen que le attendu la sortie concaténée est accessible. Maintenant, dans le cas de littéraux de chaîne, ils ont déjà de la mémoire allouée au moment de la compilation et ne peuvent pas être étendus pour tenir dans toute autre entrée entrante dans ou ajouté à le contenu original. En d'autres termes, il sera impossible d'accéder au résultat concaténé (présenté) sous la forme d'un seul chaîne littérale. Donc, cette construction est intrinsèquement incorrecte.

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

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

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

Donc, nous pouvons voir que le s1 Est une chaîne , pas un littéral . Cependant, comme le contenu de s2 N’est modifié en aucune façon, il peut très bien s'agir d'un chaîne littérale.

134
Sourav Ghosh

La concaténation littérale de chaîne est effectuée par le préprocesseur au moment de la compilation. Cette concaténation n'a aucun moyen de connaître la valeur de test, qui n'est pas connue avant que le programme ne soit exécuté. Par conséquent, ces littéraux de chaîne ne peuvent pas être concaténés.

Comme le cas général est que vous ne devriez pas avoir une construction comme celle-ci pour les valeurs connues au moment de la compilation, le standard C a été conçu pour limiter la fonctionnalité d'auto-concaténation au cas le plus fondamental: lorsque les littéraux sont littéralement droits les uns à côté des autres. .

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

Parce que C n'a pas de type string. Les littéraux de chaîne sont compilés dans les tableaux char, référencés par un char* pointeur.

C permet de combiner littéraux adjacents au moment de la compilation, comme dans votre premier exemple. Le compilateur C a lui-même quelques connaissances sur les chaînes. Mais cette information est non présente à l'exécution, et la concaténation est donc impossible.

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 le compilateur combine les deux chaînes en un seul tableau statique, avant que le programme ne soit exécuté.

Cependant, votre deuxième exemple est "traduit" par 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é au moment de l'exécution, pas à la compilation, lorsque les "chaînes" n'existent plus en tant que telles, mais uniquement en tant que simples char tableaux, référencés par char* pointeurs. Contrairement aux chaînes littérales de chaîne adjacentes, aux adjacentes pointeurs de caractères sont simplement une erreur de syntaxe.

30
Unsigned

Si vous voulez vraiment que les deux branches produisent des constantes de chaîne lors de la compilation à choisir à 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;
}
12
Eric

Quelle est la raison de ceci?

Votre code utilisant l'opérateur ternaire choisit conditionnellement entre deux littéraux de chaîne. Peu importe la condition connue ou inconnue, ceci ne peut pas être évalué au moment de la compilation, il ne peut donc pas être compilé. Même cette déclaration printf("Hi" (1 ? "Bye" : "Goodbye")); ne compilerait pas. La raison est expliquée en profondeur dans les réponses ci-dessus. Une autre possibilité de faire une telle déclaration en utilisant un opérateur ternaire valide pour compiler, impliquerait également un balise de format et le résultat de la déclaration de l'opérateur ternaire au format argument supplémentaire à printf. Même dans ce cas, printf() printout donnerait une impression de "concaténation" de ces chaînes uniquement à et aussi tôt que runtime.

#include <stdio.h>

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

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

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

7
pmg

Pour répondre à la question - je voudrais aller à la définition de printf. La fonction printf attend const char * en argument. Toute chaîne littérale telle que "Hi" est un caractère constant *; Cependant, une expression telle que (test)? "str1" : "str2" n'est PAS un caractère constant *, car le résultat de cette expression n'est trouvé qu'au moment de l'exécution et est donc indéterminé au moment de la compilation, un fait qui cause des plaintes au compilateur. Par contre, cela fonctionne parfaitement bien printf("hi %s", test? "yes":"no")

0
Stats_Lover