Quelle est la différence entre char s [] et char * s?

506

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 lors de la compilation et de l'exécution.

Conteur - Unslander Monica
la source
8
char * s = "bonjour", ici s peut pointer n'importe quelle autre chaîne au moment de l'exécution Je veux dire que ce n'est pas un pointeur constant, vous pouvez affecter une autre valeur au moment de l'exécution p = "Nishant", tandis que s [] ici s est un pointeur constant .. ..il ne peut pas être ré-attribuer une autre chaîne mais nous pouvons assigner une autre valeur de caractère à s [index].
Nishant Kumar

Réponses:

541

La différence ici est que

char *s = "Hello world";

sera placé "Hello world"dans les parties en lecture seule de la mémoire , et rendre sun pointeur vers 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 mémoire nouvellement allouée sur la pile. Faisant ainsi

s[0] = 'J';

légal.

Rickard
la source
22
La chaîne littérale se "Hello world"trouve dans les "parties en lecture seule de la mémoire" dans les deux exemples. L'exemple avec le tableau y pointe , l'exemple avec le tableau copie les caractères dans les éléments du tableau.
pmg
28
pmg: Dans le deuxième cas, la chaîne littérale n'existe pas nécessairement en mémoire en tant qu'objet contigu unique - c'est juste un initialiseur, le compilateur pourrait émettre de manière très raisonnable une série d'instructions "charger l'octet immédiat" qui contiennent les valeurs de caractères incorporées dans leur.
caf
10
L'exemple de tableau de caractères ne place pas nécessairement la chaîne sur la pile - si elle apparaît au niveau du fichier, elle se trouvera probablement dans une sorte de segment de données initialisé.
caf
9
Je voudrais souligner que char s = "xx" n'a pas besoin d'être en mémoire morte (certaines implémentations n'ont pas de MMU, par exemple). Le projet n1362 c1x indique simplement que la modification d'un tel tableau provoque un comportement indéfini. Mais +1 de toute façon, car compter sur ce comportement est une chose stupide à faire.
paxdiablo
3
J'obtiens une compilation propre sur un fichier contenant uniquement char msg[] = "hello, world!"; la chaîne se retrouve dans la section des données initialisées. Lorsque déclaré char * constpour se retrouver dans la section des données en lecture seule. gcc-4.5.3
gcbenison
152

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 char []qu'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. Donc:

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 ne sont pas définis (ce qui signifie souvent un crash):

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 tant que sa portée de contaning, donc si vous faites cela dans une fonction, ne retournez pas ou ne laissez pas de pointeur sur ce tableau - faites plutôt une copie avec strdup()ou similaire. Si le tableau est alloué dans une portée globale, bien sûr, pas de problème.

bdonlan
la source
72

Cette déclaration:

char s[] = "hello";

Crée un objet - un chartableau de taille 6, appelé s, initialisé avec les valeurs 'h', 'e', 'l', 'l', 'o', '\0'. L'endroit où ce tableau est alloué en mémoire et sa durée de vie dépendent de l'endroit où la déclaration apparaît. Si la déclaration se trouve dans une fonction, elle vivra jusqu'à la fin du bloc dans lequel elle est déclarée et sera presque certainement allouée sur la pile; s'il est en dehors d'une fonction, il sera probablement stocké dans un "segment de données initialisé" qui est chargé à partir du 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 objets:

  • un tableau en lecture seule de 6 chars contenant les valeurs 'h', 'e', 'l', 'l', 'o', '\0', qui n'a pas de nom et a une durée de stockage statique (ce qui signifie qu'il vit pendant toute la durée de vie du programme); et
  • une variable de type pointeur vers caractère, appelée s, qui est initialisée 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 en lecture seule, avec le code lui-même. L'emplacement de la svariable pointeur dans la mémoire dépend de l'endroit où la déclaration apparaît (comme dans le premier exemple).

caf
la source
1
Dans les deux déclarations, la mémoire "bonjour" est allouée à la fois.? Et une autre chose char * p = "bonjour" ici "bonjour" est stockée dans le segment de texte comme vous l'avez dit dans votre réponse ... = "bonjour" sera-t-il également stocké en premier dans la partie de segment de texte et pendant l'exécution, il copiera dans la pile comme Rickard l'a déclaré dans sa réponse. veuillez clarifier ce point.
Nishant Kumar
2
@Nishant: Dans le char s[] = "hello"cas, le "hello"est juste un initialiseur indiquant au compilateur comment le tableau doit être initialisé. Il peut ou non se traduire par une chaîne correspondante dans le segment de texte - par exemple, s'il sa une durée de stockage statique, il est probable que la seule instance de "hello"se trouve dans le segment de données initialisé - l'objet slui-même. Même s'il sa une durée de stockage automatique, il peut être initialisé par une séquence de magasins littéraux plutôt qu'une copie (par exemple. movl $1819043176, -6(%ebp); movw $111, -2(%ebp)).
caf
Plus précisément, GCC 4.8 le place dans .rodata, que le script de l'éditeur de liens transfère ensuite dans le même segment que .text. Voir ma réponse .
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
@caf Dans la première réponse de Rickard, il est écrit que char s[] = "Hello world";la chaîne littérale est placée en mémoire morte et copie la chaîne dans la mémoire nouvellement allouée sur la pile. Mais, votre réponse ne parle que de la mettre de chaîne littérale dans la mémoire en lecture seule et saute la deuxième partie de la phrase qui dit: copies the string to newly allocated memory on the stack. Alors, votre réponse est-elle incomplète pour ne pas avoir spécifié la deuxième partie?
KPMG
1
@AjaySinghNegi: Comme je l'ai dit dans d'autres commentaires (à cette réponse et à la réponse de Rickard), la chaîne de caractères char s[] = "Hellow world";n'est qu'un initialiseur et n'est pas nécessairement stockée du tout comme une copie distincte en lecture seule. Si sla durée de stockage est statique, la seule copie de la chaîne est susceptible d'être dans un segment de lecture-écriture à l'emplacement de s, et même si ce n'est pas le cas, le compilateur peut choisir d'initialiser le tableau avec des instructions de chargement immédiat ou similaire plutôt que de copier à partir d'une chaîne en lecture seule. Le fait est que dans ce cas, la chaîne d'initialisation elle-même n'a pas de présence d'exécution.
caf
60

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 char( const charen C ++) avec une durée de stockage statique, ce qui signifie que sa 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 non défini.

La ligne

char *s0 = "hello world";

se définit s0comme un pointeur sur charla durée de stockage automatique (ce qui signifie que la variable s0n'existe que pour la portée dans laquelle elle est déclarée) et y copie l' adresse du littéral de chaîne ( 0x00008000dans cet exemple). Notez que depuis les s0points à 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";

se définit s1comme un tableau de 12 éléments de char(la longueur est tirée 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.

s0et s1sont 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 s0pour pointer vers un littéral de chaîne différent ou vers une autre variable. Vous ne pouvez pas réaffecter la variable s1pour pointer vers un tableau différent.

John Bode
la source
2
Je pense que la carte mémoire hypothétique le rend facile à comprendre!
midnightBlue
32

C99 N1256 draft

Il existe deux utilisations différentes des littéraux de chaîne de caractères:

  1. Initialiser char[]:

    char c[] = "abc";      

    C'est "plus magique", et décrit au 6.7.8 / 14 "Initialisation":

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

    Ce n'est donc qu'un raccourci pour:

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

    Comme tout autre tableau régulier, cpeut ê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 conversion implicite de char[]à char *, qui est toujours légale.

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

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

    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 qui résulte d'un ou plusieurs littéraux de chaîne. La séquence de caractères multi-octets est ensuite utilisée pour initialiser un tableau de durée et de longueur de stockage statique juste suffisant 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 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.

6.7.8 / 32 "Initialisation" donne un exemple direct:

EXEMPLE 8: La déclaration

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

définit les objets de tableau de caractères "simples" set tdont 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'un autre côté, la déclaration

char *p = "abc";

définit pavec le type "pointeur sur char" et l'initialise pour pointer vers un objet de type "tableau de 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 pmodifier le contenu du tableau, le comportement n'est pas défini.

Implémentation ELF GCC 4.8 x86-64

Programme:

#include <stdio.h>

int main(void) {
    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: GCC le stocke char*dans la .rodatasection, pas dans .text.

Notez cependant que le script de l'éditeur de liens par défaut place .rodataet .textdans le même segment , qui a exécuter mais aucune autorisation d'écriture. Cela peut être observé avec:

readelf -l a.out

qui contient:

 Section to Segment mapping:
  Segment Sections...
   02     .text .rodata

Si nous faisons de même 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 pile (par rapport à %rbp).

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
la source
15
char s[] = "hello";

déclare sêtre un tableau chardont la longueur est suffisante 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) charet le pointe directement vers un emplacement fixe (en lecture seule) contenant le littéral "hello".

CB Bailey
la source
1
Quelle méthode est préférable d'utiliser dans les fonctions si s ne sera pas modifié, f (const char s []) ou f (const char * s)?
psihodelia
1
@psihodelia: Dans une déclaration de fonction, il n'y a pas de différence. Dans les deux cas sest un pointeur vers const char.
CB Bailey
4
char s[] = "Hello world";

Voici sun 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 vers laquelle spointe ce pointeur . 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é.

Sailaja
la source
@bo Persson Pourquoi le bloc de caractères ne peut pas être changé dans le deuxième cas?
Pankaj Mahato
3

En outre, considérez que, comme à des fins de lecture seule, l'utilisation des deux est identique, vous pouvez accéder à un caractère en l'indexant avec []ou au *(<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 un défaut de segmentation, car vous essayez d'accéder à la mémoire en lecture seule.

Nick Louloudakis
la source
Ce n'est en rien différent de x[1] = 'a';ce qui se produira également (en fonction de la plate-forme, bien sûr).
glglgl
3

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 alloué comme élément final.

Muzab
la source
2
char *str = "Hello";

Les ensembles ci-dessus str pointent vers la valeur littérale "Hello" qui est codée en dur dans l'image binaire du programme, qui est marquée comme lecture seule en mémoire, signifie que tout changement dans ce littéral String est illégal et cela entraînerait des erreurs de segmentation.

char str[] = "Hello";

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

means str[0] = 'M';

changera la chaîne en "Mello".

Pour plus de détails, veuillez passer par la question similaire:

Pourquoi est-ce que j'obtiens une erreur de segmentation lors de l'écriture dans une chaîne initialisée avec "char * s" mais pas "char s []"?

Mohit
la source
0

Dans le cas de:

char *x = "fred";

x est une valeur l - elle peut être affectée à. Mais dans le cas de:

char x[] = "fred";

x n'est pas une valeur l, c'est une valeur r - vous ne pouvez pas lui attribuer.

Lee-Man
la source
3
Techniquement, xest une valeur non modifiable. Dans presque tous les contextes cependant, il évaluera un pointeur sur son premier élément, et cette valeur est une valeur r.
caf
0
char *s1 = "Hello world"; // Points to fixed character string which is not allowed to modify
char s2[] = "Hello world"; // As good as fixed array of characters in string so allowed to modify

// s1[0] = 'J'; // Illegal
s2[0] = 'J'; // Legal
Atul
la source
-1

À la lumière des commentaires ici, il devrait être évident que: char * s = "bonjour"; Est une mauvaise idée et doit être utilisée dans un cadre très étroit.

Cela pourrait être une bonne occasion de souligner que «l'exactitude de la const» est une «bonne chose». Quand et où 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 plus "détendus" lorsque les pointeurs entrent en jeu.

Assez de mélodrame, voici ce que l'on peut réaliser en ornant les pointeurs de "const". (Remarque: il faut lire les déclarations de pointeurs de droite à gauche.) Voici les 3 différentes façons 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'objet 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 changer l'objet DBJ via p, mais vous ne pouvez pas changer 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 changer le pointeur p lui-même, ni changer l'objet 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 ni de pénalité de vitesse pour const.

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

--DBJ


la source
Tout cela est correct, mais cela n'a rien à voir avec la question. Et en ce qui concerne votre hypothèse sur un compilateur C ++, la question est étiquetée comme C, pas comme C ++.
Fabio dit Réintégrer Monica
Il n'y a rien de mal à char * s = "const string";
Paul Smith