Question 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 ceci:

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.


439
2017-11-09 22:34


origine


Réponses:


La différence est que

char *s = "Hello world";

va placer "Hello world" dans le parties en lecture seule de la mémoireet faire s un pointeur sur cela rend toute opération d'écriture sur cette mémoire illégale.

Tout en faisant:

char s[] = "Hello world";

place la chaîne littérale dans la mémoire morte et copie la chaîne dans la nouvelle mémoire allouée sur la pile. Ainsi faisant

s[0] = 'J';

légal.


480
2017-11-09 22:38



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, tout en char [] alloue un tableau. Où va la chaîne dans le premier cas, demandez-vous? 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 essayer de modifier le contenu de ce tableau anonyme via ce pointeur; les effets sont indéfinis (ce qui signifie souvent un accident):

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

L'utilisation de la syntaxe du tableau l'alloue directement dans la nouvelle mémoire. Ainsi, la modification est sûre:

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

Cependant, le tableau ne vit que pendant sa portée de contaning, donc si vous faites cela dans une fonction, ne retournez pas ou ne perdez pas un pointeur vers ce tableau - faites une copie à la place avec strdup() ou similaire. Si le tableau est alloué de manière globale, bien sûr, pas de problème.


132
2017-11-09 22:45



Cette déclaration:

char s[] = "hello";

Crée un objet - un char tableau de taille 6, appelé s, initialisé avec les valeurs 'h', 'e', 'l', 'l', 'o', '\0'. L'emplacement de ce tableau en mémoire et sa durée de vie dépendent de l'emplacement de la déclaration. Si la déclaration est dans une fonction, elle restera jusqu'à la fin du bloc dans lequel elle est déclarée, et sera certainement allouée sur la pile; s'il est en dehors d'une fonction, il le fera Probablement être stocké dans un "segment de données initialisé" qui est chargé à partir du fichier exécutable dans la mémoire inscriptible lorsque le programme est exécuté.

En revanche, cette déclaration:

char *s ="hello";

Crée deux objets:

  • une lecture seulement tableau de 6 chars contenant les valeurs 'h', 'e', 'l', 'l', 'o', '\0', qui n'a pas de nom et a durée de stockage statique (ce qui signifie qu'il vit toute la vie du programme); et
  • une variable de type pointeur à char, appelée s, qui est initialisé avec l'emplacement du premier caractère dans ce tableau en lecture seule sans nom.

Le tableau en lecture seule sans nom est généralement situé dans le segment «texte» du programme, ce qui signifie qu'il est chargé à partir du disque dans la mémoire morte, avec le code lui-même. L'emplacement de la s La variable de pointeur en mémoire dépend de l'endroit où la déclaration apparaît (comme dans le premier exemple).


60
2017-11-09 22:42



Compte tenu des 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

Le littéral de chaîne "hello world" est un tableau de 12 éléments de 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 reste allouée jusqu'à la fin du programme. Tenter de modifier le contenu d'un littéral de chaîne appelle un comportement indéfini.

La ligne

char *s0 = "hello world";

définit s0 comme pointeur vers char avec la durée de stockage automatique (ce qui signifie la variable s0 n'existe que pour le périmètre dans lequel il est déclaré) et copie le adresse de la chaîne littérale (0x00008000 dans cet exemple). Notez que depuis s0 pointe vers un littéral de chaîne, il ne doit pas être utilisé comme argument pour une fonction qui tenterait de le modifier (par exemple, strtok(), strcat(), strcpy(), etc.).

La ligne

char s1[] = "hello world";

définit s1 comme un tableau de 12 éléments de char (longueur est tirée de la chaîne littérale) avec la durée de stockage automatique et copie le Contenu du littéral au 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 pointer sur un littéral de chaîne différent ou sur une autre variable. Vous ne pouvez pas réaffecter la variable s1 pointer vers un tableau différent.


51
2017-11-09 23:03



Projet C99 N1256

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

  1. Initialiser char[]:

    char c[] = "abc";      
    

    Ceci est "plus magique", et décrit à 6.7.8 / 14 "Initialisation":

    Un tableau de type caractère peut être initialisé par une chaîne de caractères littérale, optionnellement   enfermé dans des accolades. Caractères successifs de la chaîne de caractères littérale (y compris le   fin du caractère nul s'il y a de la place ou si le tableau est de taille inconnue) initialise le   éléments du tableau.

    Donc, ceci est juste un raccourci pour:

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

    Comme n'importe quel autre tableau régulier, c peut être modifié.

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

    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 distribution implicite de char[] à char *, qui est toujours légal.

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

    Ceci est documenté à 6.4.5 "Littéraux de chaîne":

    5 En phase de traduction 7, un octet ou code de valeur zéro est ajouté à chaque multi-octet   séquence de caractères résultant d'un littéral de chaîne ou de littéraux. Le caractère multi-octets   séquence est ensuite utilisée pour initialiser un tableau de durée de stockage statique et la longueur juste   suffisant pour contenir la séquence. Pour les littéraux de chaîne de caractères, les éléments du tableau ont   type char, et sont initialisés avec les octets individuels du caractère multi-octets   séquence [...]

    6 Il n'est pas précisé si ces matrices sont distinctes à condition que leurs éléments aient la   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 des objets de tableau char "simples" 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. D'autre part, la déclaration

char *p = "abc";

définit p avec type "pointer to char" et l'initialise pour pointer vers un objet 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 une tentative est faite pour utiliser p pour modifier le contenu du tableau, le comportement est indéfini.

Implémentation ELF GCC 4.8 x86-64

Programme:

#include <stdio.h>

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

Compiler et décompiler:

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

La sortie 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: les magasins du CCG char* dans .rodata section, ne 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)

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

Notez cependant que le script de l'éditeur de liens par défaut .rodata et .text dans le même segment, qui a une autorisation 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 

26
2018-06-05 07:32



char s[] = "hello";

déclare s être un tableau de char qui est assez long pour contenir l'initialiseur (5 + 1 chars) et initialise le tableau en copiant les membres du littéral de chaîne donné dans le tableau.

char *s = "hello";

déclare s être un pointeur vers un ou plusieurs (dans ce cas plus) chars et le pointe directement à un emplacement fixe (en lecture seule) contenant le littéral "hello".


14
2017-11-09 22:40



char s[] = "Hello world";

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

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 que ce pointeur s pointe vers. Nous pouvons ici réaffecter l'objet vers lequel il pointe en changeant cela, mais tant qu'il pointe vers une chaîne littérale, le bloc de caractères vers lequel il pointe ne peut pas être changé.


3
2017-11-09 22:55



En plus, considérons que, comme pour les fins de lecture seule, l'utilisation des deux est identique, vous pouvez accéder à un char en indexant soit avec [] ou *(<var> + <index>) format:

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 obtiendrez probablement une erreur de segmentation, car vous essayez d'accéder à la mémoire en lecture seule.


3
2017-11-30 10:22



Juste pour ajouter: 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, pour un tableau '\0' sera attribué en tant qu'élément final.


3
2017-11-21 10:39



char *str = "Hello";

La commande ci-dessus pointe vers la valeur littérale "Hello" qui est codée en dur dans l'image binaire du programme, qui est signalée comme étant en lecture seule. Toute modification de ce littéral String est donc illégale et entraînerait des erreurs de segmentation.

char str[] = "Hello";

copie la chaîne dans la nouvelle mémoire allouée sur la pile. 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 question similaire:

Pourquoi ai-je une erreur de segmentation lorsque j'écris sur une chaîne initialisée avec "char * s" mais pas "char s []"?


2
2017-11-11 11:46