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.
Réponses:
La différence ici est que
sera placé
"Hello world"
dans les parties en lecture seule de la mémoire , et rendres
un pointeur vers cela rend toute opération d'écriture sur cette mémoire illégale.Tout en faisant:
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
légal.
la source
"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.char msg[] = "hello, world!";
la chaîne se retrouve dans la section des données initialisées. Lorsque déclaréchar * const
pour se retrouver dans la section des données en lecture seule. gcc-4.5.3Tout d'abord, dans les arguments de fonction, ils sont exactement équivalents:
Dans d'autres contextes,
char *
alloue un pointeur, tandischar []
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: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):
L'utilisation de la syntaxe du tableau l'alloue directement dans la nouvelle mémoire. Ainsi, la modification est sûre:
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.la source
Cette déclaration:
Crée un objet - un
char
tableau 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:
Crée deux objets:
char
s 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); ets
, 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
s
variable pointeur dans la mémoire dépend de l'endroit où la déclaration apparaît (comme dans le premier exemple).la source
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'ils
a 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'objets
lui-même. Même s'ils
a 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)
)..rodata
, que le script de l'éditeur de liens transfère ensuite dans le même segment que.text
. Voir ma réponse .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?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. Sis
la 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 des
, 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.Compte tenu des déclarations
supposons la carte mémoire hypothétique suivante:
Le littéral de chaîne
"hello world"
est un tableau de 12 élémentschar
(const char
en 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
se définit
s0
comme un pointeur surchar
la durée de stockage automatique (ce qui signifie que la variables0
n'existe que pour la portée dans laquelle elle est déclarée) et y copie l' adresse du littéral de chaîne (0x00008000
dans cet exemple). Notez que depuis less0
points à 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
se définit
s1
comme un tableau de 12 éléments dechar
(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 danss1
.s0
ets1
sont interchangeables dans la plupart des contextes; voici les exceptions:Vous pouvez réaffecter la variable
s0
pour pointer vers un littéral de chaîne différent ou vers une autre variable. Vous ne pouvez pas réaffecter la variables1
pour pointer vers un tableau différent.la source
C99 N1256 draft
Il existe deux utilisations différentes des littéraux de chaîne de caractères:
Initialiser
char[]
:C'est "plus magique", et décrit au 6.7.8 / 14 "Initialisation":
Ce n'est donc qu'un raccourci pour:
Comme tout autre tableau régulier,
c
peut être modifié.Partout ailleurs: il génère un:
Donc, quand vous écrivez:
Ceci est similaire à:
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":
6.7.8 / 32 "Initialisation" donne un exemple direct:
Implémentation ELF GCC 4.8 x86-64
Programme:
Compiler et décompiler:
La sortie contient:
Conclusion: GCC le stocke
char*
dans la.rodata
section, pas dans.text
.Notez cependant que le script de l'éditeur de liens par défaut place
.rodata
et.text
dans le même segment , qui a exécuter mais aucune autorisation d'écriture. Cela peut être observé avec:qui contient:
Si nous faisons de même pour
char[]
:on obtient:
il est donc stocké dans la pile (par rapport à
%rbp
).la source
déclare
s
être un tableauchar
dont la longueur est suffisante pour contenir l'initialiseur (5 + 1char
s) et initialise le tableau en copiant les membres du littéral de chaîne donné dans le tableau.déclare
s
être un pointeur vers un ou plusieurs (dans ce cas plus)char
et le pointe directement vers un emplacement fixe (en lecture seule) contenant le littéral"hello"
.la source
s
est un pointeur versconst char
.Voici
s
un tableau de caractères qui peut être écrasé si nous le souhaitons.Un littéral de chaîne est utilisé pour créer ces blocs de caractères quelque part dans la mémoire vers laquelle
s
pointe 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é.la source
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:Et:
De toute évidence, si vous essayez de faire
Vous obtiendrez probablement un défaut de segmentation, car vous essayez d'accéder à la mémoire en lecture seule.
la source
x[1] = 'a';
ce qui se produira également (en fonction de la plate-forme, bien sûr).Juste pour ajouter: vous obtenez également des valeurs différentes pour leurs tailles.
Comme mentionné ci-dessus, pour un tableau
'\0'
sera alloué comme élément final.la source
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.
copie la chaîne dans la mémoire nouvellement allouée sur la pile. Ainsi, tout changement est autorisé et légal.
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 []"?
la source
Dans le cas de:
x est une valeur l - elle peut être affectée à. Mais dans le cas de:
x n'est pas une valeur l, c'est une valeur r - vous ne pouvez pas lui attribuer.
la source
x
est 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.la source
À 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:
- c'est-à-dire que l'objet DBJ ne peut pas être modifié via p.
- c'est-à-dire que vous pouvez changer l'objet DBJ via p, mais vous ne pouvez pas changer le pointeur p lui-même.
- 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