Pointer in Verbindung mit Inkrementoperator



  • Hallo liebe C++-Community,

    ich habe kürzlich begonnen mir C++ als erste Programmiersprache autodidaktisch mit Hilfe eines Lehrbuches beizubringen. Nun bin ich zum ersten Mal auf eine Unklarheit gestoßen, die ich mir auch nach einigem Nachdenken nicht selber beantworten kann. Daher würde ich mich riesig freuen, wenn ihr dabei helfen könntet! 🙂

    Auslöser war folgender Codeausschnitt:

    char s[40] = "Ein Ausgabetest", *zs;
    zs = s;
    while(*zs) cout << *zs++;
    

    Zeile 2 bewirkt hierbei, dass "Ein Ausgabetest" ausgegeben wird. *zs++ bedeutet hier demnach *zs und dann zs = zs + 1. Für mich war nun unklar, warum es nicht
    auch *zs und dann *zs = *zs + 1 bedeuten kann und wie man genau das (*zs und dann *zs = *zs + 1) unter Verwendung des ++-Operators ausdrücken würde.

    Ich weiß, dass der Indirektionsoperator * und der Inkrementoperator ++ zur gleichen Vorrangsgruppe gehören und die Abarbeitung innerhalb dieser Gruppe von rechts nach links erfolgt. Mit diesem Wissen konnte ich meine Vewirrung leider nicht auflösen. Daraufhin habe ich selber noch mit Kombinationen der beiden Operatoren experimentiert:

    int i[] = { 1, 5 }, *zi;
    zi = i;
    int a = ++*zi;
    int b = *++zi;
    

    Zeile 3 bewirkt *zi = *zi + 1 und dann int a = *zi.
    Zeile 4 bewirkt zi = zi + 1 und dann int b = *zi.

    An dieser Stelle habe ich das Schreiben meines Beitrages unterbrochen, noch einmal über den Sachverhalt nachgedacht und einen Erklärungsansatz gefunden. Dazu habe ich mir noch einmal klar gemacht, dass ++i "erhöhe i um 1, dann verwende es in dem Ausdruck" und i++ "verwende i in dem Ausdruck, dann erhöhe es um 1" bedeutet und die Abarbeitung von rechts nach links innerhalb dieser Vorranggruppe zugrunde gelegt:

    1. *zs++: Von rechts beginnend ist ++ der erste Operator. Es handelt sich um den Post-Inkrementoperator, womit ich zuerst zs in dem Ausdruck verwenden muss.
      Das bedeutet, dass zs an den Indirektionsoperator übergeben wird, der wiederum zs dereferenziert und an cout übergibt. Schließlich wird zs um eins erhöht.

    2. ++*zi: Von rechts beginnend ist hier * der erste Operator. Demnach wird zi dereferenziert und das Ergebnis (hier gleich i[0]) an den Prä-Inkrementoperator übergeben, der wiederum zuerst *zi um eins erhöht und dann *zi in dem Ausdruck verwendet, also die Zuweisung a = *zi.

    3. *++zi: Von rechts beginnend ist hier ++ der erste Operator. Es handelt sich dabei um den Prä-Inkrementoperator, womit zi zuerst um eins erhöht und dann in dem Ausdruck verwendet wird. Somit wird daraufhin der um eins erhöhte Zeiger zi an den Indirektionsoperator übergeben, der dann wiederum zi derefernziert und das Ergebnis (hier gleich i[1]) der Variablen b zuweist.

    Nach diesem Muster müsste *zs und dann *zs = *zs + 1 (was ich mich weiter oben gefragt habe) als (*zs)++ geschrieben werden, womit man die Abarbeitung von rechts nach links aufheben würde.

    Ist diese Erklärung korrekt? Ist es richtig, wenn ich dabei immer wieder von "übergeben" spreche?

    Zum Schluss habe ich noch eine kurze Frage, für die ich keinen eigenen Thread eröffnen möchte: Ist das abschließende Semikolen einer do-while-Schleife notwendig? Oder hätte man die Syntax auch als "do <anweisung> while(Ausdruck)" festlegen können, ohne dass man Uneindeutigkeiten verursacht?

    Viele Grüße
    Ruperrrt



  • @Ruperrrt sagte in Pointer in Verbindung mit Inkrementoperator:

    Nach diesem Muster müsste *zs und dann *zs = *zs + 1 (was ich mich weiter oben gefragt habe) als (*zs)++ geschrieben werden,

    Muss du auch.
    Aber *zs++ ist *zs und dann zs = zs + 1
    (achte auf den Dereferenzierungsoperator 😉

    Ansonsten gibt es eine definierte Rangfolge der Operatoren.



  • @Ruperrrt sagte in Pointer in Verbindung mit Inkrementoperator:

    Ich weiß, dass der Indirektionsoperator * und der Inkrementoperator ++ zur gleichen Vorrangsgruppe gehören und die Abarbeitung innerhalb dieser Gruppe von rechts nach links erfolgt.

    Das stimmt aber nicht. Es gibt zwei Inkrementoperatoren, Präfix und Postfix. Der Postfixoperator hat eine höhere Rangfolge als der unäre * Operator.

    Und das mit der Abarbeitung innerhalb der Gruppe von rechts nach links kannst du vergessen. Das steht überall so, weil jeder von jedem abschreibt, aber da die Operatoren innerhalb der Gruppe ohnehin alle unäre Präfixoperatoren sind, gibt es keine Mehrdeutigkeit, die durch so eine Regel aufgelöst werden müsste. Beispielsweise bedeutet ++*p selbstverständlich ++(*p), was sonst?

    Wichtig ist die Assoziativität bei binären Operatoren, z.B. sollte a - b + c tunlichst (a - b) + c und nicht a - (b + c) bedeuten.



  • @DirkB sagte in Pointer in Verbindung mit Inkrementoperator:

    @Ruperrrt sagte in Pointer in Verbindung mit Inkrementoperator:

    Nach diesem Muster müsste *zs und dann *zs = *zs + 1 (was ich mich weiter oben gefragt habe) als (*zs)++ geschrieben werden,

    Muss du auch.
    Aber *zs++ ist *zs und dann zs = zs + 1
    (achte auf den Dereferenzierungsoperator 😉

    Ansonsten gibt es eine definierte Rangfolge der Operatoren.

    Vielen Dank für deine Antwort! Dass *zs++ erst *zs und dann zs = zs + 1 bedeutet habe ich geschrieben. Mit *zs und dann *zs = *zs + 1 meinte ich dort (*zs)++ (also mit einer Klammerung, um die höhere Priorität das Postfix-Inkrements aufzuheben.

    @Bashar sagte in Pointer in Verbindung mit Inkrementoperator:

    Das stimmt aber nicht. Es gibt zwei Inkrementoperatoren, Präfix und Postfix. Der Postfixoperator hat eine höhere Rangfolge als der unäre * Operator.

    Auch dir vielen Dank für deine Antwort und diesen Hinweis! 🙂 Ich habe die Tabelle meines Lehrbuches mit entsprechenden Prioritätstabellen aus dem Internet verglichen und dabei festgestellt, dass die Tabelle meines Lehrbuches in dem Punkt schlichtweg falsch ist: Sie setzt sowohl Postfix- als auch Präfix-Inkrement mit dem Dereferenzierungsoperator in eine Vorrangsgruppe. Tatsächlich ist es aber so - wie von dir geschrieben - dass das Postfix-Inkrement eine Vorrangsgruppe höher als das Präfix-Inkrement und der Dereferenzierungsoperator ist. Somit ist schon einmal klar, warum *zs++ bedeutet *(zs++) und NICHT (*zs)++.

    @Bashar sagte in Pointer in Verbindung mit Inkrementoperator:

    Und das mit der Abarbeitung innerhalb der Gruppe von rechts nach links kannst du vergessen. Das steht überall so, weil jeder von jedem abschreibt, aber da die Operatoren innerhalb der Gruppe ohnehin alle unäre Präfixoperatoren sind, gibt es keine Mehrdeutigkeit, die durch so eine Regel aufgelöst werden müsste. Beispielsweise bedeutet ++*p selbstverständlich ++(*p), was sonst?

    Benötigt man nicht genau bei ++*p und *++p die von rechts nach links Regel? Mit einer Auswertung von links nach rechts könnte die Bedeutung ja auch genau umgekehrt sein, also ++*p = *(++p) und *++p = ++(*p). Diese Festlegung von links nach rechts wäre zwar nicht besonders sinnvoll, aber dennoch möglich, oder?

    Habt ihr auch eine Idee zum abschließenden Semikolon bei der do-while-Schleife? 🙂

    Viele Grüße
    Ruperrrt



  • @Ruperrrt sagte in Pointer in Verbindung mit Inkrementoperator:

    Benötigt man nicht genau bei ++*p und *++p die von rechts nach links Regel?

    @Bashar sagte in Pointer in Verbindung mit Inkrementoperator:

    unäre Präfixoperatoren



  • @Swordfish sagte in Pointer in Verbindung mit Inkrementoperator:

    @Ruperrrt sagte in Pointer in Verbindung mit Inkrementoperator:

    Benötigt man nicht genau bei ++*p und *++p die von rechts nach links Regel?

    @Bashar sagte in Pointer in Verbindung mit Inkrementoperator:

    unäre Präfixoperatoren

    Ich verstehe leider wirklich nicht, wie das meine Unklarheit auflösen kann. Wieso könnten nicht ++*p = *(++p) und *++p = ++(*p) bei einer Auswertung von links nach rechts sein (auch wenn es kontraintuitiv wäre)?

    Falls damit gesagt werden soll, dass eine Auswertereihenfolge nur ab binären Operatoren Sinn macht: Warum ist dann in der Tabelle https://de.cppreference.com/w/cpp/language/operator_precedence in der Gruppe 3 die Assoziativität mit von rechts nach links angegeben? Sind das nicht alles unäre Operatoren?



  • @Ruperrrt sagte in Pointer in Verbindung mit Inkrementoperator:

    Ich verstehe leider wirklich nicht, wie das meine Unklarheit auflösen kann. Wieso könnten nicht ++*p = *(++p) und *++p = ++(*p) bei einer Auswertung von links nach rechts sein (auch wenn es kontraintuitiv wäre)?

    Ja könnte, tun sie aber nicht. [expr.unary]/1



  • @Ruperrrt sagte in Pointer in Verbindung mit Inkrementoperator:

    Benötigt man nicht genau bei ++*p und *++p die von rechts nach links Regel? Mit einer Auswertung von links nach rechts könnte die Bedeutung ja auch genau umgekehrt sein, also ++*p = *(++p) und *++p = ++(*p). Diese Festlegung von links nach rechts wäre zwar nicht besonders sinnvoll, aber dennoch möglich, oder?

    Das kommt mir völlig an den Haaren herbeigezogen vor, sorry. Aber wie gesagt, jeder schreibt das so, also wenn du dir irgendeine Erklärung zusammenreimst, was die Assoziativität bei unären Operatoren ist und wozu man sie angeben muss, wärst du vermutlich in guter Gesellschaft.

    Habt ihr auch eine Idee zum abschließenden Semikolon bei der do-while-Schleife? 🙂

    Das scheint theoretisch tatsächlich nicht notwendig zu sein, wie auch das bei continue oder break. Aber es enden alle anderen Anweisungen auf ; oder }, also scheint es sinnvoll, das Semikolon in der Syntax auch bei diesen vorzuschreiben.



  • meines Lehrbuches

    Welches? Von Jürgen Wolf?



  • Ich würde auf dieses Zusammenziehen des Codes verzichten heutzutage. Früher konnte das jeder sofort lesen aber heute ist Pointermagic eher unüblich. Wenn das was da passiert, wirklich genau so passieren muss, würde ich das in eine for schleife packen. Dann ist Ausgabe, pointer weiter schieben und Abbruchbedingung klar getrennt und es sollte schneller ersichtlich sein, was bei dem Code gemeint ist.



  • @Swordfish sagte in Pointer in Verbindung mit Inkrementoperator:

    @Ruperrrt sagte in Pointer in Verbindung mit Inkrementoperator:

    Ich verstehe leider wirklich nicht, wie das meine Unklarheit auflösen kann. Wieso könnten nicht ++*p = *(++p) und *++p = ++(*p) bei einer Auswertung von links nach rechts sein (auch wenn es kontraintuitiv wäre)?

    Ja könnte, tun sie aber nicht. [expr.unary]/1

    Vielen Dank für deine Antwort! Es ist gut zu wissen, dass es bei entsprechender Festlegung auch so sein könnte.

    @Bashar sagte in Pointer in Verbindung mit Inkrementoperator:

    @Ruperrrt sagte in Pointer in Verbindung mit Inkrementoperator:

    Benötigt man nicht genau bei ++*p und *++p die von rechts nach links Regel? Mit einer Auswertung von links nach rechts könnte die Bedeutung ja auch genau umgekehrt sein, also ++*p = *(++p) und *++p = ++(*p). Diese Festlegung von links nach rechts wäre zwar nicht besonders sinnvoll, aber dennoch möglich, oder?

    Das kommt mir völlig an den Haaren herbeigezogen vor, sorry. Aber wie gesagt, jeder schreibt das so, also wenn du dir irgendeine Erklärung zusammenreimst, was die Assoziativität bei unären Operatoren ist und wozu man sie angeben muss, wärst du vermutlich in guter Gesellschaft.

    Ich verstehe absolut, warum das als an den Haaren herbeigezogen empfindest. Es wäre sehr kontraintuitiv einen Ausdruck wie z.B. ~-d als -(~d) statt ~(-d) auszuwerten. Im Internet finde ich nichtsdestotrotz allerhand Aussagen zur Rechtsassoziativität bei unären Operatoren (z.B. "Unäre Operatoren und Zuweisungsoperatoren werden von rechts nach links ausgewertet" aus Java in a nutshell oder hier http://www14.in.tum.de/lehre/2017WS/info1/split/sub-Auswertung-von-Ausdruecken-single.pdf: "Man kann keinen legalen Ausdruck bilden, bei der die Assoziativität der Postfix-Operatoren (Gruppe Priorität 2) eine Rolle spielen würde". In einer darauffolgenden Tabelle ist u.a. das Prä-Inkrement dann als rechtsassoziativ angegeben.). Deshalb denke ich, dass es sich dabei letzten Endes doch um eine (offensichtliche) Festlegung handelt - es könnte ja auch genau umgekehrt sein und würde genauso gut funktionieren. Oder liege ich da falsch?

    @Bashar sagte in Pointer in Verbindung mit Inkrementoperator:

    Habt ihr auch eine Idee zum abschließenden Semikolon bei der do-while-Schleife? 🙂

    Das scheint theoretisch tatsächlich nicht notwendig zu sein, wie auch das bei continue oder break. Aber es enden alle anderen Anweisungen auf ; oder }, also scheint es sinnvoll, das Semikolon in der Syntax auch bei diesen vorzuschreiben.

    Ja, stimmt, danke! Es macht insofern Sinn, sie mit einem Semikolon enden zu lassen, als es sich bei der do-while-Schleife insgesamt um EINE Anweisung handelt. Ist das korrekt? Die Anweisung in der Mitte der Bedingungsschleife (nach dem do) hat mich wohl von dieser Erkenntnis ein wenig abgelenkt.

    @manni66 sagte in Pointer in Verbindung mit Inkrementoperator:

    meines Lehrbuches

    Welches? Von Jürgen Wolf?

    Nein, das ist nicht aus einem Lehrbuch von Jürgen Wolf. Ich besitze allerdings auch eins von ihm, das ich nebenbei verwende. Allerdings empfinde ich es als weniger gut als das von mir hauptsächlich genutzte.

    @TGGC sagte in Pointer in Verbindung mit Inkrementoperator:

    Ich würde auf dieses Zusammenziehen des Codes verzichten heutzutage. Früher konnte das jeder sofort lesen aber heute ist Pointermagic eher unüblich. Wenn das was da passiert, wirklich genau so passieren muss, würde ich das in eine for schleife packen. Dann ist Ausgabe, pointer weiter schieben und Abbruchbedingung klar getrennt und es sollte schneller ersichtlich sein, was bei dem Code gemeint ist.

    In dem Buch ist es direkt darüber tatsächlich ähnlich geschehen. Das diente wohl dazu, beide Möglichkeiten aufzuzeigen.



  • @Ruperrrt sagte in Pointer in Verbindung mit Inkrementoperator:

    Nein, das ist nicht aus einem Lehrbuch von Jürgen Wolf. Ich besitze allerdings auch eins von ihm, das ich nebenbei verwende. Allerdings empfinde ich es als weniger gut als das von mir hauptsächlich genutzte.

    Dein Empfinden ist korrekt - und ich sage das, ohne das andere Buch zu kennen 👹

    Und ich finde tatsächlich sowas wie while (bedingung) { *a++ = *b++; } gut zu lesen. Sieh dir auch mal Beispiele an wie in der möglichen Implementierung hier: https://en.cppreference.com/w/cpp/algorithm/copy
    Also lesen sollte man es können - und ich bevorzuge in einigen Fällen auch diese Kurzschreibweise. Sie kommt auch häufig in Code vor.



  • @Ruperrrt sagte in Pointer in Verbindung mit Inkrementoperator:

    Ich verstehe absolut, warum das als an den Haaren herbeigezogen empfindest. Es wäre sehr kontraintuitiv einen Ausdruck wie z.B. ~-d als -(~d) statt ~(-d) auszuwerten. Im Internet finde ich nichtsdestotrotz allerhand Aussagen zur Rechtsassoziativität bei unären Operatoren (z.B. "Unäre Operatoren und Zuweisungsoperatoren werden von rechts nach links ausgewertet" aus Java in a nutshell

    Bei der Assoziativität geht es nicht um die Auswertungsreihenfolge. Beispielsweise bekommt man in readInt() - readInt() i.A. unterschiedliche Ergebnisse, je nachdem, welche Eingabe zuerst erfolgt, so dass bspw. in Java grundsätzlich eine Auswertungsreihenfolge von links nach rechts vorgeschrieben ist. In C++ bin ich da nicht ganz auf dem laufenden, früher war es unspezifiziert, heute vielleicht nicht mehr.

    Da steht jetzt also, dass unäre Operatoren rechtsassoziativ sind. Soso. Auf der nächsten Seite ist dann eine Tabelle, in der Postfix-Operatoren als linksassoziativ gekennzeichnet sind.

    Stroustrup macht in seinem Buch übrigens den gleichen Fehler, zu behaupten, dass Zuweisungen und unäre Operatoren rechtsassoziativ sind.

    Meine Interpretation davon: Das ist alles Blödsinn. Es ergibt keinen Sinn, bei unären Operatoren überhaupt von Assoziativität zu sprechen.

    oder hier http://www14.in.tum.de/lehre/2017WS/info1/split/sub-Auswertung-von-Ausdruecken-single.pdf: "Man kann keinen legalen Ausdruck bilden, bei der die Assoziativität der Postfix-Operatoren (Gruppe Priorität 2) eine Rolle spielen würde".

    Oder anders gesagt: Assoziativitätsangaben für unäre Operatoren sind Käse.

    es könnte ja auch genau umgekehrt sein und würde genauso gut funktionieren. Oder liege ich da falsch?

    Du meinst, so wie du am Anfang gesagt hast? Dass op expr bedeuten kann, dass op in expr reinguckt, alle anderen Präfixoperatoren überspringt und sich dann am Ende irgendwo einfügt? Wie gesagt, ich halte das für an den Haaren herbeigezogen. Nur um zwanghaft das Feld für Assoziativität in der Operatorentabelle ausfüllen zu können.

    Dabei ist diese Tabelle, zumindest in C++, pure Fiktion. Im Standard gibt es so eine Tabelle nicht, nur eine Grammatik, und diese Grammatik ist wenn ich das richtig sehe zu kompliziert, um als Operatorenvorrangtabelle dargestellt zu werden.



  • @wob sagte in Pointer in Verbindung mit Inkrementoperator:

    Dein Empfinden ist korrekt - und ich sage das, ohne das andere Buch zu kennen 👹

    Warum bist du dieser Meinung? 😋

    @wob sagte in Pointer in Verbindung mit Inkrementoperator:

    Und ich finde tatsächlich sowas wie while (bedingung) { *a++ = *b++; } gut zu lesen. Sieh dir auch mal Beispiele an wie in der möglichen Implementierung hier: https://en.cppreference.com/w/cpp/algorithm/copy

    In welcher Reihenfolge würde man denn *a++ = *b++; auswerten? Ich wüsste z.B. schon einmal nicht, ob zu erst das Post-Inkrement von a oder b an der Reihe wäre. Und in welcher Reihenfolge würde man dann schließlich die Pointer um eins erhöhen? Erst a oder erst b?

    @Bashar sagte in Pointer in Verbindung mit Inkrementoperator:

    Bei der Assoziativität geht es nicht um die Auswertungsreihenfolge. Beispielsweise bekommt man in readInt() - readInt() i.A. unterschiedliche Ergebnisse, je nachdem, welche Eingabe zuerst erfolgt, so dass bspw. in Java grundsätzlich eine Auswertungsreihenfolge von links nach rechts vorgeschrieben ist. In C++ bin ich da nicht ganz auf dem laufenden, früher war es unspezifiziert, heute vielleicht nicht mehr.

    Wie kommt es dabei zu den unterschiedlichen Ergebnissen, je nachdem welche Eingabe zuerst erfolgt?

    @Bashar sagte in Pointer in Verbindung mit Inkrementoperator:

    Da steht jetzt also, dass unäre Operatoren rechtsassoziativ sind. Soso. Auf der nächsten Seite ist dann eine Tabelle, in der Postfix-Operatoren als linksassoziativ gekennzeichnet sind.

    Welche Tabelle in welcher Quelle meinst du? Ich kann sie leider nicht finden.

    Auf jeden Fall möchte ich dir für deine Antworten danken!



  • @Ruperrrt sagte in Pointer in Verbindung mit Inkrementoperator:

    @wob sagte in Pointer in Verbindung mit Inkrementoperator:

    Bei der Assoziativität geht es nicht um die Auswertungsreihenfolge. Beispielsweise bekommt man in readInt() - readInt() i.A. unterschiedliche Ergebnisse, je nachdem, welche Eingabe zuerst erfolgt, so dass bspw. in Java grundsätzlich eine Auswertungsreihenfolge von links nach rechts vorgeschrieben ist. In C++ bin ich da nicht ganz auf dem laufenden, früher war es unspezifiziert, heute vielleicht nicht mehr.

    Wie kommt es dabei zu den unterschiedlichen Ergebnissen, je nachdem welche Eingabe zuerst erfolgt?

    Sagen wir, der Benutzer gibt hintereinander 10 und 5 ein. Wenn der Ausdruck von links nach rechts ausgewertet wird, ist das Ergebnis 5, wenn er von rechts nach links ausgewertet wird ist es -5.

    @Bashar sagte in Pointer in Verbindung mit Inkrementoperator:

    Da steht jetzt also, dass unäre Operatoren rechtsassoziativ sind. Soso. Auf der nächsten Seite ist dann eine Tabelle, in der Postfix-Operatoren als linksassoziativ gekennzeichnet sind.

    Welche Tabelle in welcher Quelle meinst du? Ich kann sie leider nicht finden.

    Ich hab das Buch nicht in meinem Regal, sondern gegoogelt, und folgendes PDF gefunden: http://www.r-5.org/files/books/computers/languages/java/main/Benjamin_Evans_David_Flanagan-Java_in_a_Nutshell_6th_ed-EN.pdf

    Da steht der besagte Satz auf Seite 31 und die erwähnte Tabelle ist auf Seite 32.


Log in to reply