web-dev-qa-db-fra.com

Post-incrémentation 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         */
}

Production:

64
@ 0xbf84c6b0

63
@ 0xbf84c6b0

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

51
ChristopheD

En raison des règles de priorité des opérateurs et du fait que ++ Est un opérateur de suffixe, add_one_v2() ne déréférence le pointeur, mais le ++ Est en fait appliqué à la pointeur lui-même. Cependant, rappelez-vous que C utilise toujours une valeur de passage: add_one_v2() incrémente son copie locale du pointeur, ce qui n'aura aucun effet sur la valeur stockée à cette adresse.

À titre de test, remplacez add_one_v2() par ces bits de code et voyez 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.
}
40
hbw

C'est l'un de ces petits trucs qui rendent le C et C++ tellement amusant. Si vous voulez plier votre cerveau, découvrez celui-ci:

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

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

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

(*our_var_ptr)++;
61
Mark Ransom

D'ACCORD,

*our_var_ptr++;

cela fonctionne comme ceci:

  1. Le déréférencement se produit en premier, vous donnant l'emplacement mémoire indiqué par our_var_ptr (qui en contient 63).
  2. Ensuite, l'expression est évaluée, le résultat de 63 est toujours de 63.
  3. Le résultat est jeté (vous ne faites rien avec).
  4. our_var_ptr est ensuite incrémenté APRÈS l'évaluation. Cela change où le pointeur pointe, pas ce vers quoi il pointe.

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

*our_var_ptr;
our_var_ptr = our_var_ptr + 1; 

Ça a du sens? La réponse de Mark Ransom en est un bon exemple, sauf qu'il utilise réellement le résultat.

33
BIBD

Beaucoup de confusion ici, voici donc 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 résultante:

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 apportées à la copie locale du pointeur sont pas reflétées dans le pointeur appelant.
  2. les modifications apportées à 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 indiquée dans add_one_v2 est pas incrémenté et la valeur suivante non plus, mais le pointeur est
  4. l'incrément du pointeur dans add_one_v2 arrive après la déréférence

Pourquoi?

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

Comme les autres l'ont souligné, la priorité 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 de post-incrémentation, il n'est pas tout à fait vrai de dire qu'il incrémente le pointeur puis le déréférence. Si cela était vrai, je ne pense pas que vous obtiendrez 63 comme sortie, car il retournerait la valeur dans le prochain emplacement de mémoire. En fait, je crois que la séquence logique des opérations est la suivante:

  1. Enregistrer la valeur actuelle du pointeur
  2. Incrémenter le pointeur
  3. Déréférencer la valeur du pointeur enregistrée à l'étape 1

Comme expliqué par htw, vous ne voyez pas la modification de la valeur du pointeur car elle est transmise par valeur à la fonction.

6
Dave Costa

Si vous n'utilisez pas de parenthèses pour spécifier l'ordre des opérations, les incréments de préfixe et de postfixe ont priorité sur la référence et la déréférence. Cependant, l'incrément de préfixe et l'incrément de postfix sont des opérations différentes. Dans ++ x, l'opérateur prend une référence à votre variable, en ajoute une et la renvoie par valeur. Dans x ++, l'opérateur incrémente votre variable, mais renvoie son ancienne valeur. Ils se comportent un peu comme ceci (imaginez qu'ils sont déclarés comme des méthodes dans votre classe):

//prefix increment (++x)
auto operator++()
{
    (*this) = (*this) + 1;
    return (*this);
}

//postfix increment (x++)
auto operator++(int) //unfortunatelly, the "int" is how they differentiate
{
    auto temp = (*this);
    (*this) = (*this) + 1; //same as ++(*this);
    return temp;
}

(Notez qu'il y a une copie impliquée dans l'incrément postfix, ce qui la rend moins efficace. C'est la raison pour laquelle vous devriez préférez ++ i au lieu d'i ++ dans les boucles, même si la plupart des compilateurs le font automatiquement pour vous ces jours-ci.)

Comme vous pouvez le voir, l'incrément postfix est traité en premier, mais, en raison de la façon dont il se comporte, vous déréférencerez la valeur précédente du pointeur.

Voici un exemple:

char * x = {'a', 'c'};
char   y = *x++; //same as *(x++);
char   z = *x;

Dans la deuxième ligne, le pointeur x sera incrémenté avant la déréférence, mais la déréférence se produira sur l'ancienne valeur de x (qui est une adresse retournée par l'incrément postfix). Donc, y sera initialisé avec "a" et z avec "c". Mais si vous le faites comme ceci:

char * x = {'a', 'c'};
char   y = (*x)++;
char   z = *x;

Ici, x sera déréférencé et la valeur pointée par lui ('a') sera incrémentée (à 'b'). Puisque l'incrément de suffixe renvoie l'ancienne valeur, y sera toujours initialisé avec 'a'. Et comme le pointeur n'a pas changé, z sera initialisé avec la nouvelle valeur 'b'.

Voyons maintenant les cas de préfixe:

char * x = {'a', 'c'};
char   y = *++x; //same as *(++x)
char   z = *x;

Ici, la déréférence se produira sur la valeur incrémentée de x (qui est immédiatement retournée par l'opérateur d'incrémentation du préfixe), donc y et z seront initialisés avec 'c'. Pour obtenir un comportement différent, vous pouvez modifier l'ordre des opérateurs:

char * x = {'a', 'c'};
char   y = ++*x; //same as ++(*x)
char   z = *x;

Ici, vous vous assurez que vous incrémentez le contenu de x en premier et que la valeur du pointeur ne change jamais, donc y et z seront affectés avec 'b'. Dans la fonction strcpy (mentionnée dans une autre réponse), l'incrémentation est également effectuée en premier:

char * strcpy(char * dst, char * src)
{
    char * aux = dst;
    while(*dst++ = *src++);
    return aux;
}

À chaque itération, src ++ est traité en premier et, étant un incrément postfixé, il renvoie l'ancienne valeur de src. Ensuite, l'ancienne valeur de src (qui est un pointeur) est déréférencée pour être affectée à tout ce qui se trouve dans le côté gauche de l'opérateur d'affectation. Le dst est ensuite incrémenté et son ancienne valeur est déréférencée pour devenir une valeur l et recevoir l'ancienne valeur src. C'est pourquoi dst [0] = src [0], dst [1] = src [1] etc., jusqu'à ce que * dst soit affecté avec 0, rompant la boucle.

Addendum:

Tout le code de cette réponse a été testé avec le langage C. En C++, vous ne pourrez probablement pas lister-initialiser le pointeur. Donc, si vous voulez tester les exemples en C++, vous devez d'abord initialiser un tableau puis le dégrader en un pointeur:

char w[] = {'a', 'c'};
char * x = w;
char   y = *x++; //or the other cases
char   z = *x;
3
Jango

our_var_ptr est un pointeur vers une certaine mémoire. c'est-à-dire que c'est la cellule mémoire où les données sont stockées. (dans ce cas 4 octets au format binaire d'un int).

* our_var_ptr est le pointeur déréférencé - il va à l'emplacement où le pointeur 'pointe'.

++ incrémente une valeur.

alors. *our_var_ptr = *our_var_ptr+1 déréférence le pointeur et en ajoute un à la valeur à cet emplacement.

Ajoutez maintenant la priorité de l'opérateur - lisez-la comme (*our_var_ptr) = (*our_var_ptr)+1 et vous voyez que la déréférence se produit en premier, vous prenez donc la valeur et l'incrémentez.

Dans votre autre exemple, l'opérateur ++ a une priorité plus faible que le *, il prend donc le pointeur que vous avez passé, en a ajouté un (il pointe donc sur les ordures maintenant), puis revient. (rappelez-vous que les valeurs sont toujours transmises par valeur en C, donc lorsque la fonction retourne le pointeur testvar d'origine reste le même, vous avez uniquement changé le pointeur à l'intérieur de la fonction).

Mon conseil, lorsque vous utilisez le déréférencement (ou toute autre chose), utilisez des crochets pour rendre votre décision explicite. N'essayez pas de vous souvenir des règles de priorité car vous finirez par utiliser une autre langue un jour qui les a légèrement différentes et vous serez confus. Ou vieux et finissent par oublier ce qui a une priorité plus élevée (comme je le fais avec * et ->).

3
gbjbaanb
    uint32_t* test;
    test = &__STACK_TOP;


    for (i = 0; i < 10; i++) {
        *test++ = 0x5A5A5A5A;
    }

    //same as above

    for (i = 0; i < 10; i++) {
        *test = 0x5A5A5A5A;
        test++;
    }

Parce que test est un pointeur, test ++ (c'est sans le déréférencer) incrémentera le pointeur (il incrémente la valeur de test qui se trouve être l'adresse (de destination) de ce qui est pointé). Parce que la destination est de type uint32_t, test ++ incrémentera de 4 octets, et si la destination était par exemple un tableau de ce type, alors test pointe désormais sur l'élément suivant. Lorsque vous effectuez ce type de manipulations, vous devez parfois lancer le pointeur en premier pour obtenir le décalage de mémoire souhaité.

        ((unsigned char*) test)++;

Cela incrémentera l'adresse d'un octet seulement;)

1
pgibbons

L'opérateur '++' a une priorité plus élevée que l'opérateur '*', ce qui signifie que l'adresse du pointeur sera incrémentée avant d'être déréférencée.

L'opérateur '+' a cependant une priorité inférieure à '*'.

1
Martin Cote

Je vais essayer de répondre à cela sous un angle un peu différent ... Étape 1 Regardons les opérateurs et les opérandes: Dans ce cas, c'est l'opérande, et vous avez deux opérateurs, dans ce cas * pour le déréférencement et ++ pour incrément. L'étape 2 qui a la priorité la plus élevée ++ a la priorité la plus élevée sur * Étape 3 Où est ++, c'est à droite ce qui signifie POST Increment Dans ce cas , le compilateur prend une 'note mentale' pour effectuer l'incrémentation APRÈS c'est fait avec tous les autres opérateurs ... note si c'était * ++ p alors il le fera AVANT donc dans ce cas, cela équivaut à prendre deux du registre du processeur, l'un contiendra la valeur du déréférencé * p et l'autre contiendra la valeur du p ++ incrémenté, la raison dans ce cas il y en a deux, c'est l'activité POST ... C'est là que dans ce cas c'est délicat, et ça ressemble à une contradiction. On peut s'attendre à ce que le ++ ait priorité sur le *, ce qu'il fait, seulement que le POST signifie qu'il ne sera appliqué qu'après TOUS les autres opérandes, AVANT le prochain jeton ';' ...

1
G. Simsic

D'après K&R, page 105: "La valeur de * t ++ est le caractère vers lequel t pointe avant que t ne soit incrémenté".

0
ItM

Les images valent environ mille mots (donner ou prendre environ un million) ... et les symboles peuvent être des images (et vice versa.)

Donc, pour ceux d'entre nous qui cherchent tl;drc'est (consommation de données optimisée) tout en souhaitant toujours un encodage "(principalement) sans perte", les images vectorielles/images/illustrations/démos sont primordiales.

En d'autres termes, ignorez simplement mes 2 dernières déclarations et voyez ci-dessous.

Valid forms:


* a ++ ≣ * (a ++)
≣ (a ++) [0] ≣ a ++ [0]
≣ 0 [a ++] // Ne vous avisez pas d'utiliser cela ("à des fins éducatives uniquement")
// Ceux-ci produisent des effets (secondaires) équivalents;
≡ val = * a, ++ a, val
≡ ptr = a, ++ a, * ptr
≡ * (ptr = a, ++ a, ptr)

* ++ a ≣ * (++ a)
≣ * (a + = 1) ≣ * (a = a + 1)
≣ (++ a) [0] ≣ (a + = 1) [0] ≣ (a = a + 1) [0] // () sont des nécessaire
≣ 0 [++ a] // 0 [a + = 1], etc ... "uniquement à des fins éducatives"
// Ceux-ci produisent des effets (secondaires) équivalents:
≡ ++ a, * a
≡ a + = 1, * a
≡ a = a + 1, * a

++ * a ≣ ++ (* a)
≣ * a + = 1
≣ * a = * a + 1
≣ ++ a [0] ≣ ++ (a [0])
≣ ++ 0 [a] // RESTEZ LOIN

(* a) ++ // Notez que cela ne renvoie PAS de pointeur;
// L'emplacement `a` pointe vers ne change pas
// (c'est-à-dire que la 'valeur' ​​de `a` est inchangée)
≡ val = * a, ++ * a, val

Notes

/ * L'opérateur d'indirection/déférence doit précéder l'identifiant cible: * /
a ++ *; 
a * ++; 
++ a *; 

0
YenForYang