Postinkrementoperator bei If-Anweisung



  • Jo, if (!a++) ist ein ausreichender Grund für ein paar ordentliche Watschen.



  • X.DarkForce.X schrieb:

    Ups! Mir ist grade eingefallen, ich könnt das mit dem SizeOf-Operator lösen

    int a = 0;
    if( !sizeof(a) )
    	b = ++a;
    

    wäre mein Vorschlag.



  • EOP schrieb:

    X.DarkForce.X schrieb:

    Ups! Mir ist grade eingefallen, ich könnt das mit dem SizeOf-Operator lösen

    int a = 0;
    if( !sizeof(a) )
    	b = ++a;
    

    wäre mein Vorschlag.

    Wie kommt man denn von sizeof() auf a++?
    Ernstgemeinte Frage.....



  • Nun, zu seiner Verteidigung sei gesagt dass einfach ausprobieren ist solchen Fällen ja unter Umständen gar nichtmal so einfach ist da es bei komplizierteren Ausdrücken gerne auch mal implementation-defined wird sodass einfaches ausprobieren einen nicht weiterbringt oder sogar total fehlgeleitete Schlüsse ziehen lässt 😉



  • Von Ausprobieren auf Allgemeingültigkeit zu schließen, ist gefährlich - ganz besonders, wenn es um Sequenzpunkte geht. Jürgen Wolf hat auf diese Weise ganze Bücher voller Blödsinn produziert.

    Sequenzpunkte bestimmen die Reihenfolge, in der Programmteile ausgeführt werden. Einfachstes Beispiel:

    foo();
    /* Sequenzpunkt */
    bar();
    

    ...so dass foo() vor bar() ausgeführt wird. Gibt es zwischen zwei Ausdrücken keinen Sequenzpunkt, so ist nicht definiert, in welcher Reihenfolge sie ausgeführt werden und wann ihre Nebeneffekte eintreten. Das ist wichtig - beispielsweise ist bei einem Funktionsaufruf nicht definiert, in welcher Reihenfolge die Parameter ausgewertet werden, und hätte ich

    foo() + bar();
    /* Sequenzpunkt */
    

    geschrieben, wäre zwischen foo() und bar() kein Sequenzpunkt, sondern erst danach - ob zuerst foo() oder bar() ausgeführt wird, ist nicht definiert.

    Ganz wild ist es mit ++ und -- - im obrigen Beispiel gibt es innerhalb von foo und bar Sequenzpunkte, so dass bei Auswertung des + zumindest klar ist, dass Nebeneffekte von foo() und bar() eingetreten sind, aber schreibst du

    int a = 0;
    int b = a++ + a++;
    

    dann darf der Compiler daraus durchaus auch b == 0 machen. Es gibt keinen Sequenzpunkt im Ausdruck a++ + a++, also können die Nebeneffekte der a++-Ausdrücke, wenn es der Compiler so will, erst eintreten, nachdem der +-Operator bereits ausgewertet wurde.

    Daraus ergibt sich in der Praxis, dass in der Kürze zwar Würze liegt, es aber wichtiger ist, sich klar auszudrücken.

    Sequenzpunkte sind:

    1. Der Aufruf einer Funktion, nachdem alle Parameter ausgewertet wurden. D.h.

    #include <stdio.h>
    
    int a = 1;
    
    void foo(int x) {
      printf("%d\n, a + x);
    }
    
    int main(void) {
      foo(a++);
      return 0;
    }
    
    /* Ausgabe:
     *
     * 3
     */
    

    ...da bei Funktionseintritt der Nebeneffekt von a++ in main eingetreten ist, dementsprechend darin a == 2, x == 1. Dagegen wäre bei

    void foo(int x, int y) { }
    
    /* ... */
    
    foo(++a, ++a);
    

    nicht definiert, welche Werte x und y in der Funktion haben.

    2. Nach der Auswertung des ersten Arguments Operatoren &&, ||, ?: (ternär) und , (Komma). D.h.

    int a = 0, b;
    
    b = ++a && a; /* b == 1 */
    
    a = 0;
    b = a++ || a; /* b == 1 */
    
    a = 0;
    b = a++ ? a : 2; /* b == 1 */
    
    a = 0;
    b = a++, a; /* b == 1*/
    

    3. Das Ende eines full declarators, d.h.

    int a = 0;
    int b = a++;
    int c = a; /* c == 1 */
    

    Nicht definiert ist der Wert von s.b und s.c bei

    int a = 0;
    struct { int b, c; } s = { a++, a++ };
    

    4. Das Ende eines vollständigen Ausdrucks, d.h. praktisch alle Semikola, die Bedingungen einer if- oder switch-Abfrage, einer while- bzw. do-Schleife, alle drei Terme im Kopf einer for-Schleife, und der Ausdruck in einem return-Statement. D.h.

    a++;
    /* Effekt hier eingetreten */
    
    if(a++) {
      /* Effekt hier eingetreten */
    }
    
    switch(a++) {
    default:
      /* Effekt hier eingetreten */
    }
    
    while(a++) {
      /* Effekt hier eingetreten, bei jedem Schleifendurchlauf */
    }
    /* Effekt hier eingetreten nach Schleifenende */
    
    do {
      /* Effekt ab zweitem Schleifendurchlauf hier eingetreten,
         bei jedem Schleifendurchlauf*/
    } while(a++);
    /* Effekt hier eingetreten nach Schleifenende */
    
    for(a++; b++; c++) {
      /* Effekt von a++ hier bei erstem Schleifeneintritt eingetreten. 
         Effekt von b++ bei jedem Schleifendurchlauf eingetreten
         Effekt von c++ ab dem zweiten Schleifendurchlauf bei jedem
                        Schleifendurchlauf eingetreten
       */
    }
    /* Effekt des letzten c++ und b++ nach Schleifenende eingetreten */
    

    Für return fällt mir gerade kein Beispiel ein, aber wenn es da mal Zweifel geben sollte, kannst du dich darauf verlassen, dass der Ausdruck, den du zurückgibst, ausgewertet wird und alle seine Nebeneffekte eingetreten sind, bevor er zurückgegeben wird.

    5. Direkt bevor eine Funktion der Standardbibliothek zurückkehrt.

    6. Nach der Verarbeitung von Umwandlungsspezifikatoren (gemeint sind *printf und *scanf).

    7. Direkt vor und nach jedem Aufruf einer an qsort oder bsearch übergebenen Vergleichsfunktion.

    Bei den letzten drei handelt es sich wohl um Einschränkungen für Compiler- und Standardbibliotheksentwickler.



  • diese sequenzpunkte liegen imho in c/c++ an der falschen stelle und führen zu total unerwarteten ergebnissen.


  • Mod

    seldon schrieb:

    Nicht definiert ist der Wert von s.b und s.c bei

    int a = 0;
    struct { int b, c; } s = { a++, a++ };
    

    Das sollte eigentlich definiert sein. Jedenfalls sehe ich keinen signifikanten Unterschied zu

    int a = 0;
    int b = a++, c = a++;
    


  • seldon schrieb:

    Sequenzpunkte sind: [...]

    Schön, mal einen ausführlichen Überblick zu haben. Punkt 6-7 kannte ich nicht, wie ist Punkt 5 zu verstehen? Der Aufrufer der Standardbibliotheksfunktion kann sich ja teilweise nicht auf die Aufrufreihenfolge verlassen, was bringt also ein Sequenzpunkt im aufrufenden Ausdruck?

    __-- schrieb:

    diese sequenzpunkte liegen imho in c/c++ an der falschen stelle und führen zu total unerwarteten ergebnissen.

    Wenn dein Beitrag nicht als Getrolle aufgefasst werden soll, musst du schon etwas konkreter werden. Was findest du denn "total unerwartet"?



  • Nexus schrieb:

    __-- schrieb:

    diese sequenzpunkte liegen imho in c/c++ an der falschen stelle und führen zu total unerwarteten ergebnissen.

    Wenn dein Beitrag nicht als Getrolle aufgefasst werden soll, musst du schon etwas konkreter werden. Was findest du denn "total unerwartet"?

    Dieser __-- ist nur ein ungebildeter C-Troll. Einfach ignorieren.



  • Also wenn ich mir diese Liste hier ansehe, dann ist die Reihenfolge und somit die Nebeneffekte sehr Wohl definiert. Oder meinst du etwas anderes?

    http://www.highscore.de/cpp/einfuehrung/operatoren.html#präzedenztabelle



  • int a = 0; 
    int b = 0;
    if(a++ || b--) 
       b = a;
    
    std::cout << a << "\n";
    std::cout << b << "\n";
    
    if(a++ && b--)
       b = 15;
    
    std::cout << a << "\n";
    std::cout << b << "\n";
    

    Ist das Ergebnis definiert oder undefiniert?
    Ich bekomme: 1, -1, 2, 15



  • int a = 0;
    int b = 0;
    if(a++ || b--)		// a == 0 || b == 0; a++, b--
       b = a;		// wird ignoriert, da false or false == false
    
    std::cout << a << "\n";	//	a == 1
    std::cout << b << "\n";	//	b == -1
    
    if(a++ && b--)		// a == 1 && b == -1; a++, b--
       b = 15;		// da true && true == true
    
    std::cout << a << "\n";	//	a == 2
    std::cout << b << "\n"	//	b == 15
    

    Und damit definiert. Wobei der Code auch noch gut zu lesen ist.


  • Mod

    Nick Unbekannt schrieb:

    Und damit definiert. Wobei der Code auch noch gut zu lesen ist.

    edit: Siehe unten. Es ist nicht deswegen definiert. Auch bei undefiniertem Verhalten bekommt man irgendein Ergebnis. Aber es kann von Compiler zu Compiler unterschiedlich sein.

    Ein Beispiel dafür wäre

    void foo(int a, int b)
    {
     cout<<a<<b;
    }
    
    //...
    
    int i=1;
    foo(i++,i++);
    

    12 oder 21?

    Das Ergebnis des Beispiels von WirklichSchlechterCode ist aber soweit ich das sehe vollständig definiert, da || und && als Sequenzpunkte fungieren und zudem die Reihenfolge der Auswertung (bzw. Nichtauswertung) bei diesen Operatoren genau festgelegt ist.

    edit: @Nick Unbekannt: Im zweiten Lesen merke ich, ich habe dich missverstanden. Du meintest das gleiche wie ich, warum der Code definiertes Verhalten zeigt. Vergiss daher meine Eingangsbemerkung.



  • Nick Unbekannt schrieb:

    Also wenn ich mir diese Liste hier ansehe, dann ist die Reihenfolge und somit die Nebeneffekte sehr Wohl definiert. Oder meinst du etwas anderes?

    http://www.highscore.de/cpp/einfuehrung/operatoren.html#präzedenztabelle

    Andere Baustelle, die Vorrangregeln sagen nichts über die Abarbeitungsreihenfolge aus.



  • SeppJ schrieb:

    12 oder 21?

    Sollte auch definiert sein.
    http://en.wikipedia.org/wiki/X86_calling_conventions



  • Nick Unbekannt schrieb:

    Sollte auch definiert sein.
    http://en.wikipedia.org/wiki/X86_calling_conventions

    Das hat damit nichts zu tun (davon abgesehen, dass auch unterschiedliche Calling-Conventions verwendet werden können). Es ist implementation-defined, in welcher Reihenfolge die Inkrementierungen abgearbeitet werden.

    Deshalb gibt es auch auf der Seite von boost::shared_ptr einen Hinweis unter "Best Practices" im Bezug auf temporäre shared_ptr -Objekte als Funktionsargumente.



  • Vielen vielen herzlichen Dank für all die guten Beiträge! 👍
    Vorallem der von seldon war klasse!!!


Anmelden zum Antworten