C déclaration et initialisation du pointeur à double caractère

J’ai toujours pensé que

char *c = "line"; 

était le même que

 char c[] = "line"; 

et donc j’ai fait

 char **choices = { "New Game", "Continue Game", "Exit" }; 

Ce qui me donne un type de pointeur incompatible, où

 char *choices[] = { "New Game", "Continue Game", "Exit" }; 

ne pas Toute aide pour comprendre cela?

Eh bien, ils ne sont pas les mêmes. C’est simplement plus facile pour la plupart des gens de penser qu’ils sont identiques, alors tout le monde commence à penser de cette façon jusqu’à ce qu’ils rencontrent un problème comme celui-ci 🙂

J’allais écrire quelque chose de long et compliqué, mais ensuite je me suis dit que quelqu’un d’autre devait déjà l’avoir fait. Et ils ont. C’est une très bonne explication:

http://www.lysator.liu.se/c/c-faq/c-2.html

La meilleure façon de penser à cela est que lorsque vous faites quelque chose comme:

   char * foo = "quelque chose";

Vous faites vraiment quelque chose comme:

   char randomblob [] = "quelque chose";
   char * foo = randomblob;

Maintenant … ce n’est pas vraiment une image précise (même si je ne suis pas un expert en compilateur). Cela vous permet au moins de penser aux choses d’une manière légèrement plus correcte.

Donc, revenons à votre problème, si je comprends bien les choses (ce qui n’est jamais garanti), vous ne pouvez pas faire la ligne d’exemple n ° 3 en C. Vous avez raison de dire que quelqu’un pourrait écrire un compilateur qui ferait la bonne chose ici, mais GCC ne le fait pas. Le 4ème exemple, cependant, fait le “bon choix” et vous donne “un tableau de pointeurs pointant chacun vers un tableau de caractères const”.

Une fois, j’ai croisé une page Web qui traduirait un type C complexe en anglais. C’était probablement au début des années 90, mais je parie que si vous en donniez suffisamment sur Google, vous obtiendrez une description plus précise du libellé que celle que je viens de vous expliquer.

 char *c = "line"; 

n’est pas la même chose que

 char c[] = "line"; 

c’est vraiment pareil que

 static char hidden_C0[] = "line"; char *c = hidden_C0; 

sauf que la variable hidden_C0 n’est pas directement accessible. Mais vous le verrez si vous extrayez du langage assembleur généré (il aura généralement un nom qui n’est pas un identifiant C valide, tel que .LC0 ). Et dans votre exemple de tableau de constantes de chaînes, la même chose se passe:

 char *choices[] = { "New Game", "Continue Game", "Exit" }; 

devient

 char hidden_C0[] = "New Game"; char hidden_C1[] = "Continue Game"; char hidden_C2[] = "Exit"; char *choices[] = { hidden_C0, hidden_C1, hidden_C2 }; 

À présent, il s’agit d’un comportement de cas spécial disponible uniquement pour les constantes de chaîne. Vous ne pouvez pas écrire

 int *numbers = { 1, 2, 3 }; 

tu dois écrire

 int numbers[] = { 1, 2, 3 }; 

et c’est pourquoi vous ne pouvez pas écrire

 char **choices = { "a", "b", "c" }; 

non plus.

(Votre confusion est un cas particulier de l’idée fausse commune selon laquelle les tableaux sont “les mêmes que” les pointeurs de C. Ils ne le sont pas. Les tableaux sont des tableaux. Les variables avec des types de tableaux subissent une désintégration du type de pointeur quand ils sont utilisés contexte), mais pas quand ils sont définis.)

Ça va, il suffit d’écrire

char **choices = (char *[]){ "New Game", "Continue Game", "Exit" };

Cependant, les choices ne peuvent être utilisés que pour l’adressage linéaire. Par exemple:

printf ("%s", &(*choices)[0]); sorties: New Game
printf ("%s", &(*choices)[1]); sorties: nouveau ew Game
printf ("%s", &(*choices)[9]); sorties: Continue Game

Donc, ce n’est pas une blague, c’est une initialisation valide. Juste un autre type d’utilisation.


Vous pouvez également trouver un exemple très proche ici , expliquant la notion de littératures composées .

Norme C en ligne (projet n1256 ):

6.7.8 Initialisation

11 L’initialiseur d’un scalaire doit être une expression unique , éventuellement entourée d’accolades. La valeur initiale de l’object est celle de l’expression (après conversion); les mêmes contraintes de type et les mêmes conversions que pour une affectation simple s’appliquent, le type du scalaire étant la version non qualifiée de son type déclaré.

16 Dans le cas contraire, l’initialiseur d’un object doté d’ un type d’agrégat ou d’union doit être une liste d’accolades accompagnée d’accolades pour les éléments ou les membres nommés.

L’accent a été ajouté.

char ** est un type scalaire, pas un agrégat, et n’est donc pas compatible avec l’initialiseur {"New Game", "Continue Game", "Exit"} . En revanche, char *[] est un type d’agrégat (tableau).

De même, vous ne pouvez pas écrire quelque chose comme

 int *foo = {1, 2, 3}; 

car int * n’est pas un type de tableau.

Votre compréhension de

 char *c = "line"; 

et

 char c[] = "line"; 

est légèrement éteint; ils ne sont pas les mêmes. Le premier formulaire copie l’ adresse du littéral de chaîne dans la valeur du pointeur c . La seconde forme copie le contenu de l’expression de tableau "line" dans le tampon désigné par c .