ausführungsreihenfolge



  • da es ja einen unterschied macht ob man x / y * z von links nach rechts oder von rechts nach links durchrechnet würde ich gerne wissen, ob der standard wirklich 100%ig garantiert dass hier die operationen von links nach rechts durchgeführt werden oder ob ich zusätzliche klammern setzen muss. denn z.b. von if-abfragen wie z.b. if(x || y) ist es meinem wissen nach nicht garantiert, dass x vor y ausgewertet wird, daher bin ich verunsichert...



  • Doch solange die entsprechenden Operatoren nicht überladen worden sind, ist es garantiert, dass x vor y ausgewertet wird und y nur dann ausgwertet wird wenn x false ist.
    Das gleiche ist auch bei &&, nur wenn x true ergibt, wird y ausgwertet. Genauso ist die Ausführungsreihenfolge bei x,y garantiert, hier wird aber immer beides ausgewertet.
    Und x/y * z wird von links nach rechts ausgewertet.



  • und wenn sie überladen worden sind (was man annehmen muss wenn man z.b. ein funktionstemplate schreibt) dann ist es etwa nicht mehr so? wie inkonsistent...



  • Logische Verknüpfungen (&& und ||) überladt man nicht!

    Warum? Weil man es nicht macht!

    Gibt dazu einiges von Meyers, Sutter dürfte das auch mal erwähnt haben und im Web findet man sicher auch viel dazu.
    Und der Grund ist, wie du schon sagst, dass grundlegende Semantiken geändert werden.



  • æäæäæäæäæ schrieb:

    und wenn sie überladen worden sind (was man annehmen muss wenn man z.b. ein funktionstemplate schreibt) dann ist es etwa nicht mehr so? wie inkonsistent...

    Das ist nicht inkonsisten. Es gibt eben keine garantierte Reihenfolge bei der Auswertung von Funktiosnparametern.



  • was man annehmen muss wenn man z.b. ein funktionstemplate schreibt

    Noe, man fordert einfach, dass Vergleiche/Tests keine Seiteneffekte hat. Dann spielt es keine Rolle, in welcher Reihenfolge sie ausgewertet werden.


  • Mod

    æäæäæäæäæ schrieb:

    da es ja einen unterschied macht ob man x / y * z von links nach rechts oder von rechts nach links durchrechnet würde ich gerne wissen, ob der standard wirklich 100%ig garantiert dass hier die operationen von links nach rechts durchgeführt werden oder ob ich zusätzliche klammern setzen muss. denn z.b. von if-abfragen wie z.b. if(x || y) ist es meinem wissen nach nicht garantiert, dass x vor y ausgewertet wird, daher bin ich verunsichert...

    Das sind zwei verschiedene Fragen.
    1. Der Standard garantiert in der Regel nicht, das die einzelnen Operanden in einer bestimmten Reihenfolge ausgeführt werden, ausser dass nat. die Operanden erst einmal ermittelt werden müssen, bevor ein Operator selbst ausgewertet wird. Es gibt ein paar wichtigen Ausnahmen bezogen auf bestimmte (nicht überladene) Operatoren && || , ?: Abgesehen vom Kommaoperator liegt das daran, dass es bei diesen Operatoren eben nicht immer erforderlich ist, erst alle Operanden zu ermitteln, um das Ergebnis bestimmen zu können. Und es gilt in diesem Fall als vorteilhaft (Effizienz!), Operanden nicht auszuwerten, wenn diese das Ergebnis nicht beeinflussen können.

    2. Die Assoziativität von Operatoren ist vorgegeben und unveränderlich.
    x/y*z ist immer äquivalent zu (x/y)*z
    Der linke Operand der Multiplikation ist also immer das Ergebnis der Division und nicht nur y. Das hat aber nur indirekt etwas mit der Auswertungsreihenfolge zu tun (erst Operanden, dann Operator s.o.)



  • Also könnte

    #include <iostream>
    using namespace std;
    
    int x=3;
    int neueZahl(){
        ++x;
        return x;
    }
    
    int main(){
        int y=neueZahl()*(neueZahl()-neueZahl());
        cout<<y<<'\n';
    }
    

    sechs verschiede Ergebniosse liefern.



  • æäæäæäæäæ schrieb:

    und wenn sie überladen worden sind (was man annehmen muss wenn man z.b. ein funktionstemplate schreibt) dann ist es etwa nicht mehr so? wie inkonsistent...

    Der Grund ist, dass es nicht erlaubt ist, dass in einer Anweisung dieselbe Variable mehrfach geschrieben wird.

    int i(0);
    i = i++; //UB weil i doppelt beschrieben wird
    
    struct J{
        J operator++(int){
    		return *this;
        }
    } j;
    
    j = j++; //nicht UB weil j nicht doppelt beschrieben wird
    

    Die j-Variante wird deshalb nicht doppelt beschrieben, weil die letzte Zeile nur hübsche Syntax dafür ist:

    j.operator=(j.operator++(0));
    

    Und jetzt sieht man, dass das Funktionsaufrufe sind. Weiterhin ist garantiert, dass eine Funktion nicht aufgerufen wird bevor ihre Argumente ausgewertet wurden. Also gibt es hier keine Möglichkeit etwas fehl zu interpretieren. i = f(i++); ist auch völlig ok, denn i++ muss vor f ausgewertet werden und f vor der Zuweisung.

    Meiner Meinung nach sollte man aber int i = 0; i = i++; definieren. So einen kleinen Satz im Standard wo steht "Die Ausführungsreihenfolge eines primitiven Datentyps ist gleich eines nutzerdefinierten Datentyps." und schon wärs nicht mehr UB.



  • Skym0sh0 schrieb:

    Logische Verknüpfungen (&& und ||) überladt man nicht!

    Warum? Weil man es nicht macht!

    Gibt dazu einiges von Meyers, Sutter dürfte das auch mal erwähnt haben und im Web findet man sicher auch viel dazu.
    Und der Grund ist, wie du schon sagst, dass grundlegende Semantiken geändert werden.

    Tolle Begründung. Grundlegende Semantiken können ebenso bei jedem anderen Operator geändert werden.

    Die ganze "Argumentation" basiert darauf, dass du annimmst, die überladenen Logikoperatoren geben bool zurück. Das ist aber typischerweise gerade nicht der Fall. Beispiele sind Expression Templates oder andere Situationen, in denen das Ergebnis nicht unmittelbar berechnet wird.


  • Mod

    Der Grund ist, dass es nicht erlaubt ist, dass in einer Anweisung dieselbe Variable mehrfach geschrieben wird.

    Das ist nicht der Grund.

    Zuerst muss der Begriff von sequenced und unsequenced geklärt werden.
    Zwei Auswertungen (von bspw. Ausdrücken) A und B sind sequenced, wenn A immer vor B ausgewertet wird.

    Given any two evaluations A and B, if A is sequenced before B, then the execution of A shall precede the execution of B. If A is not sequenced before B and B is not sequenced before A, then A and B are unsequenced.
    [ Note: The execution of unsequenced evaluations can overlap. — end note ]

    Man beachte auch die Notiz.
    Nun heißt es:

    The value computations of the operands of an operator are sequenced before the value computation of the result of the operator.

    Alle Operanden eines Operators werden immer vor dem Operator-Ausdruck selbst ausgewertet. Das trifft aber nur auf Wertberechnungen zu, nicht auf Seiteneffekte!
    Außerdem:

    When calling a function (whether or not the function is inline), every value computation and side effect associated with any argument expression, or with the postfix expression designating the called function, is sequenced before execution of every expression or statement in the body of the called function.
    [ Note: Value computations and side effects associated with different argument expressions are unsequenced. — end note ]

    Es werden also alle Argumente einer Funktion, auch Default-Argumente, vor dem Aufruf ausgewertet, die Reihenfolge ist aber nicht festgelegt - und sie können sich auch überlappen.

    Hier ist dann die goldene Regel bezüglich Skalaren:

    If a side effect on a scalar object is unsequenced relative to either another side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined.

    Ein Klassiker bezüglich sequencing ist

    i = i++;
    

    Hier gibt es zwei Seiteneffekte: Zuweisung und Inkrementieren. i++ wird zwar vor der Zuweisung berechnet, das nachträgliche Inkrementieren ist aber ein noch ausstehender Seiteneffekt.
    Hier ist nicht festgelegt, ob i nach oder vor der Zuweisung inkrementiert wird, es ist also unsequenced. Daher: UB.

    Anderes Beispiel:

    i = (i++, i++, 8);
    

    Das ist definiertes Verhalten, da in einer Liste von Ausdrücken jeder Seiteneffekt vor jeder Wertberechnung und jedem Seiteneffekt des nächsten Ausdrucks geschieht. Der Wert von i nach der Auswertung dieser Zuweisung ist 8.
    Ideone: https://ideone.com/PhufJP

    Der Ausdruck in diesem Code:

    f( i++, i );
    

    ist UB. Der Grund ist offensichtlich: Die value computation von i als zweites Argument und das nachträgliche Inkrementieren von i++ (welches ein Seiteneffekt ist) sind nicht sequenced.

    Es gab schon Beispiele, welche nwp3s Pauschalregel widersprachen.

    Hier ist noch einer:

    f(a, (t=3, t=2), c);
    

    Das ist definiert. Hingegen ist

    f(a, (t=3, t=2), t);
    

    UB.


Anmelden zum Antworten