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.
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.
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.
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).
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.
C99 N1256 projet
il y a deux utilisations complètement différentes de tableaux littéraux:
-
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é. -
partout ailleurs::
- sans nom
- tableau de char qu'est-Ce que le type de littéraux de chaîne en C et C++?
- avec stockage statique
- qui donne UB si modifié
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
ett
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 utiliserp
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
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"
.
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é.
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.
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.
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:
À 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