Warum sollte man keine versteckten Anweisungen in logischen Ausdrücken ausführen lassen?



  • NineT4 schrieb:

    Vielen Dank für eure Antworten. Demzufolge ist es nur fehleranfälliger Code so zu schreiben oder?

    Anderes Beispiel:

    if (n > 100 && n++ > 100){
       //tue etwas
    }
    

    Das Inkrementieren von n ist hier ein Aufruf mit Nebeneffekt, weil sich der Wert von n ändert, richtig? Demnach wäre es besser den Code so zu schreiben:

    if(n > 100 && n+1 > 100){
       n++;
       //tue etwas
    }
    

    Naja ... wenn n > 100 ist auch n > 100.
    Der 2. Test im 1. Beispiel ist also redundant.
    (Der Wert von n++ ist das was in n VOR dem Inkrementieren steht -- für den Wert danach müsstest du ++n schreiben!)

    Und das 2. Beispiel macht nicht das selbe wie das 1.
    Unter der Annahme dass n ein int o.Ä. ist macht er zwar fast immer das selbe, aber eben nicht immer (n+1 könnte überlaufen).

    Leicht modifiziertes Beispiel:

    if (a > 100 && b++ > 100){
       //tue etwas
    }
    
    // ==>
    
    if (a > 100){
        if (b++ > 100){
           //tue etwas
        }
    }
    
    // ==>
    
    if (a > 100){
        auto const old_b = b;
        b++;
        if (old_b > 100){
           //tue etwas
        }
    }
    
    // bzw. wenn nicht mit Überlauf zu rechnen ist auch einfach
    
    if (a > 100){
        b++;
        if (b > 101){
           //tue etwas
        }
    }
    

    Die "auto" Variante finde ich ehrlich gesagt selbst grässlich. Anhand eines so abstrakten Beispiels kann man aber nicht viel dazu sagen. Ich müsste 'was mit dem Wert und den Tests die da gemacht werden verbinden um sagen zu können welche Variante ich im "Realeinsatz" vorziehen würde.


  • Mod

    hustbaer schrieb:

    SeppJ schrieb:

    Die Begründung gefällt mir nicht. Kurzschlussauswertung wird schließlich oftmals gezielt dafür benutzt, dass man bedingt Ausdrücke mit Nebeneffekten auswerten kann. Das geht natürlich nach hinten los, wenn man nicht weiß, was Kurschlussauswertung ist. Das muss aber jeder Programmierer ohnehin wissen (wenn man mit Sprachen arbeitet, die dieses Feature haben).

    Welche Begründung?

    NineT4 schrieb:

    dass man keine versteckten Anweisungen in logischen Ausdrücken ausführen lassen soll, da es sein kann, dass der betreffende Teilausdruck gar nicht mehr ausgeführt wird.

    Klingt für mich so, als wolle er Überraschungen bei der Kurzschlussauswertung vorbeugen.

    NineT4 schrieb:

    Vielen Dank für eure Antworten. Demzufolge ist es nur fehleranfälliger Code so zu schreiben oder?

    Anderes Beispiel:

    if (n > 100 && n++ > 100){
       //tue etwas
    }
    

    Das Inkrementieren von n ist hier ein Aufruf mit Nebeneffekt, weil sich der Wert von n ändert, richtig? Demnach wäre es besser den Code so zu schreiben:

    if(n > 100 && n+1 > 100){
       n++;
       //tue etwas
    }
    

    Das ist genau, was gemeint ist. Die beiden Codestücke tun unterschiedliche Dinge. Was dir aber nicht klar zu sein scheint, weil du Kurzschlussauswertung noch nicht verstanden hast. Bei den Operatoren || und && (und gewissermaßen auch ? , aber da erwartet man dies sowieso) wird die zweite Seite nicht mehr ausgewertet, wenn das Ergebnis bereits feststeht. Ist also bei && die linke Seite schon false , wird die rechte Seite gar nicht mehr angefasst. Und umgekehrt bei || . Dieses Verhalten ist garantiert und wird auch häufig gezielt benutzt, um Nebeneffekte auf der rechten Seite nur in bestimmten Fällen auszuführen.



  • Sorry Es sollte so aussehen:
    ich habe "n" an Stelle von "m" geschrieben

    if (n > 100 && m++ > 100){
       //tue etwas
    }
    
    if(n > 100 && m+1 > 100){ 
        m++; 
        //tue etwas 
    }
    

    Jetzt habe ich es auch verstanden.

    Die beiden Beispiele sind nur äquivalent wenn der linke Teil der UND-Verknüpfung false ist. der rechte Teil wird dann garnicht berücksichtigt.

    Wenn der linke Teil im 1. Beispiel true ist dann wird die rechte Seite ausgewertet, was bedeutet, dass m inkrementiert wird auch wenn die rechte Seite false ist (Das habe ich nichts gewusst).

    Im 2. Beispiel wird m nicht um 1 erhöht, wenn die Bedingung (true && false) ist, da die Wertveränderung nicht in der Bedingung sondern im Anweisungsblock passiert und dieser wird komplett ignoriert.

    Wenn beides true ist, kann - wie hustbaer gesagt hat - die Variable überlaufen (daran habe ich auch nicht gedacht).

    #include <iostream>
    using namespace std;
    
    int main() {
    	short test = 32767;
    
    	cout << test + 1 << endl; //32768
    	cout << test++ << endl; //32767
    
    	return 0;
    }
    


  • Ich sehe da keinen Überlauf.

    Beachte ++n vs n++



  • #include <iostream>
    #include <limits>
    
    #define printMinMaxT( TYPE ) \
      std::cout << #TYPE << std::endl; \
      std::cout << "min: " << std::numeric_limits<TYPE>::lowest() << std::endl; \
      std::cout << "max: " << std::numeric_limits<TYPE>::max() << std::endl;
    
    int main() {
      printMinMaxT(short)
      return 0;
    }
    
    short
    min: -32768
    max: 32767
    

    🙂

    Nochmal von vorne (edit3 - jetzt aber).
    Es ist nur kein Überlauf da implizit auf int gecastet wird.
    Mit folgenden Zeilen kommt es zum Überlauf:

    short test = 32767;
      std::cout << short(test + 1) << std::endl; // -32768  !!!
      std::cout << short(test++) << std::endl;   //32767
    


  • Beachtet aber nebenbei dass signed überläufe undefiniert sind 😉



  • roflo schrieb:

    Beachtet aber nebenbei dass signed überläufe undefiniert sind 😉

    Ja, ein wunderbar schwachsinniges Relikt...



  • Sind die nicht mittlerweile implementation defined?
    Ich merk mir das nie...



  • Shade Of Mine schrieb:

    Ich sehe da keinen Überlauf.

    Beachte ++n vs n++

    if (n > 100 && n++ > 100)
    

    vs.

    if(n > 100 && n+1 > 100){ 
        n++;
    

    Und die haben nur dann das selbe Verhalten, wenn es bei n+1 keinen Überlauf gibt.


  • Mod

    hustbaer schrieb:

    Sind die nicht mittlerweile implementation defined?
    Ich merk mir das nie...

    Nein, es ist nach wie vor undefined. It's not a bug, it's a feature. So kann man signed Typen aggressiver optimieren als unsigned Typen. In diesem konkreten Beispiel ( if (n>100 && n+1>100) ) kann beispielsweise der Test mit n+1 wegoptimiert werden, wenn n signed ist. Denn man kann davon ausgehen, dass n+1 > n ist. Und wenn es doch zu einem Überlauf kommt, ist es eben undefiniertes Verhalten. Bei einem unsigned int müssen hingegen die Rechnung und der Test tatsächlich ausgeführt werden.

    Das sieht in diesem konkreten Fall natürlich ziemlich schwachsinnig aus, das liegt aber da dran, dass das Beispiel nicht viel her gibt. Es gibt sicherlich bessere Beispiele.



  • Nein, es wurde aus dem Bug ein zweifelhaftes "Feature" gemacht.


  • Mod

    Der echte Tim schrieb:

    Nein, es wurde aus dem Bug ein zweifelhaftes "Feature" gemacht.

    Quelle? Begründung?



  • Du glaubst nicht ernsthaft, dass signed overflow im Standard undefined ist damit Compilerbauer hier "optimieren" können?


  • Mod

    Der echte Tim schrieb:

    Du glaubst nicht ernsthaft, dass signed overflow im Standard undefined ist damit Compilerbauer hier "optimieren" können?

    Doch. Wenn es um 1-Komplement vs. 2-Komplement ginge, hätten sie implementation defined geschrieben. Durch undefined sind ziemlich gute Optimierungen möglich, vor allem was die Lauflänge von Schleifen angeht.



  • Wäre das tatsächlich der Grund, dann wäre es aber nur konsequent wenn unsigned overflow auch undefined wäre.

    Zumal man sich damals über derartige Optimierungen ziemlich sicher keine Gedanken gemacht hat. Denn dann hätte man Dinge wie restrict auch direkt eingeführt. Nein, damals hat man andere Sorgen gemacht, z.B. das (zum Glück mittlerweile entfernte) 31 Zeichen-Limit von Bezeichnern.



  • Was damals der Grund war es ursprünglich so zu machen ist die eine Frage.

    Was der Grund war weswegen es mit C++11 bzw. C++14 nicht geändert wurde ist eine andere Frage.

    Kann der selbe Grund sein, muss aber nicht.



  • NineT4 schrieb:

    Sorry Es sollte so aussehen:
    ich habe "n" an Stelle von "m" geschrieben

    if (n > 100 && m++ > 100){
       //tue etwas
    }
    
    if(n > 100 && m+1 > 100){ 
        m++; 
        //tue etwas 
    }
    

    Das ist nicht das gleiche.

    if (n > 100 )
       m++
       if ( m > 100)
       {
          //tue etwas
       }
    }
    

    Das ist wie das erste Beispiel, aber ganz einfach lesbar.



  • - signed overflow war schon immer UB (im Gegensatz zur signed (integralen) promotion, die schon immer IB war)
    - unsigned overflow war schon immer defined (wrap around), was ich persönlich für den praktischen Gebrauch für Unsinn halte, da hier Überläufe (stillschweigend und standardkonform!) verschleiert werden; ich möchte nicht wissen, wieviele Software in kritischen Bereichen im Umlauf ist, die dieses "Stillschweigen" aus Sorg- oder/und Ahnungslosigkeit "verwenden"
    - der Standard macht (naturgemäß) keine Vorgaben für Implementierungsvarianten geschweige denn Optimierungen
    - der Standard sieht aber sehr wohl prinzipielle Möglichkeiten vor, um solche fragilen Konstrukte sichtbar zu machen, er spricht z.B. von "the result is implementation-defined or an implementation-defined signal is raised" und bei UB spricht er sogar explizit davon, dass eine Implementierung (also ein Compiler) eine UB in eine constraint-violation umwandeln darf, die dann wiederum standardgemäß auch ein "signal" in irgendeiner Form von sich geben muss



  • lesenkönnen schrieb:

    if (n > 100 )
       m++
       if ( m > 100)
       {
          //tue etwas
       }
    }
    

    Das ist wie das erste Beispiel, aber ganz einfach lesbar.

    Mal abgesehen davon dass ein ";" fehlt...

    Nein, ist nicht "wie das erste Beispiel".
    Das Thema war aber eigentlich schon durch.



  • hustbaer schrieb:

    lesenkönnen schrieb:

    if (n > 100 )
       m++
       if ( m > 100)
       {
          //tue etwas
       }
    }
    

    Das ist wie das erste Beispiel, aber ganz einfach lesbar.

    Mal abgesehen davon dass ein ";" fehlt...

    Nein, ist nicht "wie das erste Beispiel".
    Das Thema war aber eigentlich schon durch.

    Und eine { fehlt auch.
    Aber

    #include <iostream>
        using namespace std;
    
        int main() {
        	int n = 180;
        	int m = 80;
        	if (n > 100 && m++ > 100){
           		//tue etwas
        	}
        	std::cout << "n: " << n << " m: " << m;
        	// your code goes here
        	return 0;
        }
    

    n: 180 m: 81

    #include <iostream>
        using namespace std;
    
        int main() {
        	int n = 180;
        	int m = 80;
        	if(n > 100 && m+1 > 100){
        	   m++;
            	//tue etwas
        	}
        	std::cout << "n: " << n << " m: " << m;
        	// your code goes here
        	return 0;
        }
    

    n: 180 m: 80

    #include <iostream>
        using namespace std;
    
        int main() {
        	int n = 180;
        	int m = 80;
        	if (n > 100 ) {
           		m++;
           		if ( m > 100)
           		{
              		//tue etwas
           		}
        	}
        	std::cout << "n: " << n << " m: " << m;
        	// your code goes here
        	return 0;
        }
    

    n: 180 m: 81

    Der Compiler macht doch aus jedem && wieder ein geschachteltes if.
    Zeigt ein Beispiel wo bei 1 und 3 nicht das gleiche raus kommt.


Anmelden zum Antworten