Cordes en C: Question simple

J’ai trois variables initialisées ci-dessous:

char c1[] = "Hello"; char c2[] = { 'H', 'e', 'l', 'l', 'o', '\0'}; char* c3 = "Hello"; 

Je suis conscient que c1 et c2 sont identiques, et que ce sont deux chaînes car elles sont terminées par \ 0. Cependant, c3 est différent de c1 et c2. Est-ce parce que c3 ne se termine pas par un \ 0? Cela signifie-t-il que c3 n’est pas une chaîne? Si c3 n’est pas une chaîne, alors pourquoi printf("%s", c3); pas donner une erreur? Merci!

MODIFIER:

Existe-t-il une raison pour laquelle c1 et c2 peuvent être modifiés alors que c3 ne le peut pas?

En termes de C, la différence la plus pertinente entre c3 et les autres est que vous n’êtes pas autorisé à essayer de modifier les caractères sous-jacents avec c3 . Je trouve souvent utile de penser comme ça:

 char *xyz = "xyz"; 

créera un pointeur modifiable sur la stack et le fera pointer sur la séquence de caractères non modifiable {'x','y','z','\0'} . D’autre part,

 char xyz[] = "xyz"; 

créera un tableau modifiable sur la stack suffisamment grand pour contenir la séquence de caractères {'x','y','z','\0'} et ensuite copier cette séquence de caractères. Le contenu du tableau sera alors modifiable. Gardez à l’esprit que la norme ne dit rien à propos des stacks, mais c’est généralement ainsi que c’est fait. C’est juste un aide-mémoire, après tout.

Formellement, c3 est un pointeur sur un littéral de chaîne, tandis que c1 et c2 sont des tableaux de caractères qui se terminent tous deux par un caractère nul. Lorsqu’elles sont passées à des fonctions telles que printf , elles se décomposent en un pointeur sur le premier élément du tableau, ce qui signifie qu’elles seront traitées de la même manière que c3 dans cette fonction (en fait, elles se désintègrent dans un certain nombre de circonstances, voir la troisième citation de c99. ci-dessous pour les exceptions).

Les sections pertinentes de C99 sont 6.4.5 Ssortingng literals ce qui explique pourquoi vous n’êtes pas autorisé à modifier les points c3 :

Il n’est pas précisé si ces tableaux sont distincts à condition que leurs éléments aient les valeurs appropriées. Si le programme tente de modifier un tel tableau, le comportement n’est pas défini.

et pourquoi il a un terminateur nul:

Dans la phase de traduction 7, un octet ou un code de valeur zéro est ajouté à chaque séquence de caractères multi-octets résultant d’un littéral de chaîne ou de littéraux.

Et 6.3.2.1 Lvalues, arrays, and function designators sous 6.3 Conversions indiquent:

Sauf s’il s’agit de l’opérande de l’opérateur sizeof ou de l’opérateur unaire &, ou s’il s’agit d’un littéral utilisé pour initialiser un tableau, une expression de type ” tableau de type ” est convertie en une expression de type ” pointeur sur type ” qui pointe sur l’élément initial de l’object tableau et n’est pas une valeur. Si l’object tableau a une classe de stockage de registre, le comportement n’est pas défini.

Premier point,

 char* c3 = "Hello"; // may be valid C, but bad C++! 

est un style sujet aux erreurs, alors ne l’utilisez pas. Au lieu d’utiliser,

 const char* c3 = "Hello"; 

C’est un code valide. Le pointeur c3 pointe sur l’adresse de l’emplacement où la chaîne "Hello" est stockée. Mais vous ne pouvez pas modifier *c3 (c’est-à-dire le contenu de c3 ) comme dans les cas précédents (si vous le faites, le comportement n’est pas défini).

c3 est un pointeur sur une chaîne, ce que printf("%s", ...) attend comme argument.

La raison pour laquelle printf("%s", c1) ou printf("%s", c2) fonctionnerait aussi est que, dans les tableaux C, les “désintégrations” se font très facilement dans les expressions. En fait, le seul cas où un nom de tableau ne se décompose pas en un pointeur dans une expression est lorsqu’il est utilisé comme opérande de l’opérateur sizeof ou opérande de l’opérateur & (adresse-de).

Cela conduit à une confusion commune sur le fait que les pointeurs et les tableaux sont équivalents en C, ce qui n’est pas correct. C’est juste que dans C, les tableaux peuvent être utilisés presque partout. Une exception est qu’ils ne peuvent pas être affectés, sauf lorsqu’ils sont en indice (ce qui s’avère être une expression qui les traite comme un pointeur).

Notez qu’il y a une autre différence dans la dernière chaîne – puisqu’il s’agit d’un littéral d’aiguillon, il ne peut pas être modifié (ce qui se passera si vous essayez n’est pas défini). `

c1 et c2 allouent 6 octets de mémoire et stockent la chaîne terminée par un caractère nul.

c3 , cependant, alloue la chaîne (également à zéro) dans la mémoire de programme et crée un pointeur sur celle-ci, c.-à-d. que la chaîne est stockée avec les autres instructions plutôt que sur la stack (ou heap? quelqu’un me corrige) afin de la modifier. serait dangereux.

En C, la constante "ssortingng" peut avoir deux significations, en fonction du contexte dans lequel elle est utilisée. Il peut soit dénoter une chaîne dans la section ro de l’exécutable (bien que je ne pense pas que les sorts standard le sortent), rendant const char *foo = "bar" une instruction initialisant foo pour pointer vers un emplacement dans la mémoire de l’exécutable chargé. . Si le blob binary ( "bar" ) est bien dans la section ro et que vous faites quelque chose comme foo[0] = 'x' , vous obtiendrez un SIGSEGV .

Cependant, lorsque vous écrivez char x[] = "Hello" (ou char x[6] = "Hello" ), vous utilisez "Hello" comme initialiseur de tableau (comme int x[2] = { 1, 2 } ), et le x est juste un tableau régulier (en écriture) alloué sur la stack. Dans ce cas, "Hello" n’est qu’un raccourci pour {'H', 'e', 'l', 'l', 'o', '\0' } .

"bar" et "Hello" sont tous deux terminés par null.

c3 n’est pas terminé par un NUL ou un NULL. C’est un pointeur sur une chaîne terminée par un NUL.

C’est un pointeur sur une chaîne avec une terminaison différente.

C’est une chaîne Elle pointe sur une chaîne, mais c’est risqué.

C3 est un pointeur sur la première cellule de la chaîne. C1, C2 ne sont que des tableaux réguliers qui ne sont pointés par quelqu’un.