Zuweisungen



  • Hi, ich versuche zu verstehen wie die folgende Zuweisung funktioniert

    int erg = ((i = i + 1) * (i = i + 1) * (i = i + 1));
    

    Dazu habe ich mir den folgenden Code geschrieben:

    #include <iostream>
    using namespace std;
    
    int main()
    {
    	int i = 2;
    
            //Variante 1
    	int erg = ((i = i + 1) * (i = i + 1) * (i = i + 1)); //Was wird hier genau gemacht?
    	cout << erg << endl;
    
    	i = 2;
    
            //Variante 2
    	i = i + 1;
    	i = i + 1;
    	i = i + 1;
    	cout << (i * i * i);
    
    	return 0;
    }
    

    Ich hatte zunächst angenommen dass die Zuweisung die in Variante 1 steht im Prinzip das gleiche ist wie Variante 2 nur anders geschrieben. Doch da hab ich
    mich geirrt, denn das Ergebnis der Variante 1 lautet 80, während das Ergebnis der Variante 2 aber 125 lautet.

    Kann mir jemand erklären wie Variate 1 funktioniert?



  • halte dich damit nicht auf, das Ergebnis ist undefiniert.



  • Woher hast du den Quatsch denn? ^^



  • Mit undefiniert meinst du, dass es einfach falsch berechnet wird,
    weils ein unzulässiger Ausdruck ist?

    Und was ist damit?
    Ist das Ergebnis auch undefiniert?
    Ich habe dieses Beispiel nämlich im Internet gefunden,
    aber ich kann hier einfach nicht nachvollziehen, wieso
    das Ergebnis dieser Berechnung 36 ist.

    #include <iostream>
    using namespace std;
    
    #define QUAD(x) ((x) * (x))
    
    int main()
    {
    	int x = 4;
    	int erg = QUAD(++x);
    	cout << erg; // liefert 36
    
    	return 0;
    }
    


  • blupblup schrieb:

    Und was ist damit?
    Ist das Ergebnis auch undefiniert?

    Ja, wenn QUAD kein Makro, sondern eine Funktion wäre (so wie man es richtig machen würde), dann wäre es nicht undefiniert...



  • Auf der Seite wo ich das nachfolgende Beispiele habe,
    soll ein Funktionsaufruf mit einem Makroaufruf verglichen werden.

    Der Funktionsaufruf und der Makroaufruf scheinen dieselbe Berechnung
    zu machen, wenn man das Programm allerdings startet zeigt sich,
    dass dem nicht so ist. Leider steht zu dem Beispiel nichts weiter,
    so dass ich nicht verstehe woran das liegt.

    Nun versteh ich die Aussage des Beispiels auch nicht. Soll es zeigen,
    dass das Makro so nicht aufgerunfen werden kann wie im Beispiel gezeigt?
    Berechnet das Makro einen falschen Wert? Oder ist der Wert richtig
    und man muss sich die Berechnung irgendwie anders vorstellen?

    #include <iostream>
    using namespace std;
    
    #define QUAD(x) ((x) * (x))
    
    int quadrat(int);
    
    int main()
    {
    	int x = 4;
    	int erg = QUAD(++x); //erg = 36
    	cout << erg << endl; 
    
    	int y = 4;
    	int erg2 = quadrat(++y); //erg2 = 25
    	cout << erg2;
    
    	return 0;
    }
    
    int quadrat(int x)
    {
    	return x * x;
    }
    


  • Dir fehlt Grundwissen, hol dir ein gutes Buch.

    Thread gibts in der FAQ.



  • Das Beispiel soll einfach zeigen, dass ein Makro nicht mit einem Vergleichsaufruf gleichzusetzen ist, leider benutzt es vielleicht fragwürdige Beispiele oder erläutert diese nicht genau.

    Wenn du folgenden Source hast:

    double square( double in ) { return in*in; }
    #define SQUARE(x) ((x)*(x))
    a = SQUARE(++test);
    b = square(++test);
    

    Dann sieht der C++-Compiler, nachdem der Präprozessor mit dem Makro fertig ist:

    a = ((++test)*(++test));  // Ups!
    b = square(++test);       // Ok!
    


  • blupblup schrieb:

    Auf der Seite wo ich das nachfolgende Beispiele habe,
    soll ein Funktionsaufruf mit einem Makroaufruf verglichen werden.

    Der Funktionsaufruf und der Makroaufruf scheinen dieselbe Berechnung
    zu machen, wenn man das Programm allerdings startet zeigt sich,
    dass dem nicht so ist. Leider steht zu dem Beispiel nichts weiter,
    so dass ich nicht verstehe woran das liegt.

    Nun versteh ich die Aussage des Beispiels auch nicht. Soll es zeigen,
    dass das Makro so nicht aufgerunfen werden kann wie im Beispiel gezeigt?
    Berechnet das Makro einen falschen Wert? Oder ist der Wert richtig
    und man muss sich die Berechnung irgendwie anders vorstellen?

    Die Aussage des Beispiels soll vermutlich sein: Makros sind böse und sollten nicht verwendet werden (zumindest nicht für sowas). 😉

    Ist der Wert denn deiner Meinung nach richtig? Was findest du, sollte bei QUAD(++x) rauskommen?

    Anyway, hier die Erklärung:

    int erg = QUAD(++x);
    

    expandiert zu

    int erg = ((++x) * (++x));
    

    Es ist allerdings undefiniert, wann genau während der Auswertung dieses Ausdruckes der Inkrement passiert. Das einzige, was garantiert ist, ist, dass nach der Auswertung des ganzen Ausdruckes x um 2 inkrementiert wurde. Das Ergebnis kann daher also 16, 20, 24, 25, 30 oder 36 sein.
    Bei der Variante mit der Funktion gibt es das Problem nicht und das Ergebnis ist daher garantiert 25.
    Wenn du allerdings sowas hättest:

    void blub(int x, int y)
    {
      return x * y;
    }
    
    int x = 4;
    int erg = blub(++x, ++x);
    

    wär das Ergebnis wieder undefiniert...



  • blupblup schrieb:

    #define QUAD(x) ((x) * (x))
    int main()
    {
    	int x = 4;
    	int erg = QUAD(++x);
    }
    

    Schauen wir mal. Ein Makro ersetzt ja einfach nur etwas. Also steht da:

    int x = 4;
    	int erg = ((++x) * (++x));
    

    C++ garantiert nicht, wann ein Seiteneffekt (Änderung des Speicherbereichs) eintritt. D.h. der Operator ++ gibt dir schön den um 1 erhöhten Wert zurück. Aber niemand weiß, ob der neue Wert schon auf die Variable geschrieben wird. Also kann vieles passieren:

    int x = 4;
    	int erg = ((5) * (5));
    

    oder

    int x = 4;
    	int erg = ((6) * (6));
    

    oder

    int x = 4;
    	int erg = ((5) * (6));
    

    oder...

    C++ garantiert dir aber, dass ein Seiteneffekt spätestens nach einem sequence-point (google wikipedia) eingetreten ist. D.h. erst nach der Anweisung int erg = ((++x) * (++x)); kannst du sicher sein, dass x=6 ist.

    int quadrat(int x)
    {
        return x * x;
    }
    
    int main()
    {
        int y = 4;
        int erg2 = quadrat(++y); //erg2 = 25
    }
    

    Hier ist es GANZ anders. Ein Funktionsaufruf stellt bereits ein sequence-point dar. D.h es ist sicher, dass der Parameter x den Wert 5 hat. Und 5*5 ist nunmal 25. :p


  • Mod

    Ich habe mal eine kleine Übersicht zum ersten Problem erstellt:

    int erg = ((i = i + 1) * (i = i + 1) * (i = i + 1));
              ^^^^^^1^^^^^   ^^^^^2^^^^^   ^^^^^3^^^^^
    
    //                                              op       side effect
    int L0 = i;                // load(1)           (a)      
    int L1 = L0 + 1;           // add(1)            (b)                     (a)<(b) 1.9/15s.2
    i = L1;                    // store(1)          (c)      *              (b)<(c) 5.17/1s.4
    int L2 = L1;               // stored value(1)   (d)                     (c)<(d) 5.17/1s.4
    
    int L3 = i;                // load(2)           (e)
    int L4 = L3 + 1;           // add(2)            (f)
    i = L4;                    // store(2)          (g)      *
    int L5 = L4;               // stored value(2)   (h)
    
    int L6 = i;                // load(3)           (i)
    int L7 = L6 + 1;           // add(3)            (j)
    i = L7;                    // store(3)          (k)      *
    int L8 = L7;               // stored value(3)   (l)
    
    int L9 = L2 * L5;          // mul(1,2)          (m)                     (d)<(m)  (h)<(m)
    int L10 = L9 * L8;         // mul(12,3)         (n)                     (l)<(n)  (m)<(n)
    
    erg = L7;                  // store             (o)      *              (o)<(n)
    
    // < sequenced before
    // > sequenced after
    // | indeterminately sequenced 
    // ~ unsequenced (1.9/15s1)
    // # unsequenced UB (1.9/15s3)
    
    //   a   b   c   d   e   f   g   h   i   j   k   l   m   n   o
    //a      <   <   <   ~   ~   #   ~   ~   ~   #   ~   <   <   <
    //b   >      <   <   ~   ~   ~   ~   ~   ~   ~   ~   <   <   <
    //c   >   >      <   #   ~   #   ~   #   ~   #   ~   <   <   <
    //d   >   >   >      ~   ~   ~   ~   ~   ~   ~   ~   <   <   <
    //e   ~   ~   #   ~      <   <   <   ~   ~   #   ~   <   <   <
    //f   ~   ~   ~   ~   >      <   <   ~   ~   ~   ~   <   <   <
    //g   #   ~   #   ~   >   >      <   #   ~   #   ~   <   <   <
    //h   ~   ~   ~   ~   >   >   >      ~   ~   ~   ~   <   <   <
    //i   ~   ~   #   ~   ~   ~   #   ~      <   <   <   ~   <   <
    //j   ~   ~   ~   ~   ~   ~   ~   ~   >      <   <   ~   <   <
    //k   #   ~   #   ~   #   ~   #   ~   >   >      <   ~   <   <
    //l   ~   ~   ~   ~   ~   ~   ~   ~   >   >   >      ~   <   <
    //m   >   >   >   >   >   >   >   >   ~   ~   ~   ~      <   <
    //n   >   >   >   >   >   >   >   >   >   >   >   >   >      <
    //o   >   >   >   >   >   >   >   >   >   >   >   >   >   >
    

    Der Begriff sequence point wird ja so nicht mehr verwendet.



  • dot schrieb:

    int erg = ((++x) * (++x));
    

    Es ist allerdings undefiniert, wann genau während der Auswertung dieses Ausdruckes der Inkrement passiert. Das einzige, was garantiert ist, ist, dass nach der Auswertung des ganzen Ausdruckes x um 2 inkrementiert wurde. Das Ergebnis kann daher also 16, 20, 24, 25, 30 oder 36 sein.
    Bei der Variante mit der Funktion gibt es das Problem nicht und das Ergebnis ist daher garantiert 25.
    Wenn du allerdings sowas hättest:

    void blub(int x, int y)
    {
      return x * y;
    }
    
    int x = 4;
    int erg = blub(++x, ++x);
    

    wär das Ergebnis wieder undefiniert...

    Achso verstehe. Das Beispiel mit der Funktion ist auch interessant, das hätte
    ich nicht erwartet.

    Also Aufrufe mit ++x sollte man bei Makros immer sein lassen.
    Wenn das Makro wie nachfolgend lauten würde könnte es dann sein,
    dass der Variablen erg manchmal der Wert 2 und manchmal der Wert 3 zugewiesen wird?
    Gäbe es also hier ein ähnliches Problem wie bei dem QUAD(X) Beispiel?

    #define BLA(x) (x)
    
    int main()
    {
        int a = 2;
        int erg = BLA(++a);
        return 0;
    }
    


  • blupblup schrieb:

    Also Aufrufe mit ++x sollte man bei Makros immer sein lassen.

    Nicht unbedingt, allerdings sind Makros sowieso zu vermeiden. Für sowas generell besser (inline) Funktionen verwenden.

    blupblup schrieb:

    Wenn das Makro wie nachfolgend lauten würde könnte es dann sein,
    dass manchmal der Wert 2 und manchmal der Wert 3 ausgegeben wird?
    Gäbe es also hier ein ähnliches Problem wie bei dem QUAD(X) Beispiel?

    #define BLA(x) (x)
    
    int main()
    {
        int a = 2;
        int erg = BLA(++a);
        return 0;
    }
    

    Nope, da kommt ganz sicher immer 3 raus. Das Problem hat nicht mit dem Makro an sich zu tun, sondern damit, dass du mehrere Inkrements im selben Ausdruck stehen hast...



  • Okay verstehe.
    Danke für die Hilfe.



  • Merk dir einfach die Faustregel: Innerhalb eine Anweisung darf eine Variable max. 1x modifiziert werden. Hälst du dich daran, bist du stets auf der sicheren Seite.



  • Genau wie out sagt, denn genau deshalb ist

    a = f(i++,i);
    

    auch wohl definiert!



  • Decimad schrieb:

    Genau wie out sagt, denn genau deshalb ist

    a = f(i++,i);
    

    auch wohl definiert!

    Nope, ist es nicht, die Auswertungsreihenfolge der Parameter ist nicht definiert. Es ist lediglich garantiert, dass der eine vor dem anderen ausgewertet wird, aber nicht, welcher zuerst.


  • Mod

    out schrieb:

    int x = 4;
    	int erg = ((++x) * (++x));
    

    [...] D.h. erst nach der Anweisung int erg = ((++x) * (++x)); kannst du sicher sein, dass x=6 ist.

    nein. Wenn der Compiler gut optimiert, kommt wahrscheinlich x==5 raus - mit UB ist es allerdings sinnlos, auf irgendein konkretes Ergebnis zu wetten.

    out schrieb:

    Merk dir einfach die Faustregel: Innerhalb eine Anweisung darf eine Variable max. 1x modifiziert werden. Hälst du dich daran, bist du stets auf der sicheren Seite.

    Das vermeidet eine Stolperfalle, hinreichend für wohldefiniertes Verhalten ist das noch lange nicht.

    Decimad schrieb:

    Genau wie out sagt, denn genau deshalb ist

    a = f(i++,i);
    

    auch wohl definiert!

    Leider hat auch dieser Code undefiniertes Verhalten (ich nehme mal an, dass f eine Funktion oder eine Klasse mit geeignetem Konstruktor sein soll)



  • camper schrieb:

    out schrieb:

    Merk dir einfach die Faustregel: Innerhalb eine Anweisung darf eine Variable max. 1x modifiziert werden. Hälst du dich daran, bist du stets auf der sicheren Seite.

    Das vermeidet eine Stolperfalle, hinreichend für wohldefiniertes Verhalten ist das noch lange nicht.

    Hast du ein Beispiel parat, in dem eine Variable innerhalb einer Anweisung nur 1x modifiziert wird und es trotzdem zu UB kommt?



  • out schrieb:

    camper schrieb:

    out schrieb:

    Merk dir einfach die Faustregel: Innerhalb eine Anweisung darf eine Variable max. 1x modifiziert werden. Hälst du dich daran, bist du stets auf der sicheren Seite.

    Das vermeidet eine Stolperfalle, hinreichend für wohldefiniertes Verhalten ist das noch lange nicht.

    Hast du ein Beispiel parat, in dem eine Variable innerhalb einer Anweisung nur 1x modifiziert wird und es trotzdem zu UB kommt?

    i = j++;
    

Log in to reply