Post-Inkrement auf einem dereferenzierten Zeiger?

Lesezeit: 11 Minuten

Benutzeravatar von ChristopheD
Christophe D

Beim Versuch, das Verhalten von Zeigern in C zu verstehen, war ich ein wenig überrascht von dem Folgenden (Beispielcode unten):

#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         */
}

Ausgabe:

64
@ 0xbf84c6b0

63
@ 0xbf84c6b0

Was genau bedeutet die *our_var_ptr++ Anweisung in der zweiten Funktion (add_one_v2) tun, da es eindeutig nicht dasselbe ist wie *our_var_ptr = *our_var_ptr +1?

  • Mögliche Duplikate von Zeigerausdrücken: *ptr++, *++ptr und ++*ptr

    – Bernhard Barker

    25. Mai 2017 um 12:10 Uhr

Benutzeravatar von Mark Ransom
Mark Lösegeld

Dies ist einer dieser kleinen Fallstricke, die C und C++ so viel Spaß machen. Wenn Sie Ihr Gehirn biegen wollen, finden Sie dieses heraus:

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

Es ist eine String-Kopie. Die Zeiger werden solange inkrementiert, bis ein Zeichen mit dem Wert Null kopiert wird. Sobald Sie wissen, warum dieser Trick funktioniert, werden Sie nie wieder vergessen, wie ++ mit Zeigern funktioniert.

PS Sie können die Operatorreihenfolge immer mit Klammern überschreiben. Das Folgende erhöht den Wert, auf den gezeigt wird, und nicht den Zeiger selbst:

(*our_var_ptr)++;

  • Dieses Beispiel, zusammen mit einigen anderen „Schau ma, keine Hände!“ Codeschnipsel, taucht in dieser Bastion des C-Wissens auf, Die Programmiersprache C.

    – Benjamin Barenblat

    11. Dezember 2013 um 21:44 Uhr


Benutzeravatar von hbw
hbw

Aufgrund von Operator-Vorrangregeln und der Tatsache, dass ++ ist ein Postfix-Operator, add_one_v2() dereferenziert den Zeiger, aber die ++ wird tatsächlich angewendet zum Zeiger selbst. Denken Sie jedoch daran, dass C immer Pass-by-Value verwendet: add_one_v2() erhöht seine lokale Kopie des Zeigers, was keinerlei Auswirkung auf den an dieser Adresse gespeicherten Wert hat.

Testweise ersetzen add_one_v2() mit diesen Codebits und sehen Sie, wie die Ausgabe beeinflusst wird:

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

  • Das mit der Reihenfolge stimmt nicht. In add_one_v2 wird ++ auf den Zeiger angewendet, nicht auf die Dereferenzierung. Da es sich jedoch um ein Post-Inkrement handelt, erfolgt die Dereferenzierung VOR dem Inkrement.

    – Torlack

    13. Mai 2009 um 19:12 Uhr

  • Sprechen Sie über das ursprüngliche add_one_v2 oder eines meiner Beispiele mit Klammern?

    – hbw

    13. Mai 2009 um 19:14 Uhr

  • Ich rede vom Original. Ich versuche nur darauf hinzuweisen, dass Ihre Aussage “erhöht den Zeiger und dereferenziert dann …” falsch ist.

    – Torlack

    13. Mai 2009 um 19:16 Uhr

  • Danke für die schnelle Antwort (und für den informativen Kommentar Torlack!). ++*our_var_ptr scheint genauso zu ‘funktionieren’ wie add_one_v1. Eine Sache verstehe ich allerdings nicht: wenn die Funktionen nur mit lokalen Kopien der Pointer arbeiten –> wie erreichen sie es, den in main ausgegebenen Wert zu beeinflussen? Oder interpretiere ich diese Aussage falsch?

    – Christoph D

    13. Mai 2009 um 19:19 Uhr

  • Der Zeiger selbst ist eine lokale Kopie, der Wert, auf den er zeigt, jedoch nicht.

    – Torlack

    13. Mai 2009 um 19:20 Uhr

Benutzeravatar des BIBD
BIBD

OK,

*our_var_ptr++;

es funktioniert so:

  1. Die Dereferenzierung erfolgt zuerst und gibt Ihnen den durch angegebenen Speicherort our_var_ptr (die 63 enthält).
  2. Dann wird der Ausdruck ausgewertet, das Ergebnis von 63 ist immer noch 63.
  3. Das Ergebnis wird weggeworfen (Sie machen nichts damit).
  4. our_var_ptr wird dann NACH der Auswertung inkrementiert. Es ändert, wohin der Zeiger zeigt, nicht, worauf er zeigt.

Es ist effektiv dasselbe wie dies zu tun:

*our_var_ptr;
our_var_ptr = our_var_ptr + 1; 

Sinn ergeben? Die Antwort von Mark Ransom hat ein gutes Beispiel dafür, außer dass er das Ergebnis tatsächlich verwendet.

  • GCC akzeptiert nicht: *our_var_ptr++; Es akzeptiert als: *(our_var_ptr)++

    – a.saurabh

    18. Oktober 2014 um 17:53 Uhr


  • Auch IMO sollte es *our_var_ptr sein; *unser_var_ptr = *unser_var_ptr + 1;

    – a.saurabh

    18. Oktober 2014 um 17:55 Uhr


  • @a.saurabh Ich garantiere, dass GCC akzeptieren wird *our_var_ptr++. Es könnte eine Warnung ausgeben, aber wenn Sie das wegnehmen würden, würden Sie SOOOO viel Zeug kaputt machen. Mit *(our_var_ptr)++ das Innere der Klammer wird zuerst ausgewertet our_var_ptr; was gleich ist *our_var_ptr++.

    – BIBD

    16. April 2015 um 14:54 Uhr

  • @a.saurabh *our_var_ptr = *our_var_ptr + 1; ist der Effekt, den das Originalplakat wollte; nicht das, was ich zu erklären versuchte. our_var_ptr = our_var_ptr + 1; ist der Grund dafür while (*dst++ = *src++) ; funktioniert als String-Kopie in C.

    – BIBD

    16. April 2015 um 14:58 Uhr

Viel Verwirrung hier, deshalb hier ein modifiziertes Testprogramm, um klar zu machen, was passiert (oder zumindest klaräh):

#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)); 
}

mit der resultierenden Ausgabe:

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

Vier Dinge zu beachten:

  1. Änderungen an der lokalen Kopie des Zeigers sind nicht spiegelt sich im aufrufenden Zeiger wider.
  2. Änderungen am Ziel des lokalen Zeigers wirken sich auf das Ziel des aufrufenden Zeigers aus (zumindest bis der Zielzeiger aktualisiert wird).
  3. der Wert zeigte auf in add_one_v2 ist nicht inkrementiert und der folgende Wert auch nicht, der Zeiger jedoch schon
  4. das Inkrement des Zeigers in add_one_v2 das passiert nach die Dereferenzierung

Wieso den?

  • Da ++ bindet fester als * (als Dereferenzierung oder Multiplikation) also das Inkrement in add_one_v2 gilt für den Zeiger und nicht für das, worauf er zeigt.
  • Post Zuwächse passieren nach die Auswertung des Terms, also bekommt die Dereferenzierung den ersten Wert im Array (Element 0).

Wie die anderen bereits betont haben, führt die Operatorpräzedenz dazu, dass der Ausdruck in der v2-Funktion als angesehen wird *(our_var_ptr++).

Da dies jedoch ein Post-Inkrement-Operator ist, ist es nicht ganz richtig zu sagen, dass er den Zeiger inkrementiert und ihn dann dereferenziert. Wenn dies wahr wäre, würden Sie meiner Meinung nach nicht 63 als Ausgabe erhalten, da der Wert an der nächsten Speicherstelle zurückgegeben würde. Eigentlich glaube ich, dass die logische Abfolge der Operationen ist:

  1. Speichern Sie den aktuellen Wert des Zeigers
  2. Erhöhen Sie den Zeiger
  3. Dereferenzieren Sie den in Schritt 1 gespeicherten Zeigerwert

Wie htw erklärt hat, sehen Sie die Änderung des Zeigerwerts nicht, da er als Wert an die Funktion übergeben wird.

  • Danke für diese schöne Erklärung!

    – Christoph D

    13. Mai 2009 um 19:28 Uhr

  • imo, das ist verrückt. Wenn ich mit frischen Augen zur Sprache komme (bin ich nicht), würde ich sehr erwarten, dass der inkrementierte Wert dereferenziert wird. Danke für die Schilderung der Situation hier.

    – orion elenzil

    4. Mai um 18:03 Uhr

Benutzeravatar von marc_s
marc_s

Wenn Sie keine Klammern verwenden, um die Reihenfolge der Operationen anzugeben, haben sowohl Präfix- als auch Postfix-Inkremente Vorrang vor Referenz und Dereferenzierung. Präfixinkrement und Postfixinkrement sind jedoch unterschiedliche Operationen. In ++x nimmt der Operator eine Referenz auf Ihre Variable, fügt ihr eine hinzu und gibt sie als Wert zurück. In x++ inkrementiert der Operator Ihre Variable, gibt aber ihren alten Wert zurück. Sie verhalten sich ungefähr so ​​(stellen Sie sich vor, sie sind als Methoden in Ihrer Klasse deklariert):

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

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

(Beachten Sie, dass am Postfix-Inkrement eine Kopie beteiligt ist, was es weniger effizient macht. Aus diesem Grund sollten Sie in Schleifen ++i anstelle von i++ bevorzugen, auch wenn die meisten Compiler dies heutzutage automatisch für Sie tun.)

Wie Sie sehen können, wird das Postfix-Inkrement zuerst verarbeitet, aber aufgrund seines Verhaltens dereferenzieren Sie den vorherigen Wert des Zeigers.

Hier ist ein Beispiel:

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

In der zweiten Zeile wird der Zeiger x vor der Dereferenzierung erhöht, aber die Dereferenzierung erfolgt über den alten Wert von x (der eine die Anschrift zurückgegeben durch das Postfix-Inkrement). Also wird y mit ‘a’ und z mit ‘c’ initialisiert. Aber wenn du es so machst:

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

Hier wird x dereferenziert und die Wert zeigt (‘a’) wird inkrementiert (zu ‘b’). Da das Postfix-Inkrement den alten Wert zurückgibt, wird y weiterhin mit ‘a’ initialisiert. Und da sich der Zeiger nicht geändert hat, wird z mit dem neuen Wert ‘b’ initialisiert.

Lassen Sie uns nun die Präfixfälle überprüfen:

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

Hier erfolgt die Dereferenzierung auf dem inkrementierten Wert von x (der sofort vom Präfix-Inkrementoperator zurückgegeben wird), sodass sowohl y als auch z mit ‘c’ initialisiert werden. Um ein anderes Verhalten zu erhalten, können Sie die Reihenfolge der Operatoren ändern:

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

Hier stellen Sie sicher, dass Sie zuerst den Inhalt von x inkrementieren und sich der Wert des Zeigers nie ändert, sodass y und z mit ‘b’ zugewiesen werden. In dem strcpy Funktion (in anderer Antwort erwähnt), wird das Inkrement auch zuerst durchgeführt:

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

Bei jeder Iteration wird src++ zuerst verarbeitet und gibt als Postfix-Inkrement den alten Wert von src zurück. Dann wird der alte Wert von src (der ein Zeiger ist) dereferenziert, um dem zugewiesen zu werden, was sich auf der linken Seite des Zuweisungsoperators befindet. Der dst wird dann inkrementiert und sein alter Wert wird dereferenziert, um ein lvalue zu werden und den alten src-Wert zu erhalten. Deshalb dst[0] = Quelle[0]dst[1] = Quelle[1] usw., bis *dst mit 0 belegt wird, wodurch die Schleife unterbrochen wird.

Nachtrag:

Der gesamte Code in dieser Antwort wurde mit der Sprache C getestet. In C++ werden Sie wahrscheinlich nicht in der Lage sein, den Zeiger aufzulisten. Wenn Sie also die Beispiele in C++ testen möchten, sollten Sie zuerst ein Array initialisieren und es dann zu einem Zeiger degradieren:

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

  • Danke für diese schöne Erklärung!

    – Christoph D

    13. Mai 2009 um 19:28 Uhr

  • imo, das ist verrückt. Wenn ich mit frischen Augen zur Sprache komme (bin ich nicht), würde ich sehr erwarten, dass der inkrementierte Wert dereferenziert wird. Danke für die Schilderung der Situation hier.

    – orion elenzil

    4. Mai um 18:03 Uhr

Benutzeravatar von gbjbaanb
gbjbaanb

our_var_ptr ist ein Zeiger auf einen Speicher. dh es ist die Speicherzelle, in der die Daten gespeichert werden. (n in diesem Fall 4 Bytes im Binärformat eines int).

*our_var_ptr ist der dereferenzierte Zeiger – er geht zu der Stelle, auf die der Zeiger ‘zeigt’.

++ erhöht einen Wert.

Also. *our_var_ptr = *our_var_ptr+1 dereferenziert den Zeiger und fügt dem Wert an dieser Stelle eins hinzu.

Fügen Sie nun die Operatorpriorität hinzu – lesen Sie es als (*our_var_ptr) = (*our_var_ptr)+1 und Sie sehen, dass die Dereferenzierung zuerst erfolgt, also nehmen Sie den Wert und erhöhen ihn.

In Ihrem anderen Beispiel hat der ++-Operator eine niedrigere Priorität als der *, also nimmt er den übergebenen Zeiger, fügt ihm einen hinzu (so dass er jetzt auf Müll zeigt) und kehrt dann zurück. (Denken Sie daran, dass Werte in C immer als Wert übergeben werden. Wenn also die Funktion den ursprünglichen Testvar-Zeiger zurückgibt, bleibt er gleich, Sie haben nur den Zeiger innerhalb der Funktion geändert).

Mein Rat, wenn Sie Dereferenzierung (oder irgendetwas anderes) verwenden, verwenden Sie Klammern, um Ihre Entscheidung deutlich zu machen. Versuchen Sie nicht, sich an die Vorrangregeln zu erinnern, da Sie nur eines Tages eine andere Sprache verwenden werden, in der sie etwas anders sind, und Sie werden verwirrt sein. Oder alt und am Ende vergessen, was einen höheren Vorrang hat (wie ich es mit * und -> mache).

  • Danke für die Antwort, ich denke, der Rat, es durch die Verwendung von Klammern deutlicher zu machen, ist solide!

    – Christoph D

    13. Mai 2009 um 19:30 Uhr

1415910cookie-checkPost-Inkrement auf einem dereferenzierten Zeiger?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy