web-dev-qa-db-fra.com

C: différences entre le pointeur et le tableau

Considérer:

char amessage[] = "now is the time";
char *pmessage = "now is the time";

J'ai lu dans The C Programming Language , 2e édition, que les deux instructions ci-dessus ne font pas la même chose.

J'ai toujours pensé qu'un tableau était un moyen pratique de manipuler des pointeurs pour stocker des données, mais ce n'est clairement pas le cas ... Quelles sont les différences "non triviales" entre les tableaux et les pointeurs en C?

135
Midnight Blue

C'est vrai, mais c'est une différence subtile. Essentiellement, l'ancien:

char amessage[] = "now is the time";

Définit un tableau dont les membres résident dans l'espace de pile de l'étendue actuelle, alors que:

char *pmessage = "now is the time";

Définit un pointeur qui réside dans l'espace de pile de l'étendue actuelle, mais qui fait référence à de la mémoire ailleurs (dans celui-ci, "maintenant c'est l'heure" est stocké ailleurs dans la mémoire, généralement une table de chaînes).

Notez également que, les données appartenant à la deuxième définition (le pointeur explicite) n'étant pas stockées dans l'espace de pile de l'étendue actuelle, elles ne sont pas spécifiées exactement à l'endroit où elles seront stockées et ne doivent pas être modifiées.

Edit: Comme l’ont souligné Mark, GMan et Pavel, il existe également une différence lorsque l’opérateur adresse-de est utilisé sur l’une ou l’autre de ces variables. Par exemple, & pmessage renvoie un pointeur de type char ** ou un pointeur sur un pointeur de caractères, alors que & amessage renvoie un pointeur de type char (*) [16] ou un pointeur sur un tableau de 16 caractères (qui, comme un caractère **, doit être déréférencé deux fois, comme il est indiqué).

95
Walt W

Voici une carte mémoire hypothétique, montrant les résultats des deux déclarations:

                0x00  0x01  0x02  0x03  0x04  0x05  0x06  0x07
    0x00008000:  'n'   'o'   'w'   ' '   'i'   's'   ' '   't'
    0x00008008:  'h'   'e'   ' '   't'   'i'   'm'   'e'  '\0'
        ...
amessage:
    0x00500000:  'n'   'o'   'w'   ' '   'i'   's'   ' '   't'
    0x00500008:  'h'   'e'   ' '   't'   'i'   'm'   'e'  '\0'
pmessage:
    0x00500010:  0x00  0x00  0x80  0x00

Le littéral de chaîne "now is the time" est stocké sous la forme d'un tableau de 16 caractères à l'adresse de mémoire 0x00008000. Cette mémoire peut ne pas être accessible en écriture; il vaut mieux supposer que ce n'est pas le cas. Vous ne devriez jamais essayer de modifier le contenu d'un littéral de chaîne.

La déclaration

char amessage[] = "now is the time";

alloue un tableau de caractères de 16 éléments à l'adresse mémoire 0x00500000 et y copie le conten du littéral chaîne. Cette mémoire est en écriture. vous pouvez changer le contenu d'un message à votre guise:

strcpy(amessage, "the time is now");

La déclaration

char *pmessage = "now is the time";

attribue un seul pointeur à char à l'adresse mémoire 0x00500010 et copie le adresse du littéral chaîne.

Puisque pmessage pointe vers le littéral de chaîne, il ne devrait pas être utilisé comme argument des fonctions devant modifier le contenu de la chaîne:

strcpy(amessage, pmessage); /* OKAY */
strcpy(pmessage, amessage); /* NOT OKAY */
strtok(amessage, " ");      /* OKAY */
strtok(pmessage, " ");      /* NOT OKAY */
scanf("%15s", amessage);      /* OKAY */
scanf("%15s", pmessage);      /* NOT OKAY */

etc. Si vous avez modifié pmessage pour indiquer amessage:

pmessage = amessage;

alors il peut être utilisé partout où un message peut être utilisé.

143
John Bode

Un tableau contient les éléments. Un pointeur pointe vers eux.

Le premier est une forme courte de dire

char amessage[16];
amessage[0] = 'n';
amessage[1] = 'o';
...
amessage[15] = '\0';

C'est-à-dire que c'est un tableau qui contient tous les caractères. L'initialisation spéciale l'initialise pour vous et la détermine automatiquement. Les éléments du tableau sont modifiables - vous pouvez écraser les caractères qu’il contient.

La deuxième forme est un pointeur, qui pointe simplement vers les caractères. Il stocke les caractères pas directement. Comme le tableau est un littéral de chaîne, vous ne pouvez pas prendre le pointeur et écrire à l'endroit où il pointe

char *pmessage = "now is the time";
*pmessage = 'p'; /* undefined behavior! */

Ce code planterait probablement sur votre boîte. Mais il peut faire tout ce qu'il veut, car son comportement n'est pas défini.

12

Je ne peux pas ajouter utilement aux autres réponses, mais je ferai remarquer que, dans Deep C Secrets , Peter van der Linden couvre cet exemple en détail. Si vous posez ce genre de questions, je pense que vous allez adorer ce livre.


P.S. Vous pouvez attribuer une nouvelle valeur à pmessage. Vous ne pouvez pas assigner une nouvelle valeur à amessage; il est immuable .

6
Norman Ramsey

Si un tableau est défini pour que sa taille soit disponible au moment de la déclaration, sizeof(p)/sizeof(type-of-array) renverra le nombre d'éléments du tableau.

5
Tamás Szelei

Le premier formulaire (amessage) définit une variable (un tableau) contenant une copie de la chaîne "now is the time".

La seconde forme (pmessage) définit une variable (un pointeur) qui réside dans un emplacement différent de celui de toute copie de la chaîne "now is the time".

Essayez ce programme sur:

#include <inttypes.h>
#include <stdio.h>

int main (int argc, char *argv [])
{
     char  amessage [] = "now is the time";
     char *pmessage    = "now is the time";

     printf("&amessage   : %#016"PRIxPTR"\n", (uintptr_t)&amessage);
     printf("&amessage[0]: %#016"PRIxPTR"\n", (uintptr_t)&amessage[0]);
     printf("&pmessage   : %#016"PRIxPTR"\n", (uintptr_t)&pmessage);
     printf("&pmessage[0]: %#016"PRIxPTR"\n", (uintptr_t)&pmessage[0]);

     printf("&\"now is the time\": %#016"PRIxPTR"\n",
            (uintptr_t)&"now is the time");

     return 0;
}

Vous verrez que tandis que &amessage est égal à &amessage[0], ce n'est pas vrai pour &pmessage et &pmessage[0]. En fait, vous verrez que la chaîne stockée dans amessage habite sur la pile, tandis que la chaîne pointée par pmessage habite ailleurs.

La dernière printf montre l'adresse du littéral de chaîne. Si votre compilateur effectue un "pooling de chaînes", il ne restera qu'une copie de la chaîne "le moment est venu" - et vous verrez que son adresse n'est pas la même que celle de amessage. Ceci est dû au fait que amessage obtient un copie de la chaîne lorsqu'elle est initialisée.

En fin de compte, le point important est que amessage stocke la chaîne dans sa propre mémoire (sur la pile, dans cet exemple), tandis que pmessage pointe sur la chaîne qui est stockée ailleurs.

4
Dan Moulding

En plus de la mémoire pour la chaîne "now is the time" étant alloué à deux endroits différents, vous devez également garder à l'esprit que le nom du tableau agit comme un pointeur valeur par opposition à un pointeur variable quel pmessage est. La principale différence est que la variable de pointeur peut être modifiée pour pointer ailleurs et que le tableau ne le peut pas.

char arr[] = "now is the time";
char *pchar = "later is the time";

char arr2[] = "Another String";

pchar = arr2; //Ok, pchar now points at "Another String"

arr = arr2; //Compiler Error! The array name can be used as a pointer VALUE
            //not a pointer VARIABLE
4
Graphics Noob

Un pointeur est juste une variable qui contient une adresse mémoire. Notez que vous jouez avec "string literals", ce qui est un autre problème. Différences expliquées en ligne: Fondamentalement:

#include <stdio.h>

int main ()
{

char amessage[] = "now is the time"; /* Attention you have created a "string literal" */

char *pmessage = "now is the time";  /* You are REUSING the string literal */


/* About arrays and pointers */

pmessage = NULL; /* All right */
amessage = NULL; /* Compilation ERROR!! */

printf ("%d\n", sizeof (amessage)); /* Size of the string literal*/
printf ("%d\n", sizeof (pmessage)); /* Size of pmessage is platform dependent - size of memory bus (1,2,4,8 bytes)*/

printf ("%p, %p\n", pmessage, &pmessage);  /* These values are different !! */
printf ("%p, %p\n", amessage, &amessage);  /* These values are THE SAME!!. There is no sense in retrieving "&amessage" */


/* About string literals */

if (pmessage == amessage)
{
   printf ("A string literal is defined only once. You are sharing space");

   /* Demostration */
   "now is the time"[0] = 'W';
   printf ("You have modified both!! %s == %s \n", amessage, pmessage);
}


/* Hope it was useful*/
return 0;
}
4
Sergio

Le second attribue la chaîne dans une section en lecture seule du fichier ELF. Essayez ce qui suit:

#include <stdio.h>

int main(char argc, char** argv) {
    char amessage[] = "now is the time";
    char *pmessage = "now is the time";

    amessage[3] = 'S';
    printf("%s\n",amessage);

    pmessage[3] = 'S';
    printf("%s\n",pmessage);
}

et vous obtiendrez une erreur de segmentation sur la deuxième affectation (pmessage [3] = 'S').

3
benzaita

différences entre le pointeur et le tableau

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 distribution implicite de char[] à char *, qui est toujours légal.

    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: les magasins GCC char* dans .rodata section, 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 met .rodata et .text dans le même segment, qui a une autorisation d’exécution mais pas d’écriture Ceci peut être observé avec:

readelf -l a.out

qui contient:

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

Les réponses ci-dessus doivent avoir répondu à votre question. Mais je voudrais vous suggérer de lire le paragraphe "Embryonic C" in Le développement du langage C écrit par Sir Dennis Ritchie.

1
abcoep

Pour cette ligne: char amessage [] = "le moment est venu";

le compilateur évaluera les utilisations de amessage en tant que pointeur vers le début du tableau contenant les caractères "now is the time". Le compilateur alloue de la mémoire pour "maintenant, c'est l'heure" et l'initialise avec la chaîne "maintenant, c'est l'heure". Vous savez où ce message est stocké car message signifie toujours le début de ce message. Il n'est peut-être pas donné une nouvelle valeur à amessage. Ce n'est pas une variable, c'est le nom de la chaîne "maintenant, c'est l'heure".

Cette ligne: char * pmessage = "le moment est venu";

déclare une variable, pmessage qui est initialisée (à partir d'une valeur initiale) de l'adresse de début de la chaîne "now is the time". Contrairement à amessage, pmessage peut se voir attribuer une nouvelle valeur. Dans ce cas, comme dans le cas précédent, le compilateur stocke également "maintenant l'heure est" ailleurs dans la mémoire. Par exemple, pmessage désignera le 'i' qui commence "est le temps". pmessage = pmessage + 4;

0
Ron Jacobs