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

en C, on peut utiliser une chaîne littérale dans une déclaration comme celle-ci:

char s[] = "hello";

ou comme ceci:

char *s = "hello";

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

448
demandé sur StoryTeller 2009-11-10 01:34:21

12 réponses

la différence ici est que

char *s = "Hello world";

placera "Hello world" dans la parties en lecture seule de la mémoire , et de faire s un pointeur à ce qui 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 en lecture seule et copie la chaîne dans la mémoire nouvellement attribuée sur la pile. Ce qui fait 151960920"

s[0] = 'J';

juridique.

487
répondu Rickard 2016-03-11 14:28:58

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 * attribue un pointeur, tandis que char [] attribue un tableau. Où va la ficelle dans le premier cas, vous demandez? Le compilateur attribue secrètement un tableau anonyme statique pour maintenir la chaîne littérale. So:

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.

utilisant la syntaxe du tableau l'attribue directement dans une nouvelle mémoire. Ainsi, la modification est sans danger:

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

cependant le tableau ne vit que tant que son contaning scope, donc si vous faites cela dans une fonction, ne retournez pas ou ne laissez pas échapper un pointeur vers ce tableau - faites une copie à la place avec strdup() ou similaire. Si le tableau est alloué dans la portée globale, Bien sûr, aucun problème.

136
répondu bdonlan 2017-12-02 17:52:30

cette déclaration:

char s[] = "hello";

Crée un objet char tableau de taille 6, appelé s , initialisé avec les valeurs 'h', 'e', 'l', 'l', 'o', '"151940920"' . D'où ce tableau est alloué dans la mémoire, et la durée de vie, dépend de l'endroit où la déclaration apparaît. Si la déclaration est à l'intérieur d'une fonction, il va vivre jusqu'à la fin du bloc qu'elle est déclarée, et presque certainement être allouée sur la pile; si c'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 en écriture lorsque le programme est lancé.

, d'autre part, cette déclaration:

char *s ="hello";

Crée deux les objets:

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

le tableau sans nom en lecture seule est typiquement situé dans le segment" text " du programme, ce qui signifie qu'il est chargé à partir de disque dans la mémoire en lecture seule, avec le code lui-même. L'emplacement de la variable indicatrice s dans la mémoire dépend de l'endroit où la déclaration apparaît (comme dans le premier exemple).

62
répondu caf 2009-11-09 22:50:25

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 de 12 éléments de char ( const char en C++) avec une durée de stockage statique, ce qui signifie que la mémoire pour elle est attribuée lorsque le programme démarre et reste attribuée jusqu'à ce que le programme se termine. Tenter de modifier le contenu d'une chaîne de caractères littérale appelle pas défini comportement.

La ligne

char *s0 = "hello world";

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

La ligne

char s1[] = "hello world";

définit s1 comme un tableau de 12 éléments de char (la longueur est prise à partir de la chaîne literal) avec la durée de stockage automatique et copie le contenu du literal 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éattribuer la variable s0 pour pointer vers une autre chaîne littérale ou vers une autre variable. Vous ne pouvez pas réattribuer la variable s1 pour pointer vers un tableau différent.

53
répondu John Bode 2015-03-26 15:22:09

C99 N1256 projet

il y a deux utilisations complètement différentes de tableaux littéraux:

  1. Initialiser char[] :

    char c[] = "abc";      
    

    c'est "plus de magie", et décrit à 6.7.8 / 14 "initialisation " :

    un tableau de caractères 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éral (y compris le terminez le caractère null s'il y a de la place ou si le tableau est de taille inconnue) initialisez les éléments de la matrice.

    donc ce n'est qu'un raccourci pour:

    char c[] = {'a', 'b', 'c', '"151910920"'};
    

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

  2. partout ailleurs::

    donc quand vous écrivez:

    char *c = "abc";
    

    similaire à:

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

    noter la fonte implicite de char[] à char * , ce qui est toujours légal.

    puis si vous modifiez c[0] , vous modifiez aussi __unnamed , qui est UB.

    C'est documentée à l' 6.4.5 "littéraux de Chaîne" :

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

    6 il n'est pas précisé si ces matrices sont distinctes à condition que leurs éléments aient la forme les valeurs appropriées. Si le programme tente de modifier un tel tableau, le comportement est indéterminé.

6.7.8/32 "L'initialisation " donne un exemple direct:

exemple 8: la déclaration

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

définit les objets" char array s et t dont les éléments sont initialisés par des caractères littéraux.

cette déclaration est identique à

char s[] = { 'a', 'b', 'c', '"151950920"' },
t[] = { 'a', 'b', 'c' };

le contenu des tableaux est modifiable. D'autre part, la déclaration

char *p = "abc";

définit p avec le type "pointeur vers char" et l'initialise pour pointer vers un objet avec le type "tableau de char" avec la 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.

GCC 4.8 x86-64 ELF implementation

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

contient:

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

Conclusion: GCC magasins char* il en .rodata , pas dans .text .

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

 char s[] = "abc";

nous obtenons:

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

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

notez cependant que le script de linker par défaut met .rodata et .text dans le même segment, qui a exécuter mais pas de permission d'écriture. Cela peut être observé avec:

readelf -l a.out

qui contient:

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

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

char *s = "hello";

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

15
répondu CB Bailey 2015-09-05 14:12:10
char s[] = "Hello world";

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

char *s = "hello";

une chaîne littérale est utilisée pour créer ces blocs de caractères quelque part dans la mémoire que ce pointeur s pointe. Nous ne pouvons ici réaffecter l'objet qu'il désigne par l'évolution, mais tant qu'il pointe vers une chaîne littérale du bloc de caractères à laquelle il pointe ne peut pas être changé.

4
répondu Sailaja 2012-04-27 15:10:58

comme ajout, considérer que, comme pour les buts en 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

évidemment, si vous tentez de le faire

*(x + 1) = 'a';

vous aurez probablement un défaut de Segmentation, car vous essayez d'accéder à la mémoire en lecture seule.

3
répondu Nick Louloudakis 2015-11-30 10:22:03

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 '" 151910920 "' sera attribué comme élément final.

3
répondu Muzab 2017-03-31 15:28:44
char *str = "Hello";

ce qui précède définit str pour pointer vers la valeur littérale" Hello " qui est codée en dur dans l'image binaire du programme, qui est marqué comme lecture seule dans la mémoire, signifie que tout changement dans cette chaîne littérale est illégal et qui lancerait des défauts de segmentation.

char str[] = "Hello";

copie la chaîne à la mémoire nouvellement attribuée sur la pile. Ainsi, toute modification est autorisée et légale.

means str[0] = 'M';

changera la DOD en"Mello".

pour plus de détails, s'il vous plaît passer par la question similaire:

Pourquoi est-ce que j'obtiens un défaut de segmentation en écrivant à une chaîne de caractères initialisée avec "char *s"mais pas" char s []"?

2
répondu Mohit 2017-05-23 12:26:36

dans le cas de:

char *x = "fred";

x est un lvalue -- il peut être assigné à. Mais dans le cas de:

char x[] = "fred";

x n'est pas une lvalue, c'est une rvalue -- vous ne pouvez pas lui attribuer.

0
répondu Lee-Man 2009-11-09 22:57:04

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

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 "décontractés" ou des programmeurs, qui sont généralement les plus "décontractés" lorsque les pointeurs entrent en jeu.

assez de mélodrame, voici ce que l'on peut accomplir en ornant des pointeurs avec "const". (Note: il faut lire les déclarations de pointeur 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" 

- qui est, vous ne pouvez pas modifier la pointeur p lui-même, vous ne pouvez pas non plus changer L'objet DBJ via P.

les erreurs liées aux tentatives de mutation 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.

(supposition est-ce que vous utilisez le compilateur C++, bien sûr ?)

-- DBJ

0
répondu 2009-11-09 23:27:55