Cas 1: Quand j'écris
char*str={"what","is","this"};
alors str[i]="newstring";
est valide alors que str[i][j]='j';
est invalide.
Cas 2: Quand j'écris
char str[][5]={"what","is","this"};
alors str[i]="newstring";
n'est pas valide alors que str[i][j]='J';
est valide.
Pourquoi est-ce? Je suis un débutant qui est déjà très confus après avoir lu les autres réponses.
Tout d'abord: Une suggestion: Lisez à propos de les tableaux ne sont pas des pointeurs et vice-versa !!
Cela dit, pour éclairer ce scénario particulier,
Dans le premier cas,
char*str={"what","is","this"};
ne fait pas ce que vous pensez qu'il fait. Il s'agit d'une violation de contrainte nécessitant un diagnostic de toute implémentation C conforme, conformément au chapitre§7.7.9/P2:
Aucun initialiseur ne doit tenter de fournir une valeur pour un objet non contenu dans l'entité en cours d'initialisation.
Si vous activez les avertissements, vous verriez (au moins _)
avertissement: excès d'éléments dans l'initialiseur scalaire
char*str={"what","is","this"};
Cependant, le compilateur a(ny) dont la conformité stricte est activée, devrait refuser de compiler le code. Dans le cas où le compilateur a choisi de compiler et de produire un fichier binaire, le comportement n’est pas conforme à la portée de la définition du langage C, cela dépend de l’implémentation du compilateur (et peut donc varier considérablement).
Dans ce cas, le compilateur a décidé que cette instruction ne fonctionnerait que de la même manière que char*str= "what";
Donc, ici str
est un pointeur sur une char
, qui pointe sur un chaîne littérale. Vous pouvez réaffecter le pointeur,
str="newstring"; //this is valid
mais, une déclaration comme
str[i]="newstring";
serait invalide, comme ici, un type de pointeur est tenté d'être converti et stocké dans un type char
, où les types ne sont pas compatibles. Le compilateur devrait lancer un avertissement concernant la conversion non valide dans ce cas.
Par la suite, une déclaration comme
str[i][j]='J'; // compiler error
est syntaxiquement invalide, car vous utilisez l'opérateur Array subscripting []
sur quelque chose qui n'est pas un "pointeur sur le type d'objet", comme
str[i][j] = ...
^^^------------------- cannot use this
^^^^^^ --------------------- str[i] is of type 'char',
not a pointer to be used as the operand for [] operator.
D'autre part, dans le second cas,
str
est un tableau de tableaux. Vous pouvez modifier des éléments individuels de tableau,
str[i][j]='J'; // change individual element, good to go.
mais vous ne pouvez pas assigner à un tableau.
str[i]="newstring"; // nopes, array type is not an lvalue!!
Enfin,considérant que vous vouliez écrire (comme indiqué dans les commentaires)
char* str[ ] ={"what","is","this"};
dans votre premier cas, la même logique pour les tableaux est valable. Cela fait str
un tableau de pointeurs. Donc, les membres du tableau, sont assignables, donc,
str[i]="newstring"; // just overwrites the previous pointer
est parfaitement OK. Cependant, les pointeurs, qui sont stockés en tant que membres du tableau, sont des pointeurs vers chaîne littérale, de sorte que, pour la même raison que celle décrite ci-dessus, vous appelez undefined behavior éléments de la mémoire appartenant au littéral chaîne
str[i][j]='j'; //still invalid, as above.
La disposition de la mémoire est différente:
char* str[] = {"what", "is", "this"};
str
+--------+ +-----+
| pointer| ---> |what0|
+--------+ +-----+ +---+
| pointer| -------------> |is0|
+--------+ +---+ +-----+
| pointer| ----------------------> |this0|
+--------+ +-----+
Dans cette structure de mémoire, str
est un tableau de pointeurs sur les chaînes individuelles. Habituellement, ces chaînes individuelles résident dans un stockage statique, et tenter de les modifier est une erreur. Dans le graphique, j'ai utilisé 0
pour désigner les octets nuls terminaux.
char str[][5] = {"what", "is", "this"};
str
+-----+
|what0|
+-----+
|is000|
+-----+
|this0|
+-----+
Dans ce cas, str
est un tableau 2D contigu de caractères situé sur la pile. Les chaînes sont copiées dans cette zone de mémoire lorsque le tableau est initialisé et les chaînes individuelles sont complétées avec zéro octet pour donner au tableau une forme régulière.
Ces deux configurations de mémoire sont fondamentalement incompatibles. Vous ne pouvez pas non plus transmettre à une fonction qui attend un pointeur sur l'autre. Cependant, l'accès aux chaînes individuelles est compatible. Lorsque vous écrivez str[1]
, vous obtenez un char*
au premier caractère d’une région de mémoire contenant les octets is0
, c’est-à-dire une chaîne C.
Dans le premier cas, il est clair que ce pointeur est simplement chargé de la mémoire. Dans le second cas, le pointeur est créé via array-pointer-decay: str[1]
désigne en réalité un tableau comportant exactement cinq octets (is000
), qui se décompose immédiatement en un pointeur vers son premier élément dans presque tous les contextes. Cependant, je pense qu’une explication complète de la matrice-pointeur-déclin va au-delà de la portée de cette réponse. Google array-pointeur-decay si vous êtes curieux.
Avec le premier, vous définissez une variable qui est un pointeur sur une char
, qui est généralement utilisée comme une simple chaîne single. Il initialise le pointeur pour pointer sur le littéral de chaîne "what"
. Le compilateur devrait se plaindre également du fait que la liste contient trop d'initialiseurs.
La deuxième définition fait str
un tableau de trois tableaux de cinq char
. C'est-à-dire qu'il s'agit d'un tableau de trois chaînes de cinq caractères.
Un peu différemment, on peut voir quelque chose comme ceci:
Pour le premier cas:
+ ----- + + -------- + | str | -> | "quoi" | + ----- + + -------- +
Et pour la seconde tu as
+ -------- + -------- + -------- + | "quoi" | "est" | "ceci" | + -------- + -------- + -------- +
Notez également que pour la première version, avec le pointeur sur une seule chaîne, l'expression str[i] = "newstring"
devrait également conduire à des avertissements, lorsque vous essayez d'attribuer un pointeur à l'élémentsingle char
str[i]
.
Cette affectation est également invalide dans la deuxième version, mais pour une autre raison: str[i]
est un array (de cinq éléments char
) et vous ne pouvez pas affecter à un tableau, mais uniquement y copier. Donc, vous pourriez essayer de faire strcpy(str[i], "newstring")
et le compilateur ne se plaindra pas. Cependant, c'est faux, parce que vous essayez de copier 10 caractères (rappelez-vous le terminateur) dans un tableau de 5 caractères, et cela écrira en dehors des limites conduisant au comportement non défini.
Dans la première déclaration
char *str={"what","is","this"};
déclare str
un pointeur sur un char
et est un scalaire. La norme dit que
6.7.9 Initialisation (p11):
L'initialiseur d'un scalaire doit être un expression unique, éventuellement entre accolades. [...]
Cela dit, un type scalaire peut avoir un initialiseur fermé, mais avec une seule expression, mais en cas de
char *str = {"what","is","this"}; // three expressions in brace enclosed initializer
c’est aux compilateurs que c’est la façon dont ils vont gérer cela. Notez que ce qui arrive au reste des initialiseurs est un bug . Un destinataire de la confirmation devrait donner un message de diagnostic.
[Warning] excess elements in scalar initializer
5.1.1.3 Diagnostic (P1):
Une implémentation conforme doit produire au moins un message de diagnostic (identifié de manière définie par l'implémentation) si une unité de traduction en cours de prétraitement ou une unité de traduction contient une violation d'une règle ou contrainte de syntaxe, même si le comportement est aussi explicitement spécifié comme indéfini ou implémentation défini
Vous déclarez que "str[i]="newstring";
est valide alors que str[i][j]='j';
est invalide."
str[i]
est de type char
et ne peut contenir qu'un type de données char
. L'affectation de "newstring"
(qui est de char *
) n'est pas valide. L'instruction str[i][j]='j';
n'est pas valide, car l'opérateur en indice ne peut être appliqué qu'à un type de données tableau ou pointeur.
Vous pouvez faire fonctionner str[i]="newstring";
en déclarant str
comme un tableau de char *
char *str[] = {"what","is","this"};
Dans ce cas, str[i]
est de type char *
et un littéral de chaîne peut lui être attribué, mais la modification du littéral de chaîne pointé par str[i]
invoquera un comportement non défini. Cela dit, vous ne pouvez pas faire str[0][0] = 'W'
.
L'extrait
char str[][5]={"what","is","this"};
declare str
comme un tableau de tableaux de char
s. str[i]
est en fait un tableau et, comme les tableaux sont des valeurs lvalues non modifiables, vous ne pouvez pas les utiliser comme opérande gauche de l'opérateur d'affectation. Cela rend str[i]="newstring";
invalide. Alors que str[i][j]='J';
fonctionne, les éléments d’un tableau peuvent être modifiés.
Juste parce que vous avez dit que d’autres réponses me déroutaient, voyons d’abord ce qui se passe avec un exemple plus simple.
char *ptr = "somestring";
Ici, "somestring"
est un littéral string qui est stocké dans section de données en lecture seule de la mémoire. ptr
est un pointeur (alloué exactement comme les autres variables de la même section de code) qui pointe vers le premier octet de la mémoire allouée.
Cnosider ces deux déclarations
char *ptr2 = ptr; //statement 1 OK
ptr[1] = 'a'; //statement 2 error
L'instruction 1 effectue une opération parfaitement valide (attribution d'un pointeur à un autre), mais l'instruction 2 n'est pas une opération valide (tentative d'écriture dans un emplacement en lecture seule).
Par contre si on écrit:
char ptr[] = "somestring";
Ici, ptr n'est pas réellement un pointeur, mais le nom d'un tableau (contrairement au pointeur, il ne prend pas d'espace supplémentaire dans la mémoire). Il alloue le même nombre d'octets que requis par "somestring"
(pas en lecture seule) et c'est tout.
Par conséquent, considérons les deux mêmes déclarations et une déclaration supplémentaire
char *ptr2 = ptr; //statement 1 OK
ptr[1] = 'a'; //statement 2 OK
ptr = "someotherstring" //statement 3 error
L'instruction 1 effectue une opération parfaitement valide (attribution d'un nom de tableau à un pointeur, le nom de tableau retourne l'adresse du 1er octet), l'instruction 2 est également valide car la mémoire n'est pas en lecture seule.
L'instruction 3 n'est pas une opération valide, car ici ptr n'est pas un pointeur. Elle ne peut pas pointer vers un autre emplacement de mémoire.
Maintenant, dans ce code,
char **str={"what","is","this"};
*str
est un pointeur (str[i]
est identique à *(str+i)
)
mais dans ce code
char str[][] = {"what", "is", "this"};
str[i]
n'est pas un pointeur. C'est le nom d'un tableau.
La même chose que ci-dessus suit.
Pour éliminer cette confusion, vous devez avoir une bonne compréhension des pointeurs, des tableaux et des initialiseurs . Une idée fausse commune aux débutants en programmation C est qu'un tableau est équivalent à un pointeur.
Un tableau est une collection d'éléments du même type. considérer la déclaration suivante:
char arr[10];
Ce tableau contient 10 éléments, chacun de type char
.
Une liste d'initialisation peut être utilisée pour initialiser un tableau de manière pratique. Ce qui suit initialise les éléments du tableau avec les valeurs correspondantes de la liste d'initialisation:
char array[10] = {'a','b','c','d','e','f','g','h','i','\0'};
Les tableaux ne sont pas assignables. Par conséquent, l'utilisation de la liste d'initialisation est valide lors de la déclaration de tableau uniquement.
char array[10];
array = {'a','b','c','d','e','f','g','h','i','\0'}; // Invalid...
char array1[10];
char array2[10] = {'a','b','c','d','e','f','g','h','i','\0'};
array1 = array2; // Invalid...; You cannot copy array2 to array1 in this manner.
Après la déclaration d'un tableau, les affectations aux membres du tableau doivent être effectuées via l'opérateur d'indexation du tableau ou son équivalent.
char array[10];
array[0] = 'a';
array[1] = 'b';
.
.
.
array[9] = 'i';
array[10] = '\0';
Les boucles sont un moyen courant et pratique d’attribuer des valeurs aux membres du groupe:
char array[10];
int index = 0;
for(char val = 'a'; val <= 'i'; val++) {
array[index] = val;
index++;
}
array[index] = '\0';
Les tableaux char
peuvent être initialisés via des littéraux de chaîne qui sont constants et terminés par null. Les tableaux char
:
char array[10] = "abcdefghi";
Cependant ce qui suit n'est pas valide:
char array[10];
array = "abcdefghi"; // As mentioned before, arrays are not assignable
Passons maintenant aux pointeurs .... Les pointeurs sont des variables qui peuvent stocker l’adresse d’une autre variable, généralement du même type.
Considérons la déclaration suivante:
char *ptr;
Cela déclare une variable de type char *
, un pointeur char
. C'est-à-dire un pointeur qui peut pointer vers une variable char
.
Contrairement aux tableaux, les pointeurs sont assignables. Ainsi, ce qui suit est valide:
char var;
char *ptr;
ptr = &var; // Perfectly Valid...
Comme un pointeur n'est pas un tableau, une seule valeur peut être assignée à un pointeur.
char var;
char *ptr = &var; // The address of the variable `var` is stored as a value of the pointer `ptr`
Rappelez-vous qu’un pointeur doit se voir attribuer une valeur unique. Par conséquent, les éléments suivants ne sont pas valides, car le nombre d’initialiseurs est supérieur à un:
char *ptr = {'a','b','c','d','\0'};
Il s'agit d'une violation de contrainte, mais votre compilateur peut simplement affecter 'a'
à ptr
et ignorer le reste. Mais même dans ce cas, le compilateur vous avertira parce que les littéraux de caractère tels que 'a'
ont le type int
par défaut et sont incompatibles avec le type de ptr
qui est char *
.
Si ce pointeur a été déréférencé au moment de l'exécution, une erreur d'exécution empêchant l'accès à une mémoire invalide sera générée, ce qui provoquera le blocage du programme.
Dans votre exemple:
char *str = {"what", "is", "this"};
là encore, il s'agit d'une violation de contrainte, mais votre compilateur peut affecter la chaîne what
à str
et ignorer le reste, et afficher simplement un avertissement:
warning: excess elements in scalar initializer
.
Maintenant, voici comment nous éliminons la confusion concernant les pointeurs et les tableaux: Dans certains contextes, un tableau peut se désintégrer en un pointeur sur le premier élément du tableau. Ainsi, ce qui suit est valide:
char arr[10];
char *ptr = arr;
en utilisant le nom du tableau arr
dans une expression d'affectation en tant que rvalue
, le tableau se désintègre en un pointeur sur son premier élément, ce qui rend l'expression précédente équivalente à:
char *ptr = &arr[0];
Rappelez-vous que arr[0]
est de type char
, et &arr[0]
est son adresse de type char *
, qui est compatible avec la variable ptr
.
Rappelez-vous que les littéraux de chaîne sont des constantes nuls terminées char
arrays , l'expression suivante est donc également valide:
char *ptr = "abcdefghi"; // the array "abcdefghi" decays to a pointer to the first element 'a'
Dans votre cas, char str[][5] = {"what","is","this"};
est un tableau de 3 tableaux contenant chacun 5 éléments.
Puisque les tableaux ne sont pas assignables, str[i] = "newstring";
n'est pas valide, car str[i]
est un tableau, mais str[i][j] = 'j';
est valide, car str[i][j]
est un élément de tableau qui estPASun tableau à lui seul, et est assignable.
cas 1 :
char*str={"what","is","this"};
Tout d'abord, la déclaration ci-dessus est n'est pas valide , lisez les avertissements correctement. str
est un pointeur unique, il peut pointer sur single
tableau de caractères à la fois, pas sur multiple
tableau de caractères.
bounty.c: 3: 2: warning: éléments en trop dans l'initialiseur scalaire [activé par défaut]
str
est un char pointer
et il est stocké dans la section section
de RAM, mais il est contents
dans la section code(Can't modify the content
de RAM car str
est initialisé avec string(in GCC/linux)
.
comme vous l'avez dit str [i] = "newstring"; est valide alors que str [i] [j] = 'j'; est invalide.
str= "new string"
ne provoque pas la modification de code/read-only section, ici vous assignez simplement new address
à str
c'est pourquoi il est valide mais
*str='j'
ou str[0][0]='j'
est non valide car vous modifiez ici la section lecture seule , essayez de changer la première lettre de str
.
Cas 2:
char str[][5]={"what","is","this"};
ici str
est 2D
tableau i.e str
et str[0],str[1],str[2]
lui-même sont stockés dans le stack section
de RAM
, ce qui signifie que vous pouvez modifier chaque contenu de str[i]
.
str[i][j]='w';
c'est valide parce que vous essayez d'empiler le contenu de la section, ce qui est possible. mais
str[i]= "new string";
ce n'est pas possible car str[0]
est lui-même un tableau et array est un pointeur const (ne peut pas changer l'adresse) , vous ne pouvez pas attribuer une nouvelle adresse.
Simplement dans premier cas str="new string"
est valid
parce que str
est pointer
, pas un array
et dans second cas str[0]="new string"
est not valid
parce que str
est array
pas un pointer
.
J'espère que ça aide.
Pour commencer
char*str={"what","is","this"};
n'est même pas un code C valide 1), donc discuter n’a pas beaucoup de sens}. Pour une raison quelconque, le compilateur gcc laisse passer ce code sans avertissement. Ne pas ignorer les avertissements du compilateur. Lorsque vous utilisez gcc, assurez-vous de toujours compiler en utilisant -std=c11 -pedantic-errors -Wall -Wextra
.
Ce que gcc semble faire lorsque vous rencontrez ce code non standard, c'est de le traiter comme si vous aviez écrit char*str={"what"};
. Ce qui à son tour est la même chose que char*str="what";
. Ceci n’est nullement garanti par le langage C.
str[i][j]
essaie deux fois de détourner un pointeur, même s'il ne comporte qu'un seul niveau d'indirection, ce qui entraîne une erreur du compilateur. C'est aussi logique que de taper
int array [3] = {1,2,3}; int x = array[0][0];
.
Pour en savoir plus sur la différence entre char* str = ...
et char str[] = ...
, voir FAQ: Quelle est la différence entre char s [] et char * s? .
En ce qui concerne le cas char str[][5]={"what","is","this"};
, il crée un tableau de tableaux (tableau 2D). La dimension la plus interne est définie sur 5 et la dimension la plus externe est définie automatiquement par le compilateur en fonction du nombre d'initialisateurs fournis par le programmeur. Dans ce cas 3, le code est donc équivalent à char[3][5]
.
str[i]
vous donne le numéro de tableau i
dans le tableau de tableaux. Vous ne pouvez pas affecter de tableaux en C, car c'est ainsi que le langage est conçu. De plus, il serait quand même incorrect de le faire pour une chaîne, FAQ: Comment attribuer correctement une nouvelle valeur de chaîne?
1) Il s'agit d'une violation de contrainte de C11 6.7.9/2. Voir également 6.7.9/11.