web-dev-qa-db-fra.com

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

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

  • char *p = "hello"
    

    Ceci est un pointeur de caractère pointant vers le tableau de caractères, commençant par 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);
}
195
diesel

char* et char[]sont de types différents, mais ce n'est pas toujours apparent dans tous les cas. Ceci est dû au fait que les tableaux décomposition en pointeurs, ce qui signifie que si une expression de type char[] est fournie alors qu'un type de type char* est attendu, le compilateur convertit automatiquement le tableau en pointeur. à son premier élément.

Votre exemple de fonction printSomething attend un pointeur. Par conséquent, 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]);
207
Jon

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 des types différents et ils sont gérés différemment par le compilateur (pointeur = adresse + représentation du type du pointeur, tableau = pointeur + longueur optionnelle du tableau, si elle est connue, 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 y a un question associé dans le C FAQ :

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].

A: un littéral de chaîne (terme formel pour une chaîne à double guillemet dans la source C) peut être utilisé de deux manières légèrement différentes:

  1. En tant qu'initialiseur pour un tableau de caractères, comme dans la déclaration de caractères 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 statique de noms non nommé. Ce tableau non nommé peut être stocké dans une mémoire en lecture seule et ne peut donc pas nécessairement être modifié. Dans un contexte d'expression, le tableau est immédiatement converti en un pointeur, comme d'habitude (voir la section 6), de sorte que la deuxième déclaration initialise p pour qu'il pointe vers le premier élément du tableau sans nom.

Certains compilateurs ont un commutateur contrôlant si les littéraux de chaîne sont en écriture ou non (pour compiler l'ancien code), et d'autres peuvent avoir des options pour que les littéraux de chaîne soient officiellement traités comme des tableaux de caractères const char (pour une meilleure capture d'erreur).

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

Raison Sec. 3.1.4

H & S Sec. 2.7.4 pp. 31-2

75
JJJ

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 de magie", et décrit à 6.7.8/14 "Initialisation":

    Un tableau de type caractère peut être initialisé par un littéral de chaîne de caractères, éventuellement entre accolades. Les caractères successifs du littéral de chaîne de caractères (y compris le caractère nul final s'il y a de la place ou si le tableau est de taille inconnue) initialisent les éléments du tableau.

    Donc, ceci est juste un raccourci pour:

    char c[] = {'a', 'b', 'c', '\0'};
    

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

  2. Partout ailleurs: il génère un:

    Alors quand tu écris:

    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[] en char *, qui est toujours légale.

    Ensuite, si vous modifiez c[0], vous modifiez également __unnamed, qui est UB.

    Ceci est documenté à la section 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 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 et de longueur de stockage statique 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 [...]

    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" s et t 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. D'autre part, la déclaration

char *p = "abc";

définit p avec le type "pointeur sur caractère" et l'initialise pour pointer sur un objet de type "tableau de caractère" de longueur 4 dont les éléments sont initialisés avec un littéral de chaîne de caractères. Si vous tentez d'utiliser p pour modifier le contenu du tableau, le comportement n'est pas défini.

Implémentation 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 stocke char* le dans la section .rodata, pas dans .text.

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

 char s[] = "abc";

on obtient:

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

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

Notez cependant que le script de l'éditeur de liens par défaut place .rodata et .text dans le même segment, qui dispose de l'autorisation d'exécution, mais non de celle-ci. Ceci peut être observé avec:

readelf -l a.out

qui contient:

 Section to Segment mapping:
  Segment Sections...
   02     .text .rodata

Vous n'êtes pas autorisé à modifier le contenu d'une constante de chaîne, ce à quoi le premier p pointe. La seconde p est un tableau initialisé avec une constante de chaîne et vous pouvez en modifier le contenu.

9
potrzebie

Dans les cas comme celui-ci, l'effet est le même: vous finissez par transmettre l'adresse du premier caractère d'une chaîne de caractères.

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

Les éléments suivants réservent de la mémoire à une chaîne et à un pointeur de caractère, puis initialisent le pointeur pour pointer vers le premier caractère de la chaîne.

char *p = "hello";

Tandis que ce qui suit met de côté la mémoire juste pour la chaîne. Donc, il peut réellement utiliser moins de mémoire.

char p[10] = "hello";
6
Jonathan Wood

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

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

est une déclaration vraie

3
CosminO

De 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 dans la pile; le compilateur organise l'enregistrement de la chaîne dans le segment en lecture seule de l'exécutable. Lorsque la fonction mkstemp tente de modifier la chaîne, une erreur de segmentation se produit.

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

2
Rick

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

de toute façon, le tableau en C est juste un pointeur sur le premier objet d’un objet de réglage dans la mémoire. les seuls s différents sont en sémantique. Bien que vous puissiez changer la valeur d'un pointeur pour qu'il pointe vers un emplacement différent dans la mémoire, un tableau, après avoir été créé, pointe toujours vers le même emplacement.
également lors de l’utilisation de array, les "nouvelles" et "suppressions" sont automatiquement effectuées pour vous.

1
Roee Gavirel