web-dev-qa-db-fra.com

Quelle est la différence entre char a [] =? String ?; et char * p =? string?;?

Comme le dit le titre, Quelle est la différence entre 

char a[] = ?string?; and 
char *p = ?string?;  

Cette question m’a été posée en entrevue ... Je ne comprends même pas la déclaration.

char a[] = ?string?

Ici, qu'est-ce que l'opérateur ?? Fait-il partie d'une chaîne ou a-t-il une signification particulière?

47
Sachin Mhetre

Le premier est un tableau, l'autre est un pointeur.

La déclaration de tableau char a[6]; demande de réserver un espace pour six caractères, connu sous le nom a. Autrement dit, il existe un emplacement nommé a où six personnages peuvent s'asseoir. La déclaration du pointeur char *p;, d'autre part, demande un emplacement contenant un pointeur. Le pointeur doit être connu sous le nom p et peut pointer n'importe quel caractère (ou tableau contigu de caractères) n'importe où.

Les déclarations

 char a[] = "string";
 char *p = "string"; 

aboutirait à des structures de données qui pourraient être représentées comme ceci:

     +---+---+---+---+---+---+----+
  a: | s | t | r | i | n | g | \0 |
     +---+---+---+---+---+---+----+
     +-----+     +---+---+---+---+---+---+---+ 
  p: |  *======> | s | t | r | i | n | g |\0 |    
     +-----+     +---+---+---+---+---+---+---+ 

Il est important de comprendre qu'une référence telle que x[3] génère un code différent selon que x est un tableau ou un pointeur. Étant donné les déclarations ci-dessus, lorsque le compilateur voit l'expression a[3], il émet du code pour commencer à l'emplacement a, le déplacer de trois éléments et extraire le caractère à cet emplacement. Lorsqu'il voit l'expression p[3], il émet du code pour commencer à l'emplacement p, y extraire la valeur du pointeur, ajouter trois tailles d'élément au pointeur et, finalement, extraire le caractère pointé. Dans l'exemple ci-dessus, a[3] et p[3] se trouvent être le caractère l, mais le compilateur y arrive différemment. 

Source: comp.lang.c FAQ liste · Question 6.2

84
user1208519

Le ? semble être une faute de frappe, il n'est pas sémantiquement valide. La réponse suppose donc que le ? est une faute de frappe et explique ce que l'intervieweur voulait probablement demander.


Les deux sont distinctement différents, pour commencer:

  1. Le premier crée un pointeur.
  2. La seconde crée un tableau.

Lisez la suite pour une explication plus détaillée:

La version Array:

char a[] = "string";  

Crée un tableau suffisamment grand pour contenir le littéral de chaîne "chaîne", y compris son terminateur NULL. Le tableau string est initialisé avec le littéral de chaîne "chaîne". Le tableau peut être modifié ultérieurement. De plus, la taille du tableau est connue même au moment de la compilation, l'opérateur sizeof peut donc être utilisé pour déterminer sa taille. 


La version du pointeur:

char *p  = "string"; 

Crée un pointeur pour pointer sur un littéral de chaîne "chaîne". Ceci est plus rapide que la version du tableau, mais la chaîne pointée par le pointeur ne doit pas être modifiée, car elle se trouve dans une mémoire en lecture seule définie par l'implémentation. La modification d'un tel littéral de chaîne entraîne Undefined Behavior.

En fait, C++ 03 déconseille[Ref 1] utilisation de littéral chaîne sans le mot clé const. La déclaration devrait donc être: 

const char *p = "string";

De plus, vous devez utiliser la fonction strlen() et non pas sizeof pour trouver la taille de la chaîne car l'opérateur sizeof vous donnera simplement la taille de la variable de pointeur.


Quelle version est la meilleure et laquelle devrais-je utiliser?

Dépend de l'utilisation. 

  • Si vous n'avez pas besoin de modifier la chaîne, utilisez la version du pointeur. 
  • Si vous avez l'intention de modifier les données, utilisez la version du tableau.

Note: Ceci n'est pas du C++ mais c'est spécifique du C. 

Notez que l'utilisation d'un littéral de chaîne sans le mot clé const est parfaitement valide dans C . Cependant, la modification d'un littéral de chaîne reste un comportement indéfini en C[Ref 2].

Cela soulève une question intéressante,
Quelle est la différence entre char * et const char * utilisés avec des littéraux de chaîne en C?


Pour les fans de Standerdese:
[Ref 1]C++ 03 Standard: §4.2/2 

Un littéral de chaîne (2.13.4) qui n'est pas un littéral de chaîne large peut être converti en une valeur de type «pointeur sur caractère»; un littéral de chaîne large peut être converti en une valeur de type «pointeur vers wchar_t». Dans les deux cas, le résultat est un pointeur sur le premier élément du tableau. Cette conversion est considérée uniquement lorsqu'il existe un type de cible de pointeur approprié explicite, et non lorsqu'il est généralement nécessaire de convertir une valeur lvalue en une valeur rvalue. [Remarque: cette conversion est obsolète. Voir l'Annexe D.] Dans le but de classer la résolution de surcharge (13.3.3.1.1), cette conversion est considérée comme une conversion de tableau à pointeur suivie d'une conversion de qualification (4.4). [Exemple: "abc" est converti en "pointeur vers const car" en tant que conversion de tableau à pointeur, puis en "pointeur en car" en tant que conversion de qualification. ]

C++ 11 supprime simplement la citation ci-dessus, ce qui implique qu'il s'agit d'un code illégal en C++ 11.

[Ref 2]C99 standard 6.4.5/5 "Littéraux de chaîne - Sémantique": 

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 résultant d'un littéral de chaîne ou de littéraux. La séquence de caractères multi-octets est ensuite utilisée pour initialiser un tableau de durée de stockage statique et de longueur juste suffisante 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; pour les littéraux de chaîne large, les éléments du tableau ont le type wchar_t et sont initialisés avec la séquence de caractères larges ...

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.

87
Alok Save
char a[] = "string";

Cela alloue la chaîne sur la pile.

char *p = "string";

Cela crée un pointeur sur la pile qui pointe vers le littéral du segment de données du processus.

? est celui qui l'a écrit ne sachant pas ce qu'il faisait.

12

Pile, tas, datasegment (et BSS) et segment de texte sont les quatre segments de la mémoire de processus. Toutes les variables locales définies seront en pile. La mémoire allouée dynamiquement à l'aide de malloc et calloc sera dans le tas. Toutes les variables globales et statiques seront dans le segment de données. Le segment de texte aura le code d'assemblage du programme et certaines constantes.

Dans ces 4 segments, le segment de texte est le segment READ ONLY et dans les trois autres correspond à READ et WRITE.

char a[] = "string"; - Ce statut allouera de la mémoire pour 7 octets dans la pile (car variable locale) et conservera tous les 6 caractères (s, t, r, i, n, g) plus le caractère NULL (\0) à la fin.

char *p = "string"; - Cette instruction allouera de la mémoire sur 4 octets (s'il s'agit d'une machine 32 bits) en pile (car il s'agit également d'une variable locale) et tiendra le pointeur de la chaîne de constante contenant la valeur "string". Ces 6 octets de chaîne constante seront dans le segment de texte. C'est une valeur constante. La variable de pointeur p pointe uniquement sur cette chaîne. 

Désormais, a[0] (l'index peut être compris entre 0 et 5) signifie qu'il accédera au premier caractère de cette chaîne qui est dans la pile. Nous pouvons donc écrire aussi à cette position. a[0] = 'x'. Cette opération est autorisée car nous avons un accès READ WRITE dans la pile.

Mais p[0] = 'x' entraînera un crash, car nous n’avons qu'un accès READ au segment de texte. L'erreur de segmentation se produira si nous écrivons sur un segment de texte.

Mais vous pouvez changer la valeur de la variable p, parce que sa variable locale est empilée. comme ci-dessous

char *p = "string";
printf("%s", p);
p = "start";
printf("%s", p);

Ceci est permis. Nous modifions ici l'adresse stockée dans la variable de pointeur p en adresse de la chaîne start (start est également une donnée en lecture seule dans le segment de texte). Si vous souhaitez modifier les valeurs présentes dans *p, cela signifie que vous devez utiliser de la mémoire allouée dynamiquement.

char *p = NULL;
p = malloc(sizeof(char)*7);
strcpy(p, "string");

Maintenant, l'opération p[0] = 'x' est autorisée, car nous écrivons maintenant dans le tas.

7
rashok

char *p = "string"; crée un pointeur sur la mémoire en lecture seule où le littéral de chaîne "string" est stocké. Essayer de modifier la chaîne sur laquelle p pointe sur conduit à un comportement indéfini.

char a[] = "string"; crée un tableau et initialise son contenu à l'aide du littéral de chaîne "string".

6
LihO

Ils diffèrent quant à l'endroit où la mémoire est stockée. Idéalement, le second devrait utiliser const char *.

Le premier 

char buf[] = "hello";

crée un tampon automatique assez grand pour contenir les caractères et les recopier (y compris le terminateur nul).

Le deuxième 

const char * buf = "hello";

devrait utiliser const et crée simplement un pointeur qui pointe vers la mémoire généralement stockée dans un espace statique où il est illégal de la modifier.

L'inverse (du fait que vous pouvez modifier le premier en toute sécurité et non le second) est qu'il est sûr de renvoyer le second pointeur d'une fonction, mais pas le premier. En effet, le second restera un pointeur de mémoire valide en dehors de la portée de la fonction, pas le premier.

const char * sayHello()
{
     const char * buf = "hello";
     return buf; // valid
}

const char * sayHelloBroken()
{
     char buf[] = "hello";
     return buf; // invalid
}
3
CashCow

a déclare un tableau de char valeurs - un tableau de chars qui est terminé.

p déclare un pointeur, qui fait référence à une chaîne C terminée, immuable, dont l'emplacement de stockage exact est défini par l'implémentation. Notez que cela doit être qualifié de const- (par exemple const char *p = "string";).

Si vous l’imprimez à l’aide de std::cout << "a: " << sizeof(a) << "\np: " << sizeof(p) << std::endl;, vous verrez les différences entre leurs tailles (remarque: les valeurs peuvent varier selon le système):

a: 7
p: 8

Ici c'est quoi? opérateur? Fait-il partie d'une chaîne ou a-t-il une signification particulière?

char a[] = ?string?

Je suppose qu’il s’agissait jadis de doubles guillemets "string", potentiellement convertis en "guillemets intelligents", puis ne pouvaient pas être représentés comme tels en cours de route et ont été convertis en ?.

1
justin

C et C++ ont des relations pointeur à tableau très similaires ...

Je ne peux pas vous parler des emplacements de mémoire exacts des deux déclarations sur lesquelles vous posez des questions, mais j’ai trouvé leurs articles intéressants et utiles pour comprendre certaines des différences entre la déclaration de pointeur de caractère et une déclaration de tableau de caractères.

Pour plus de clarté: 

Relation pointeur/tableau C

Pointeur C++ vers un tableau

Je pense qu'il est important de se rappeler qu'un tableau, en C et C++, est un pointeur constant vers le premier élément du tableau. Et par conséquent, vous pouvez effectuer une arithmétique de pointeur sur le tableau. 

char * p = "chaîne"; <--- Ceci est un pointeur qui pointe vers la première adresse d'une chaîne de caractères. 

ce qui suit est également possible:

char *p;
char a[] = "string";

p = a; 

À ce stade, p référence maintenant la première adresse mémoire de a (l'adresse du premier élément)

et ainsi * p == 's'

* (p ++) == 't' et ainsi de suite. (ou * (p + 1) == 't')

et la même chose fonctionnerait pour a: * (a ++) ou * (a + 1) serait également égal à 't'

0
meltdownmonk