71 votes

Post-incrément sur un pointeur déréférencé ?

En essayant de comprendre le comportement des pointeurs en C, j'ai été un peu surpris par ce qui suit (exemple de code ci-dessous) :

#include <stdio.h>

void add_one_v1(int *our_var_ptr)
{
    *our_var_ptr = *our_var_ptr +1;
}

void add_one_v2(int *our_var_ptr)
{
    *our_var_ptr++;
}

int main()
{
    int testvar;

    testvar = 63;
    add_one_v1(&(testvar));         /* Try first version of the function */
    printf("%d\n", testvar);        /* Prints out 64                     */
    printf("@ %p\n\n", &(testvar));

    testvar = 63;
    add_one_v2(&(testvar));         /* Try first version of the function */
    printf("%d\n", testvar);        /* Prints 63 ?                       */
    printf("@ %p\n", &(testvar));   /* Address remains identical         */
}

Sortie :

64
@ 0xbf84c6b0

63
@ 0xbf84c6b0

Que fait exactement le *our_var_ptr++ dans la deuxième fonction ( add_one_v2 ), puisque ce n'est clairement pas la même chose que *our_var_ptr = *our_var_ptr +1 ?

3 votes

102voto

Mark Ransom Points 132545

C'est l'un de ces petits pièges qui rendent le C et le C++ si amusants. Si vous voulez faire travailler votre cerveau, résolvez ce problème :

while (*dst++ = *src++) ;

C'est une copie de chaîne de caractères. Les pointeurs sont incrémentés jusqu'à ce qu'un caractère de valeur zéro soit copié. Une fois que vous saurez pourquoi cette astuce fonctionne, vous n'oublierez plus jamais comment ++ fonctionne sur les pointeurs.

P.S. Vous pouvez toujours modifier l'ordre des opérateurs avec des parenthèses. L'exemple suivant incrémentera la valeur pointée, plutôt que le pointeur lui-même :

(*our_var_ptr)++;

0 votes

Merci pour ce très bel exemple !

7 votes

Cet exemple, ainsi que plusieurs autres extraits de code du type "Regarde maman, pas de mains !", apparaît dans ce bastion de la connaissance du C, Le langage de programmation C .

53voto

htw Points 10312

En raison des règles de précédence des opérateurs et du fait que ++ est un opérateur postfixe, add_one_v2() déréférence le pointeur, mais l'option ++ est effectivement appliqué au pointeur lui-même . Cependant, n'oubliez pas que le C utilise toujours le principe du "pass-by-value" : add_one_v2() incrémente son copie locale du pointeur, ce qui n'aura aucun effet sur la valeur stockée à cette adresse.

À titre d'essai, remplacez add_one_v2() avec ces bouts de code et voir comment la sortie est affectée :

void add_one_v2(int *our_var_ptr)
{
    (*our_var_ptr)++;  // Now stores 64
}

void add_one_v2(int *our_var_ptr)
{
    *(our_var_ptr++);  // Increments the pointer, but this is a local
                       // copy of the pointer, so it doesn't do anything.
}

10 votes

Ce n'est pas vrai en ce qui concerne l'ordre des opérations. Dans add_one_v2, le ++ est appliqué au pointeur, pas à la déréférence. Cependant, comme il s'agit d'un postincrément, la déréférence se produit AVANT l'incrément.

0 votes

Parlez-vous de l'add_one_v2 original, ou de l'un de mes exemples entre parenthèses ?

0 votes

Je parle de l'original. J'essaie juste de souligner que votre affirmation "incrémente le pointeur puis le déréférence..." est incorrecte.

53voto

CodeSlave Points 7133

OK,

*our_var_ptr++;

ça marche comme ça :

  1. La déréférence se produit d'abord, vous donnant l'emplacement mémoire indiqué par our_var_ptr (qui en contient 63).
  2. Puis l'expression est évaluée, le résultat de 63 est toujours 63.
  3. Le résultat est jeté (vous n'en faites rien).
  4. our_var_ptr est ensuite incrémenté APRÈS l'évaluation. Il s'agit de changer l'endroit où le pointeur pointe, pas ce qu'il pointe.

C'est effectivement la même chose que de faire ceci :

*our_var_ptr;
our_var_ptr = our_var_ptr + 1; 

Cela a-t-il un sens ? La réponse de Mark Ransom contient un bon exemple de cela, sauf qu'il utilise réellement le résultat.

0 votes

GCC n'accepte pas : *notre_var_ptr++ ; Il accepte comme : *(notre_var_ptr)++

0 votes

De plus, IMO, cela devrait être *notre_var_ptr ; *notre_var_ptr = *notre_var_ptr + 1 ;

0 votes

@a.saurabh je garantis que le GCC acceptera *our_var_ptr++ . Il peut lancer un avertissement, mais si vous l'enlevez, vous allez casser BEAUCOUP de choses. Avec *(our_var_ptr)++ l'intérieur de la parenthèse est évalué en premier lieu pour our_var_ptr ce qui équivaut à *our_var_ptr++ .

8voto

dmckee Points 50318

Beaucoup de confusion ici, donc voici un programme de test modifié pour rendre ce qui se passe clair (ou au moins clair er ):

#include <stdio.h>

void add_one_v1(int *p){
  printf("v1: pre:   p = %p\n",p);
  printf("v1: pre:  *p = %d\n",*p);
    *p = *p + 1;
  printf("v1: post:  p = %p\n",p);
  printf("v1: post: *p = %d\n",*p);
}

void add_one_v2(int *p)
{
  printf("v2: pre:   p = %p\n",p);
  printf("v2: pre:  *p = %d\n",*p);
    int q = *p++;
  printf("v2: post:   p = %p\n",p);
  printf("v2: post:  *p = %d\n",*p);
  printf("v2: post:   q = %d\n",q);
}

int main()
{
  int ary[2] = {63, -63};
  int *ptr = ary;

    add_one_v1(ptr);         
    printf("@ %p\n", ptr);
    printf("%d\n", *(ptr));  
    printf("%d\n\n", *(ptr+1)); 

    add_one_v2(ptr);
    printf("@ %p\n", ptr);
    printf("%d\n", *ptr);
    printf("%d\n", *(ptr+1)); 
}

avec la sortie qui en résulte :

v1: pre:   p = 0xbfffecb4
v1: pre:  *p = 63
v1: post:  p = 0xbfffecb4
v1: post: *p = 64
@ 0xbfffecb4
64
-63

v2: pre:   p = 0xbfffecb4
v2: pre:  *p = 64
v2: post:  p = 0xbfffecb8
v2: post: *p = -63
v2: post:  q = 64

@ 0xbfffecb4
64
-63

Quatre choses à noter :

  1. les modifications de la copie locale du pointeur sont no reflétée dans le pointeur d'appel.
  2. les modifications de la cible du pointeur local affectent la cible du pointeur appelant (au moins jusqu'à ce que le pointeur cible soit mis à jour).
  3. la valeur pointée dans add_one_v2 est no incrémenté et la valeur suivante non plus, mais le pointeur est
  4. l'incrément du pointeur dans add_one_v2 se produit après le déréférencement

Pourquoi ?

  • Parce que ++ se lie plus étroitement que * (comme la déréférence ou la multiplication) de sorte que l'incrément en add_one_v2 s'applique au pointeur, et non à ce qu'il pointe.
  • poste les incréments se produisent après l'évaluation du terme, de sorte que la déréférence obtient la première valeur du tableau (élément 0).

0 votes

Merci d'avoir pris le temps d'écrire cette réponse, c'est ce genre de choses qui fait de StackOverflow un environnement si formidable ! J'ai eu plus de réponses perspicaces ici en une heure que je n'en aurais trouvé dans le même temps dans mes tutoriels C minables ;)

0 votes

Merci ! Je pense que c'est le point le plus important - " Parce que ++ se lie plus étroitement que * ".

0 votes

De plus, les gens devraient prendre note de ceci : int q = *p++ ; N'EST PAS ÉGAL À *p++ ; *p++ aura toujours la "première valeur" seulement. alors que dans q c'est la "deuxième valeur".( Je pense que vous l'avez mentionné dans le deuxième point)

7voto

Dave Costa Points 25282

Comme les autres l'ont souligné, la précédence des opérateurs fait que l'expression dans la fonction v2 est vue comme *(our_var_ptr++) .

Cependant, comme il s'agit d'un opérateur post-incrément, il n'est pas tout à fait exact de dire qu'il incrémente le pointeur puis le déréférence. Si c'était vrai, je ne pense pas que vous obtiendriez 63 en sortie, puisque l'opérateur renverrait la valeur de l'emplacement mémoire suivant. En fait, je crois que la séquence logique des opérations est la suivante :

  1. Sauvegarde de la valeur actuelle du pointeur
  2. Incrémenter le pointeur
  3. Déréférencement de la valeur du pointeur sauvegardée à l'étape 1

Comme l'a expliqué htw, vous ne voyez pas le changement de la valeur du pointeur parce qu'il est transmis par valeur à la fonction.

0 votes

Merci pour cette belle explication !

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X