Quelle est la différence entre les caractères s et char * s?

En C, on peut utiliser un littéral de chaîne dans une déclaration comme celle-ci:

char s[] = "hello"; 

ou comme ça:

 char *s = "hello"; 

Alors, quelle est la difference? Je veux savoir ce qui se passe réellement en termes de durée de stockage, à la fois à la compilation et à l’exécution.

La différence ici est que

 char *s = "Hello world"; 

placera "Hello world" dans les parties en lecture seule de la mémoire , et en faisant un pointeur vers cela, toute opération d’écriture sur cette mémoire sera illégale.

Tout en faisant:

 char s[] = "Hello world"; 

met la chaîne littérale en mémoire morte et copie la chaîne dans la mémoire nouvellement allouée sur la stack. Faisant ainsi

 s[0] = 'J'; 

légal.

Tout d’abord, dans les arguments de fonction, ils sont exactement équivalents:

 void foo(char *x); void foo(char x[]); // exactly the same in all respects 

Dans d’autres contextes, char * alloue un pointeur, tandis que char [] alloue un tableau. Où est la chaîne dans le premier cas, vous demandez? Le compilateur alloue secrètement un tableau anonyme statique pour contenir le littéral de chaîne. Alors:

 char *x = "Foo"; // is approximately equivalent to: static const char __secret_anonymous_array[] = "Foo"; char *x = (char *) __secret_anonymous_array; 

Notez que vous ne devez jamais tenter de modifier le contenu de ce tableau anonyme via ce pointeur. les effets sont indéfinis (signifiant souvent un crash):

 x[1] = 'O'; // BAD. DON'T DO THIS. 

L’utilisation de la syntaxe de tableau l’affecte directement dans la nouvelle mémoire. La modification est donc sûre:

 char x[] = "Foo"; x[1] = 'O'; // No problem. 

Cependant, le tableau ne vit que dans la mesure de son étendue, donc si vous faites cela dans une fonction, ne renvoyez pas ou ne divulguez pas un pointeur sur ce tableau – faites plutôt une copie avec strdup() ou similaire. Si le tableau est alloué de manière globale, bien sûr, pas de problème.

Cette déclaration:

 char s[] = "hello"; 

Crée un object – un tableau de taille 6, appelé s , initialisé avec les valeurs 'h', 'e', 'l', 'l', 'o', '\0' . L’emplacement où ce tableau est alloué en mémoire et la durée de vie de ce tableau dépend de l’emplacement de la déclaration. Si la déclaration est dans une fonction, elle restra active jusqu’à la fin du bloc dans lequel elle est déclarée, et sera presque certainement allouée sur la stack; s’il est en dehors d’une fonction, il sera probablement stocké dans un “segment de données initialisé” chargé depuis le fichier exécutable dans la mémoire accessible en écriture lorsque le programme est exécuté.

En revanche, cette déclaration:

 char *s ="hello"; 

Crée deux objects:

  • un tableau en lecture seule de 6 caractères contenant les valeurs 'h', 'e', 'l', 'l', 'o', '\0' , qui n’a pas de nom et a une durée de stockage statique vit pendant toute la vie du programme); et
  • une variable de type pointer-to-char, appelée s , qui est initialisée avec l’emplacement du premier caractère dans ce tableau en lecture seule non nommé.

Le tableau en lecture seule non nommé est généralement situé dans le segment “text” du programme, ce qui signifie qu’il est chargé du disque dans une mémoire en lecture seule, avec le code lui-même. L’emplacement de la variable de pointeur en mémoire dépend de l’emplacement de la déclaration (comme dans le premier exemple).

Vu les déclarations

 char *s0 = "hello world"; char s1[] = "hello world"; 

supposons la carte mémoire hypothétique suivante:

                     0x01 0x02 0x03 0x04
         0x00008000: 'h' 'e' 'l' 'l'
         0x00008004: 'o' '' 'w' 'o'
         0x00008008: 'r' 'l' 'd' 0x00
         ...
 s0: 0x00010000: 0x00 0x00 0x80 0x00
 s1: 0x00010004: 'h' e '' l '' l '
         0x00010008: 'o' '' 'w' 'o'
         0x0001000C: 'r' 'l' 'd' 0x00

La chaîne littérale "hello world" est un tableau à 12 éléments de type char ( const char en C ++) avec une durée de stockage statique, ce qui signifie que la mémoire est allouée au démarrage du programme et rest allouée jusqu’à la fin du programme. La tentative de modification du contenu d’une chaîne littérale appelle un comportement indéfini.

La ligne

 char *s0 = "hello world"; 

définit s0 comme un pointeur sur char avec une durée de stockage automatique (ce qui signifie que la variable s0 n’existe que pour l’étendue dans laquelle elle est déclarée) et copie l’ adresse du littéral de chaîne ( 0x00008000 dans cet exemple). Notez que puisque s0 pointe sur un littéral de chaîne, il ne doit pas être utilisé comme argument pour une fonction qui essaierait de le modifier (par exemple, strtok() , strcat() , strcpy() , etc.).

La ligne

 char s1[] = "hello world"; 

définit s1 comme un tableau de caractères à 12 éléments (la longueur est extraite du littéral de chaîne) avec une durée de stockage automatique et copie le contenu du littéral dans le tableau. Comme vous pouvez le voir sur la carte mémoire, nous avons deux copies de la chaîne "hello world" ; la différence est que vous pouvez modifier la chaîne contenue dans s1 .

s0 et s1 sont interchangeables dans la plupart des contextes; voici les exceptions:

 sizeof s0 == sizeof (char*) sizeof s1 == 12 type of &s0 == char ** type of &s1 == char (*)[12] // pointer to a 12-element array of char 

Vous pouvez réaffecter la variable s0 pour s0 un littéral de chaîne différent ou une autre variable. Vous ne pouvez pas réaffecter la variable s1 pour pointer vers un autre tableau.

C99 N1256 projet

Il existe deux utilisations complètement différentes des littéraux de tableau:

  1. Initialiser char[] :

     char c[] = "abc"; 

    Ceci est “plus magique” et décrit en 6.7.8 / 14 “Initialisation” :

    Un tableau de type caractère peut être initialisé par une chaîne de caractères littérale, éventuellement entre accolades. Les caractères successifs de la chaîne de caractères littérale (y compris le caractère nul final s’il y a de la place ou si le tableau est de taille inconnue) initialisent les éléments du tableau.

    Donc, ce n’est qu’un raccourci pour:

     char c[] = {'a', 'b', 'c', '\0'}; 

    Comme tout autre tableau régulier, c peut être modifié.

  2. Partout ailleurs: il génère un:

    • anonyme
    • array of char Quel est le type de littéral de chaîne en C et C ++?
    • avec stockage statique
    • qui donne UB si modifié

    Donc, quand vous écrivez:

     char *c = "abc"; 

    Ceci est similaire à:

     /* __unnamed is magic because modifying it gives UB. */ static char __unnamed[] = "abc"; char *c = __unnamed; 

    Notez la conversion implicite de char[] en char * , ce qui est toujours légal.

    Ensuite, si vous modifiez c[0] , vous modifiez également __unnamed , qui est UB.

    Ceci est documenté au 6.4.5 “Chaînes littérales” :

    5 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. La séquence de caractères multi-octets est ensuite utilisée pour initialiser un tableau de durée de stockage statique et de longueur juste suffisante pour contenir la séquence. Pour les littéraux de chaîne de caractères, les éléments du tableau ont le type char et sont initialisés avec les octets individuels de la séquence de caractères multi-octets […]

    6 Il n’est pas spécifié 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 est indéfini.

6.7.8 / 32 “Initialisation” donne un exemple direct:

EXEMPLE 8: La déclaration

 char s[] = "abc", t[3] = "abc"; 

définit les objects de tableau “plain” s et t dont les éléments sont initialisés avec des littéraux de chaîne de caractères.

Cette déclaration est identique à

 char s[] = { 'a', 'b', 'c', '\0' }, t[] = { 'a', 'b', 'c' }; 

Le contenu des tableaux est modifiable. Par contre, la déclaration

 char *p = "abc"; 

définit p avec le type “pointer to char” et l’initialise pour pointer vers un object de type “array of char” de longueur 4 dont les éléments sont initialisés avec une chaîne de caractères littérale. Si vous tentez d’utiliser p pour modifier le contenu du tableau, le comportement est indéfini.

Implémentation ELF GCC 4.8 x86-64

Programme:

 #include  int main() { char *s = "abc"; printf("%s\n", s); return 0; } 

Comstackr et décomstackr:

 gcc -ggdb -std=c99 -c main.c objdump -Sr main.o 

Le résultat contient:

  char *s = "abc"; 8: 48 c7 45 f8 00 00 00 movq $0x0,-0x8(%rbp) f: 00 c: R_X86_64_32S .rodata 

Conclusion: GCC stocke char* it dans la section .rodata , pas dans .text .

Si nous faisons la même chose pour char[] :

  char s[] = "abc"; 

on obtient:

 17: c7 45 f0 61 62 63 00 movl $0x636261,-0x10(%rbp) 

il est donc stocké dans la stack (par rapport à %rbp ).

Notez toutefois que le script de l’éditeur de liens par défaut place .rodata et .text dans le même segment, qui dispose de droits d’exécution mais pas d’écriture. Cela peut être observé avec:

 readelf -l a.out 

qui contient:

  Section to Segment mapping: Segment Sections... 02 .text .rodata 
 char s[] = "hello"; 

déclare s comme un tableau de caractères suffisamment long pour contenir l’initialiseur (5 + 1 caractères) et initialise le tableau en copiant les membres du littéral de chaîne donné dans le tableau.

 char *s = "hello"; 

déclare s comme un pointeur sur un ou plusieurs caractères (dans ce cas, plus) et le pointe directement vers un emplacement fixe (en lecture seule) contenant le littéral "hello" .

 char s[] = "Hello world"; 

Ici, s est un tableau de caractères, qui peut être écrasé si on le souhaite.

 char *s = "hello"; 

Un littéral de chaîne est utilisé pour créer ces blocs de caractères quelque part dans la mémoire vers laquelle pointe ce pointeur. Nous pouvons ici réaffecter l’object sur lequel il pointe en modifiant cela, mais tant qu’il pointe sur un littéral de chaîne, le bloc de caractères sur lequel il pointe ne peut pas être modifié.

De plus, considérez que, pour des raisons de lecture seule, l’utilisation des deux est identique, vous pouvez accéder à un caractère en indexant soit au format [] ou *( + ) :

 printf("%c", x[1]); //Prints r 

Et:

 printf("%c", *(x + 1)); //Prints r 

De toute évidence, si vous essayez de faire

 *(x + 1) = 'a'; 

Vous aurez probablement une erreur de segmentation, car vous essayez d’accéder à la mémoire en lecture seule.

Juste pour append: vous obtenez également des valeurs différentes pour leurs tailles.

 printf("sizeof s[] = %zu\n", sizeof(s)); //6 printf("sizeof *s = %zu\n", sizeof(s)); //4 or 8 

Comme mentionné ci-dessus, un tableau '\0' sera atsortingbué comme élément final.

 char *str = "Hello"; 

Le paramètre ci-dessus définit str sur la valeur littérale “Hello” qui est codée en dur dans l’image binary du programme, qui est marquée en lecture seule en mémoire, ce qui signifie que toute modification de ce littéral Ssortingng est illégale.

 char str[] = "Hello"; 

copie la chaîne dans la mémoire nouvellement allouée sur la stack. Ainsi, tout changement est autorisé et légal.

 means str[0] = 'M'; 

va changer le str à “Mello”.

Pour plus de détails, veuillez passer par la même question:

Pourquoi est-ce que je reçois une erreur de segmentation lors de l’écriture dans une chaîne de caractères initialisée avec “char * s” mais pas “char s []”?

Dans le cas de:

 char *x = "fred"; 

x est une valeur – il peut être assigné à. Mais dans le cas de:

 char x[] = "fred"; 

x n’est pas une lvalue, c’est une valeur que vous ne pouvez pas lui atsortingbuer.

À la lumière des commentaires, il devrait être évident que: char * s = “hello”; Est-ce une mauvaise idée, et devrait être utilisé dans un champ très étroit.

Cela pourrait être une bonne occasion de souligner que «la correction de const» est une «bonne chose». Où et quand vous le pouvez, utilisez le mot-clé “const” pour protéger votre code, des appelants ou des programmeurs “détendus”, qui sont généralement les plus “détendus” lorsque des pointeurs entrent en jeu.

Assez mélodrame, voici ce que l’on peut réaliser en ornant des pointeurs avec “const”. (Remarque: Il faut lire les déclarations de pointeur de droite à gauche.) Voici les trois différentes manières de vous protéger lorsque vous jouez avec des pointeurs:

 const DBJ* p means "p points to a DBJ that is const" 

– c’est-à-dire que l’object DBJ ne peut pas être modifié via p.

 DBJ* const p means "p is a const pointer to a DBJ" 

– c’est-à-dire que vous pouvez modifier l’object DBJ via p, mais vous ne pouvez pas modifier le pointeur p lui-même.

 const DBJ* const p means "p is a const pointer to a const DBJ" 

– c’est-à-dire que vous ne pouvez pas modifier le pointeur p lui-même, ni modifier l’object DBJ via p.

Les erreurs liées aux tentatives de mutations const-ant sont détectées au moment de la compilation. Il n’y a pas d’espace d’exécution ou de pénalité de vitesse pour const.

(Supposons que vous utilisez le compilateur C ++, bien sûr?)

–DBJ