Quelle est la différence entre un tableau de caractères et un pointeur de caractères en C?

216

J'essaie de comprendre les pointeurs en C mais je suis actuellement confus avec ce qui suit:

  • char *p = "hello"

    Il s'agit d'un pointeur de caractère pointant sur le tableau de caractères, commençant à h .

  • char p[] = "hello"

    Ceci est un tableau qui stocke bonjour .

Quelle est la différence lorsque je passe ces deux variables dans cette fonction?

void printSomething(char *p)
{
    printf("p: %s",p);
}
diesel
la source
5
Ce ne serait pas valide: char p[3] = "hello";la chaîne d'initialisation est trop longue pour la taille du tableau que vous déclarez. Faute de frappe?
Cody Gray
16
Ou char p[]="hello";suffirait tout simplement !
deepdive
1
doublon possible de Quelle est la différence entre les caractères s [] et les caractères * en C? Certes, cela pose également des questions spécifiques sur le paramètre de fonction, mais ce n'est pas charspécifique.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
vous devez comprendre qu'ils sont fondamentalement différents. le seul point commun à cela est que la base du tableau p [] est un pointeur const qui a permis d'accéder au tableau p [] via un pointeur. p [] lui-même contient de la mémoire pour une chaîne, tandis que * p pointe simplement vers l'adresse du premier élément de ONE CHAR (c'est-à-dire, pointe vers la base de la chaîne déjà allouée). Pour mieux illustrer cela, considérez ci-dessous: char * cPtr = {'h', 'e', ​​'l', 'l', 'o', '\ 0'}; ==> ceci est une erreur, car cPtr est un pointeur vers seulement un caractère char cBuff [] = {'h', 'e', ​​'l', 'l', 'o', '\ 0'}; ==> C'est ok, bcos cBuff lui-même est un tableau de caractères
Ilavarasan

Réponses:

222

char*et char[] sont de types différents , mais ce n'est pas immédiatement apparent dans tous les cas. Cela est dû au fait que les tableaux se désintègrent en pointeurs , ce qui signifie que si une expression de type char[]est fournie là où l'un de type char*est attendu, le compilateur convertit automatiquement le tableau en un pointeur vers son premier élément.

Votre exemple de fonction printSomethingattend un pointeur, donc si vous essayez de lui passer un tableau comme ceci:

char s[10] = "hello";
printSomething(s);

Le compilateur prétend que vous avez écrit ceci:

char s[10] = "hello";
printSomething(&s[0]);
Jon
la source
Quelque chose a changé de 2012 à maintenant. Pour un tableau de caractères "s" imprime un tableau entier .. ie, "bonjour"
Bhanu Tez
@BhanuTez Non, la façon dont les données sont stockées et ce qui est fait avec les données sont des préoccupations distinctes. Cet exemple imprime la chaîne entière car c'est ainsi que printfgère la %schaîne de format: commencez à l'adresse fournie et continuez jusqu'à rencontrer un terminateur nul. Si vous souhaitez imprimer un seul caractère, vous pouvez utiliser la %cchaîne de formatage, par exemple.
iX3
Je voulais juste demander si char *p = "abc";le caractère NULL \0est automatiquement ajouté comme dans le cas du tableau char []?
KPMG
pourquoi je peux définir char *name; name="123";mais peut faire la même chose avec le inttype? Et après avoir utilisé %cpour imprimer name, la sortie est une chaîne illisible: ?
TomSawyer
83

Voyons voir:

#include <stdio.h>
#include <string.h>

int main()
{
    char *p = "hello";
    char q[] = "hello"; // no need to count this

    printf("%zu\n", sizeof(p)); // => size of pointer to char -- 4 on x86, 8 on x86-64
    printf("%zu\n", sizeof(q)); // => size of char array in memory -- 6 on both

    // size_t strlen(const char *s) and we don't get any warnings here:
    printf("%zu\n", strlen(p)); // => 5
    printf("%zu\n", strlen(q)); // => 5

    return 0;
}

foo * et foo [] sont de types différents et ils sont traités différemment par le compilateur (pointeur = adresse + représentation du type du pointeur, tableau = pointeur + longueur facultative du tableau, si connu, par exemple, si le tableau est alloué statiquement ), les détails peuvent être trouvés dans la norme. Et au niveau de l'exécution aucune différence entre eux (en assembleur, enfin presque, voir ci-dessous).

En outre, il existe une question connexe dans la FAQ C :

Q : Quelle est la différence entre ces initialisations?

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

Mon programme se bloque si j'essaie d'attribuer une nouvelle valeur à p [i].

R : Un littéral de chaîne (le terme formel pour une chaîne entre guillemets doubles dans la source C) peut être utilisé de deux manières légèrement différentes:

  1. En tant qu'initialiseur d'un tableau de char, comme dans la déclaration de char a [], il spécifie les valeurs initiales des caractères de ce tableau (et, si nécessaire, sa taille).
  2. Partout ailleurs, il se transforme en un tableau de caractères statique sans nom, et ce tableau sans nom peut être stocké dans une mémoire en lecture seule, et qui ne peut donc pas nécessairement être modifié. Dans un contexte d'expression, le tableau est converti immédiatement en un pointeur, comme d'habitude (voir section 6), donc la deuxième déclaration initialise p pour pointer vers le premier élément du tableau sans nom.

Certains compilateurs ont un commutateur contrôlant si les littéraux de chaîne sont inscriptibles ou non (pour la compilation de l'ancien code), et certains peuvent avoir des options pour que les littéraux de chaîne soient formellement traités comme des tableaux de const char (pour une meilleure capture des erreurs).

Voir également les questions 1.31, 6.1, 6.2, 6.8 et 11.8b.

Références: K & R2 Sec. 5,5 p. 104

ISO Sec. 6.1.4, Sec. 6.5.7

Justification Sec. 3.1.4

H&S Sec. 2.7.4 p. 31-2

JJJ
la source
Dans sizeof (q), pourquoi q ne se désintègre-t-il pas en un pointeur, comme @ Jon le mentionne dans sa réponse?
garyp
@garyp q ne se désintègre pas en un pointeur car sizeof est un opérateur, pas une fonction (même si sizeof était une fonction, q ne se désintégrerait que si la fonction attendait un pointeur char).
GiriB
merci, mais printf ("% u \ n" au lieu de printf ("% zu \ n", je pense que vous devriez supprimer z.
Zakaria
33

Quelle est la différence entre un tableau de caractères et un pointeur de caractères en C?

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 résultant 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" sett dont 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. En revanche, 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.

Mise en œuvre de GCC 4.8 x86-64 ELF

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.

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)

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

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

readelf -l a.out

qui contient:

 Section to Segment mapping:
  Segment Sections...
   02     .text .rodata
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
la source
2
@ leszek.hanusz Comportement indéfini stackoverflow.com/questions/2766731/… Google "C language UB" ;-)
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
9

Vous n'êtes pas autorisé à modifier le contenu d'une constante de chaîne, ce à quoi ppointe le premier . Le second pest un tableau initialisé avec une constante chaîne, et vous pouvez changer son contenu.

potrzebie
la source
6

Pour des cas comme celui-ci, l'effet est le même: vous finissez par passer l'adresse du premier caractère dans une chaîne de caractères.

Mais les déclarations ne sont évidemment pas les mêmes.

Ce qui suit met de côté la mémoire pour une chaîne et également un pointeur de caractère, puis initialise le pointeur pour pointer vers le premier caractère de la chaîne.

char *p = "hello";

Alors que ce qui suit met de côté la mémoire juste pour la chaîne. Il peut donc utiliser moins de mémoire.

char p[10] = "hello";
Jonathan Wood
la source
codeplusplus.blogspot.com/2007/09/… "Cependant, l'initialisation de la variable prend une énorme pénalité en
termes de
@leef: Je pense que cela dépend de l'emplacement de la variable. Si c'est dans la mémoire statique, je pense qu'il est possible que le tableau et les données soient stockés dans l'image EXE et ne nécessitent aucune initialisation du tout. Sinon, oui, cela peut certainement être plus lent si les données doivent être allouées et ensuite les données statiques doivent être copiées.
Jonathan Wood
3

Pour autant que je me souvienne, un tableau est en fait un groupe de pointeurs. Par exemple

p[1]== *(&p+1)

est une vraie déclaration

CosminO
la source
2
Je décrirais un tableau comme étant un pointeur vers l'adresse d'un bloc de mémoire. Par conséquent, pourquoi *(arr + 1)vous amène au deuxième membre de arr. Si *(arr)pointe vers une adresse de mémoire 32 bits, par exemple bfbcdf5e, alors *(arr + 1)pointe vers bfbcdf60(le deuxième octet). Par conséquent, pourquoi sortir de la portée d'un tableau entraînera des résultats étranges si le système d'exploitation ne se bloque pas. Si int a = 24;est à l'adresse bfbcdf62, l'accès arr[2]peut revenir 24, en supposant qu'un défaut de segmentation ne se produit pas en premier.
Braden Best
3

De l' APUE , section 5.14:

char    good_template[] = "/tmp/dirXXXXXX"; /* right way */
char    *bad_template = "/tmp/dirXXXXXX";   /* wrong way*/

... Pour le premier modèle, le nom est alloué sur la pile, car nous utilisons une variable de tableau. Pour le deuxième nom, cependant, nous utilisons un pointeur. Dans ce cas, seule la mémoire du pointeur lui-même réside sur la pile; le compilateur organise le stockage de la chaîne dans le segment en lecture seule de l'exécutable. Lorsque la mkstempfonction tente de modifier la chaîne, une erreur de segmentation se produit.

Le texte cité correspond à l'explication de @Ciro Santilli.

Meule
la source
1

char p[3] = "hello"? devrait se char p[6] = "hello"rappeler qu'il y a un caractère '\ 0' à la fin d'une "chaîne" en C.

quoi qu'il en soit, le tableau en C n'est qu'un pointeur vers le premier objet d'un objet à ajuster dans la mémoire. les seuls différents sont en sémantique. tandis que vous pouvez modifier la valeur d'un pointeur pour pointer vers un emplacement différent dans la mémoire, un tableau, une fois créé, pointera toujours vers le même emplacement.
également lorsque vous utilisez un tableau, les «nouveaux» et «supprimer» sont automatiquement effectués pour vous.

Roee Gavirel
la source