Heftige Reaktion bei kleinem Schnitzer...


  • Mod

    Eisflamme schrieb:

    Ich finde es aber ehrlich gesagt angemessen, dass er nicht kompiliert. Es gibt doch wirklich keinen Fall, wo das sinnvoll sein könnte.

    int foo(int x)
    {
     if (x>0) return 1;
     if (x<0) return 0;
    }
    

    Fehler? Warnung? Korrektes Programm?

    void foobar()
    {
     exit(0);
    }
    
    int bar()
    {
     foobar();
    }
    

    Fehler? Warnung? Korrektes Programm?



  • SeppJ schrieb:

    int foo(int x)
    {
     if (x>0) return 1;
     if (x<0) return 0;
    }
    

    Fehler? Warnung? Korrektes Programm?

    warning C4715: 'foo' : not all control paths return a value

    SeppJ schrieb:

    void foobar()
    {
     exit(0);
    }
    
    int bar()
    {
     foobar();
    }
    

    Fehler? Warnung? Korrektes Programm?

    error C4716: 'bar' : must return a value



  • SeppJ:
    Wenn der Compiler ermitteln kann, dass kein return erfolgt, sollte er imo einen Fehler ausgeben. Wenn er es nicht mit Sicherheit sagen kann wie in Deinem Programm, dann sollte er die Schnauze halten bzw. eben eine Warnung ausgeben.

    Denn in ersterem Fall kann es imo keinen sinnvollen Fall geben. Funktionen, die einen Rückgabetyp haben, müssen etwas zurückgeben. Was sollte denn die Semantik einer Nicht-Rückgabe sein, wenn das Verhalten undefiniert ist?

    Edit: Wie out zeigt macht es der MSVC auch genau so, wie ich es für sinnvoll erachte.


  • Mod

    out schrieb:

    SeppJ schrieb:

    int foo(int x)
    {
     if (x>0) return 1;
     if (x<0) return 0;
    }
    

    Fehler? Warnung? Korrektes Programm?

    warning C4715: 'foo' : not all control paths return a value

    Ok, meinetwegen. Nervige Meldung, da ich die Funktion doch wissentlich nie mit 0 aufrufe.

    SeppJ schrieb:

    void foobar()
    {
     exit(0);
    }
    
    int bar()
    {
     foobar();
    }
    

    Fehler? Warnung? Korrektes Programm?

    error C4716: 'bar' : must return a value

    Dies ist aber ein reguläres Programm, welches nun nicht mehr compiliert. In allen erdenklichen Fällen wäre der Ablauf dieses Programms wohldefiniert.

    Eisflamme schrieb:

    Was sollte denn die Semantik einer Nicht-Rückgabe sein, wenn das Verhalten undefiniert ist?

    Fragst du gerade nach der Semantik von undefiniertem Verhalten? 😕

    Wenn der Compiler ermitteln kann, dass kein return erfolgt, sollte er imo einen Fehler ausgeben. Wenn er es nicht mit Sicherheit sagen kann wie in Deinem Programm, dann sollte er die Schnauze halten bzw. eben eine Warnung ausgeben.

    Der Punkt meiner Beispielprogramme ist: Das ist praktisch unmöglich festzustellen! Bei all den Schweinereien die in C++ erlaubt sind, könnte sogar selbstveränderlicher Code vorkommen oder ein nebenläufiges Programm funkt in der Funktion irgendwie dazwischen. Das ist sicherlich der Grund, wieso der Standard so formuliert ist.



  • Und welchen Sinn ergibt eine Funktion, die unter gar keinen Umständen etwas zurückgibt? Genau, keinen.


  • Mod

    Eisflamme schrieb:

    Und welchen Sinn ergibt eine Funktion, die unter gar keinen Umständen etwas zurückgibt? Genau, keinen.

    Vielleicht muss sie eine bestimmte Schnittstelle erfüllen?

    void needs_callback(int (*foo)())
    {
     int i = foo();
    }
    
    int just_for_debugging()
    {
     std::cerr << "Callback called! Exiting now!\n";
     exit(0);
    }
    
    int main()
    {
     needs_callback(&just_for_debugging);
    }
    

    Warnung? Fehler? Korrektes Programm?

    Soll ich weiter machen mit den Beispielen? Es ist Sonntag und ich habe Zeit 🕶



  • Im Endeffekt ist das wieder so ein Teil von C++, wo der Programmierer halt die Verantwortung in die Hände gedrückt bekommt. Wenn jemand bei jedem kleinsten Fehler Fehlermeldungen braucht, weil er sonst die Fehler nicht findet, hat bei C/C++ nix zu suchen. IMO.

    Außerdem müsste man noch definieren, wann ein Fehler und Wann eine Warnung kommt, was dann wieder in einem Krieg ausarten würde. 😃

    Wie auch immer, man sieht: VC++ macht es falsch (nicht standardkonform), und dazu (im obigen Fall) auch noch unsinnig.



  • SeppJ:
    Ja und wenn der Rückgabewert nicht von dem, der die Schnittstelle erzwingt, verwertet wird, ist die Schnittstelle wohl nicht besonders schön designed. Liegt das in eigener Hand, würde ich sie verbessern. Liegt das in fremder Hand kann man sich doch gar nicht so sicher sein, was mit dem Rückgabewert passiert. Und dann sollte man wohl dafür Sorge tragen, dass auch ein Wert (und sei es Dummy) zurückgegeben wird.

    Wenn ich eine Funktion mit Rückgabetyp habe, schreibe ich dort auch ein return rein. Und ich tue das auch dann, wenn ich weiß, dass der Rückgabewert nicht verwendet wird. Es hat doch keinen praktischen Vorteil es nicht zu tun, außer dass man höchstens unnötigen Code hinschreibt. Ist aber die Schnittstelle schon so entwickelt, dass Rückgabe erforderlich ist, so liegt der Designfehler hier nicht darin ein unnötiges return zu schreiben, sondern in meinen Augen in der Schnittstelle.

    Hast Du ein praktisches Beispiel (muss ja kein Code sein), in welchem eine Rückgabetyp einer Funktion bei einer Schnittstelle gefordert wird, obwohl man guten Gewissens das return auslassen kann?



  • Dafuer gibts doch jetzt [noreturn]. Ergo gueltiges Programm.


  • Mod

    Eisflamme schrieb:

    Hast Du ein praktisches Beispiel (muss ja kein Code sein), in welchem eine Rückgabetyp einer Funktion bei einer Schnittstelle gefordert wird, obwohl man guten Gewissens das return auslassen kann?

    main.

    Kellerautomat schrieb:

    Dafuer gibts doch jetzt [noreturn]. Ergo gueltiges Programm.

    Danke, das bringt mich auf noch eine Idee:

    int does_not_return()
    {
     for (;;);
    }
    

    Warnung? Fehler? Gültiges Programm?

    (Dieser Fall ist insofern interessant, da der Compiler annehmen darf, dass nebeneffektfreie Endlosschleifen doch terminieren, aufgrund einer obskuren Klausel im Standard, die fast niemand kennt und die ich gerade nicht suchen mag. Falls das zu verwirrend ist, möge man sich einfach eine Ausgabe in der Schleife denken :p )



  • SeppJ schrieb:

    Eisflamme schrieb:

    Hast Du ein praktisches Beispiel (muss ja kein Code sein), in welchem eine Rückgabetyp einer Funktion bei einer Schnittstelle gefordert wird, obwohl man guten Gewissens das return auslassen kann?

    main.

    Ich verteidige Eisflammes Meinung ja nur ungern :p , aber das ist doch eine vom Standard definierte Ausnahme.
    Ich hab was zum streiten:

    int f()
    {
        return f();
    }
    

    Warnung, Fehler, ...?



  • SeppJ:
    Wenn Du schon main sagen musst, hast Du offensichtlich keins. 🙄

    Das Programm finde ich genau so sinnlos. Die Funktion hat offensichtlich einen Rückgabetypen, also ist irgendwo angedacht, dass sie etwas zurückgibt. Somit sollte sie das eben auch tun. Diese obskure und anscheinend auch versteckte Klausel bestärkt das doch nur.



  • ack Eisflamme. Sogar noch mehr. Jede Funktion die nicht auf jedem return path etwas zurück gibt, sollte nicht kompilieren. (Außer bei void natürlich.) Darüber dass "ich rufe die Funktion wissentlich nur mit bestimmten Werten auf" völliger Quatsch ist, müssen wir glaube ich gar nicht reden. Und selbst wenn das so wäre, tut ein dummy return nicht weh. Das ist im Prinzip genau die gleiche Situation wie const_cast. Sollte ein Programm das etwas über einen const void* verändert kompilieren? Nein, natürlich nicht. Und wenn man es doch mal aus irgendwelchen obskuren gründen braucht, nimmt man halt einen cast bzw. in diesem Fall ein dummy return.

    @Sone: Warnung. Ist halt wie eine Endlosschleife.


  • Mod

    Eisflamme schrieb:

    SeppJ:
    Wenn Du schon main sagen musst, hast Du offensichtlich keins. 🙄

    Wieso? Das ist ein exzellentes Beispiel, denn:

    Sone schrieb:

    Ich verteidige Eisflammes Meinung ja nur ungern :p , aber das ist doch eine vom Standard definierte Ausnahme.

    Wieso ist das wohl so?
    Weil der Rückgabewert von main schon seit 20 Jahren fast niemanden mehr interessiert.

    Das Programm finde ich genau so sinnlos. Die Funktion hat offensichtlich einen Rückgabetypen, also ist irgendwo angedacht, dass sie etwas zurückgibt. Somit sollte sie das eben auch tun.

    Und wenn es die main-Funktion wäre? (Nehmen wir mal C89, wo es in main kein implizites return 0 gibt):

    int main()
    {
     for(;;)
      puts("Dieses Programm läuft für immer\n");
    
     return 0; // Muss das hier also hin?
    }
    

    Dieses letzte Beispiel richtet sich auch an cooky451 mit seiner Meinung zum Dummy-Return.



  • SeppJ schrieb:

    Dieses letzte Beispiel richtet sich auch an cooky451 mit seiner Meinung zum Dummy-Return.

    Selbstverständlich wäre das dann so sinnvoll. Ob ein Programm das unter keinen Umständen terminiert so sinnvoll ist, ist eine andere Frage. Aber es macht natürlich mehr Sinn den Programmierer zu zwingen sich Gedanken über den return Wert zu machen, als diese eine Zeile zu sparen.



  • Für main, bei dem Rückgabewert definiert ist:
    Es ergibt doch jetzt überhaupt keinen Sinn sich die Frage zu stellen, ob der Compiler bei Weglassen des returns in der main meckern sollte oder nicht. Vom Standard ist explizit vorgeschrieben, dass das Weglassen nicht zu undefiniertem Verhalten führt, sondern das Verhalten ist definiert. Gäbe es default-Rückgabewerte, hätte ich auch nichts gegen das fehlende return.

    Für main, bei dem Rückgabewert undefiniert ist:
    Das ändert nichts an meiner Argumentation. In >99% der Fälle, wenn ein return dort nicht steht, obwohl der Rückgabetyp aussagt, dass es dort stehen sollte, ist das nicht eine Reaktion auf eine vorherige Endlosschleife und somit wohlüberlegt, sondern ein Fehler.

    Ich wiederhole nochmal, wieso ich finde, dass ein return erzwungen sein sollte. Das beruht auf den möglichen "Fehlern", die man mit return begehen kann:

    1. Lässt man das return weg, wenn ein Rückgabetyp angegeben ist und dieser weiter verwendet wird, so begeht man einen erheblichen Fehler; der Code ist syntaktisch korrekt, semantisch aber verheerend
    2. Lässt man das return nicht weg, wenn ein Rückgabetyp angegeben ist und dieser nicht weiter verwendet wird, so begeht man den Fehler, dass man eine Zeile zu viel Text geschrieben hat; gleichzeitig gibt man aber, falls der Rückgabewert potenziell irgendwann einmal benutzt wird, einen Wert an, der vielleicht später interessant sein könnte.

    Der Fehler bei 1 ist also fatal, der bei 2 in meinen Augen lächerlich gering, wenn überhaupt vorhanden. Und die Szenarien, in denen man 2) begeht, erscheinen mir nur im Kontext eines fehlerhaften Designs. Ob sich MSVC also mit dem Verhalten exakt an den Standard hält oder nicht, damit werden Fehler vermieden und realistische Einschränkungen nicht geschaffen (diese gestellten Beispiele, bei denen ich immer auch ein return zurückgeben würde, sind in meinen Augen alles andere als realistisch). Daher befürworte ich die Fehlermeldung.



  • @ Eisflamme
    Bitte ordentlich lesen

    SeppJ schrieb:

    (Nehmen wir mal C89, wo es in main kein implizites return 0 gibt)



  • Ändert in meinen Augen nichts, hab's aber ergänzt, danke.



  • Sone schrieb:

    out schrieb:

    Sone schrieb:

    GCC.

    Dann mal +1 für MSVC 😃

    Lol. Dafür das er mit dem Standard inkompatibel ist? 👎

    GCC tut nur seine Pflicht :p

    Undefiniertes Verhalten erlaubt auch, das Programm gar nicht erst zu übersetzen. Siehe Standard:

    1.3.24 [defns.undefined]
    […]
    behavior for which this International Standard imposes no requirements
    [ Note: […] Permissible undefined behavior
    ranges from […] to terminating a translation or execution (with the issuance of a diagnostic message).
    […].
    — end note ]


  • Mod

    cooky451 schrieb:

    Ob ein Programm das unter keinen Umständen terminiert so sinnvoll ist, ist eine andere Frage.

    Frag mal dein Betriebssystem, wann es sich beendet. (Zumindest bevor es Systeme gab, die den PC mittels Soft-Off tatsächlich ausschalten können)


Anmelden zum Antworten