Frage

Der Versuch, das Verhalten von Zeigern in C zu verstehen, war ich ein wenig überrascht von dem folgenden (Beispiel-Code 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 macht die *our_var_ptr++ Anweisung in der zweiten Funktion (add_one_v2) tun, da es eindeutig nicht die gleichen wie *our_var_ptr = *our_var_ptr +1 ist?

War es hilfreich?

Lösung

Durch Betreiber Vorrangregeln und der Tatsache, dass ++ ein Postfix-Operator ist, add_one_v2() macht den Zeiger dereferenzieren, aber die ++ tatsächlich angewendet wird auf den Zeiger selbst . Beachten Sie jedoch, dass C verwendet immer pass-by-Wert. add_one_v2() wird Erhöhen seiner lokale Kopie des Zeigers, die keinerlei Auswirkungen auf den Wert an dieser Adresse gespeichert haben

Als Test ersetzen add_one_v2() mit dieser Bit-Code und sehen, wie der Ausgang betroffen:

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

Andere Tipps

Dies ist einer jener kleinen Gotcha ist, die C und C ++ so viel Spaß machen. Wenn Sie Ihr Gehirn biegen wollen, herauszufinden, diese:

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

Es ist eine Zeichenfolge zu kopieren. Die Zeiger halten erhöht zu werden, bis ein Zeichen mit einem Wert von Null kopiert wird. Sobald Sie wissen, warum dieser Trick funktioniert, werden Sie nie vergessen, wie ++ wieder auf Zeiger funktioniert.

P. S. Man kann immer den Operator, um mit Klammern außer Kraft setzen. Im Folgenden wird der Wert bei wies erhöhen, anstatt die Zeiger selbst:

(*our_var_ptr)++;

OK,

*our_var_ptr++;

Es funktioniert wie folgt:

  1. Die dereferenzieren geschieht zuerst, können Sie den Speicherplatz durch our_var_ptr angegeben geben (die 63 enthält).
  2. Dann wird der Ausdruck ausgewertet wird, ist das Ergebnis von 63 noch 63.
  3. Das Ergebnis weg geworfen wird (Sie sind mit ihm nichts zu tun).
  4. our_var_ptr wird dann nach der Auswertung erhöht. Es ändert sich, wenn der Zeiger zeigt auf, nicht das, was es zeigt auf.

Es ist effektiv das gleiche wie dies zu tun:

*our_var_ptr;
our_var_ptr = our_var_ptr + 1; 

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

Viel Verwirrung hier, so ist hier eine modifizierte Testprogramm zu machen, was klar passiert (oder zumindest klar 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)); 
}

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 in dem Aufruf Zeiger.
  2. Änderungen am Ziel des lokalen Zeiger kann das Ziel des anrufenden Zeiger beeinflussen (zumindest bis der Zielzeiger wird aktualisiert)
  3. der Wert zeigte auf in add_one_v2 ist nicht erhöht und weder ist der folgende Wert, aber der Zeiger
  4. die Erhöhung des Zeigers in add_one_v2 geschieht nach die dereferenzieren

Warum?

  • Da ++ bindet stärker als * (als dereferenzieren oder Multiplikation), so dass die Zunahme in add_one_v2 gilt für den Zeiger, und nicht das, was er zeigt an.
  • Post Schritten geschehen nach die Bewertung des Begriffs, so dass das dereferenzieren wird der erste Wert in dem Array (Element 0).

Wie die andere haben darauf hingewiesen, Operatorpräzedenz bewirken, dass der Ausdruck in der v2-Funktion gesehen als *(our_var_ptr++) wird.

Da dies jedoch ein Post-Inkrement-Operator ist, dann ist es nicht ganz richtig zu sagen, dass es den Zeiger erhöht und dereferenziert es dann. Wenn dies wahr wäre glaube ich nicht, dass Sie 63 als Ausgabe bekommen würden, da würde es den Wert in der nächsten Speicherplatz zurückkehren. Eigentlich glaube ich, dass die logische Folge von Operationen ist:

  1. den aktuellen Wert des Zeigers speichern off
  2. Erhöhen Sie den Zeiger
  3. Dereferenziere der Zeigerwert in Schritt 1 gespeichert

Wie htw erklärt, Sie sehen die Änderung nicht auf den Wert des Zeigers, da sie von Wert an die Funktion übergeben wird.

our_var_ptr ist ein Zeiger zu einem gewissen Speicher. dh es ist die Speicherzelle, wo die Daten gespeichert sind. (N diesem Fall 4 Bytes im binären Format eines int).

* our_var_ptr ist der dereferenzierten Zeiger - es an den Ort geht der Zeiger ‚Punkte‘ auf

.

++ erhöht einen Wert.

so. *our_var_ptr = *our_var_ptr+1 dereferenzieren den Zeiger und fügt eine auf den Wert an dieser Stelle.

Jetzt in Operatorpräzedenz hinzufügen -. Lesen Sie es als (*our_var_ptr) = (*our_var_ptr)+1 und Sie sehen, dass die dereferenzieren geschieht zuerst, so nehmen Sie den Wert und incremement es

In Ihrem anderen Beispiel hat der Operator ++ niedrigere Priorität als der *, so nimmt es den Zeiger Sie übergeben, fügte man ihm (so es an Müll zeigt jetzt), und kehrt dann zurück. (Sie erinnern sich Werte werden immer als Wert in C übergeben, so dass, wenn die Funktion gibt die ursprünglichen testvar Zeiger gleich bleiben, Sie verändern nur den Zeiger innerhalb der Funktion).

Mein Rat, bei der Verwendung von dereferencing (oder etwas anderes) verwenden Klammern Ihre Entscheidung explizit zu machen. Versuchen Sie nicht, die Vorrangregeln zu erinnern, wie Sie nur eine andere Sprache 1 Tag am Ende dann verwenden, die ihnen etwas andere hat, und Sie werden verwirrt. Oder alt und bis Vergessens enden, die eine höhere Priorität hat (wie ich mit * tun und ->).

Wenn Sie nicht Klammer verwenden die Reihenfolge der Operationen angeben, beide Präfix und Postfix-Schritte haben Vorrang vor der Referenz und dereferenzieren. Präfix Schritt und Postfix Schritt sind jedoch verschiedene Operationen. In ++ x, nimmt der Bediener einen Verweis auf Ihre Variable, fügen Sie ein, um es und senden Sie es per Wert. In x ++, der Bedienungsschritt Ihrer Variable, sondern gibt seinen alten Wert. Sie verhalten sich irgendwie wie diese (man stelle sie als Methoden in Ihrer Klasse deklariert werden):

//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;
}

(Beachten Sie, dass es eine Kopie im Postfix Schritt beteiligt ist, so dass es weniger effizient ist. Das ist der Grund, warum Sie preffer sollte ++ i statt i in Schleifen ++, obwohl die meisten Compiler es automatisch für Sie tun in diesen Tagen.)

Wie Sie sehen können, ist Postfix Schritt zuerst verarbeitet, sondern wegen der Art und Weise verhält es sich, werden Sie den vorherigen Wert des Zeigers dereferencing werden.

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 dem dereferenzieren erhöht werden, aber die dereferenzieren wird über den alten Wert von x vorkommen (das ist eine Adresse durch den Postfix Schritt zurückgeführt). So wird mit y 'a', z und mit 'C' initialisiert. Aber wenn man es wie folgt aus:

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

Hier finden x werden dereferenziert und der Wert , indem darauf hingewiesen ( 'a') wird erhöht werden ( 'b'). Da der postfix Schritt den alten Wert zurückgibt, wird y noch mit ‚a‘ initialisiert werden. Und da der Zeiger nicht geändert hat, wird z mit dem neuen Wert ‚b‘ initialisiert wird.

können Sie nun die Vorsilbe Fälle prüfen:

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

Hier wird die dereferenzieren auf der inkrementierte Wert von x passieren (die sofort durch die Vorsilbe Inkrementoperator zurückgegeben wird), so dass beide Y und Z werden 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;

Sie hier sicher, dass Sie die Inhalte von x sind Inkrementieren ersten und der Wert des Zeigers ändern sich nie, so y und z wird mit ‚b‘ zugeordnet werden. In dem strcpy Funktion (in anderer Antwort erwähnt), wird der Zuwachs auch getan zuerst:

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

Bei jeder Iteration src ++ verarbeitet ersten und einen Postfix-Zuwachs zu sein, es gibt den alten Wert von src. Dann wurde der alte Wert von src (der ein Zeiger) dereferenziert zugewiesen werden, was auch immer ist in der linken Seite des Zuweisungsoperators. Die dst wird dann erhöht und sein alter Wert dereferenziert einen L-Wert zu werden und den alten src-Wert zu erhalten. Aus diesem Grunde dst [0] = src [0], dst [1] = src [1] usw., bis * dst mit 0 zugeordnet ist, die Schleife zu brechen.

Nachtrag:

Die gesamte Code in dieser Antwort wurde mit der Sprache C getestet. In C ++ werden Sie wahrscheinlich nicht in der Lage sein, den Zeiger auf Liste zu initialisieren. Also, wenn Sie die Beispiele in C ++ testen möchten, sollten Sie ein Array zuerst initialisieren und dann auf einen Zeiger degradieren:

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

Der ‚++‘ Operator hat eine höheren Vorrang vor dem ‚*‘ Operator, was bedeutet, dass die Zeiger-Adresse wird vor dem dereferenced erhöht werden.

Der Operator '+' hat jedoch eine geringere Priorität als '*'.

Ich werde versuchen, diese aus einem etwas anderen Winkel zu beantworten ... Schritt 1 Schauen wir uns die Betreiber schauen und die Operanden: In diesem Fall ist es der Operand, und Sie haben zwei Betreiber, in diesem Fall * für dereferencing und ++ für Zuwachs. Schritt 2 was hat die höhere Priorität ++ hat eine höheren Vorrang vor * Schritt 3 Wo ++ ist, ist es auf der rechten Seite, was bedeutet, POST Increment In diesem Fall nimmt der Compiler eine ‚mentale Notiz‘ das Schrittweite nach ausführt es mit allen anderen Betreibern gemacht wird ... beachten Sie, wenn es * ++ p, dann wird es das tun, BEVOR so dass in diesem Fall ist es gleich zwei der Prozessor-Register nehmen, so wird man den Wert der dereferenziert halten * p und der andere den Wert des inkrementierten p halten ++, ist der Grund in diesem Fall es zwei sind, ist der POST-Aktivität ... Dies ist, wo in diesem Fall es schwierig ist, und es sieht aus wie ein Widerspruch. Man würde erwarten, dass die ++ Vorrang vor dem * nehmen, die es funktioniert, nur dass die POST bedeutet, dass es nur, wenn alle anderen Operanden, bevor die nächste angewendet werden ‚;‘ Token ...

    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++;
    }

Weil Test ein Zeiger ist, Test ++ (dies ist ohne es zu dereferencing) den Zeiger (es erhöht den Wert des Tests, der die (Ziel) -Adresse sein geschieht, was bei spitz ist wird) erhöht. Da das Ziel des Typ uint32_t ist, wird Test ++ um 4 Bytes inkrementiert, und wenn das Ziel beispielsweise eine Anordnung dieser Art ist, dann wäre Test nun am nächsten Elemente verweist. Wenn diese Art von Manipulationen zu tun, manchmal muss man zuerst die Zeiger werfen versetzten die gewünschten Speicher zu erhalten.

        ((unsigned char*) test)++;

Dies wird die Adresse um 1 Byte erhöht nur;)

Von K & R, Seite 105: "Der Wert von * t ++ ist das Zeichen, dass t zu spitz vor t wurde erhöht"

Die Bilder sind im Wert von rund tausend Worte (geben oder eine Million oder so nehmen) ... und Symbole können Bilder sein (und umgekehrt).

So⸺for diejenigen von uns, die tl;dr suchen s (optimierte Datenverbrauch) aber immer noch will ‚(meistens) lossless‘ Codierung, Vektorbilder / Bilder / Illustrationen / Demos sind von größter Bedeutung.

Mit anderen Worten, einfach ignoriert meine letzten 2 Aussagen und unten.

Valid forms:


*a++ ≣ *(a++)
≣ (a++)[0] ≣ a++[0]
≣ 0[a++] // Don't you dare use this (“educational purposes only”)
// These produce equivalent (side) effects;
≡ 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] // ()'s are necessary
≣ 0[++a] // 0[a+=1], etc... “educational purposes only”
// These produce equivalent (side) effects:
≡ ++a,*a
≡ a+=1,*a
≡ a=a+1,*a

++*a ≣ ++(*a)
≣ *a+=1
≣ *a=*a+1
≣ ++a[0] ≣ ++(a[0])
≣ ++0[a] // STAY AWAY

(*a)++ // Note that this does NOT return a pointer;
// Location `a` points to does not change
// (i.e. the 'value' of `a` is unchanged)
≡ val=*a,++*a,val

Notes

/* Indirection/deference operator must pr̲e̲cede the target identifier: */
a++*;
a*++;
++a*;

Da der Zeiger durch übergebene Wert wird nur die lokale Kopie wird erhöht. Wenn Sie wirklich den Zeiger erhöhen möchten, müssen Sie es passieren in durch Bezugnahme wie folgt aus:

void inc_value_and_ptr(int **ptr)
{
   (**ptr)++;
   (*ptr)++;
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top