finde den Fehler



  • SeppJ schrieb:

    Drücke ich mich so unklar aus?

    Nein, nicht du, der Standard. In dem Fall sehe ich den Fehler doch nicht.



  • Und soweit ich weiß, ist es undefiniert, ein Zeichen in einen Stream zu putback()-en, wenn man vorher noch keins rausgenommen hat...


  • Mod

    Kellerautomat schrieb:

    Und soweit ich weiß, ist es undefiniert, ein Zeichen in einen Stream zu putback()-en, wenn man vorher noch keins rausgenommen hat...

    Man hat ja auch keines rausgenommen, schließlich ist die Aktion gescheitert. Dann scheitert auch putback, da zwischenzeitlich kein clear erfolgt.

    War das Lesen jedoch erfolgreich, dann ist auch das (dann funktionierende) putback wohldefiniert.



  • Ok, so gut kenne ich mich mit den IO-Streams nicht aus. Dann ist der Fehler, dass der Wert von __x im Fehlerfall verändert wird, nämlich auf den undefinierten Wert von __re_x.


  • Mod

    Kellerautomat schrieb:

    Ok, so gut kenne ich mich mit den IO-Streams nicht aus. Dann ist der Fehler, dass der Wert von __x im Fehlerfall verändert wird, nämlich auf den undefinierten Wert von __re_x.

    Auch dies ist bei den IO-Streams so vorgesehen.



  • Wenn das einlesen fehlschlägt, sollte das Objekt doch unverändert bleiben, oder nicht?


  • Mod

    Kellerautomat schrieb:

    Wenn das einlesen fehlschlägt, sollte das Objekt doch unverändert bleiben, oder nicht?

    Wird oft so angenommen, ist aber im Allgemeinen falsch. Zumindest ist es nicht so bei short, int, float, double, bool & Co., die hier wohl imitiert werden sollen.

    Jetzt wirst du sicherlich einwenden, dass dann auch hier nicht sicher ist, dass __ch nicht verändert wird, das wäre aber auch falsch, da für char wieder andere Regeln gelten.

    Ja, die ganzen Regeln sind teilweise ziemlich arkan, man muss sich wirklich tief reindenken, um zu sehen, was der Sinn hinter den Definitionen ist. Im alten Standard war auch einiges sehr schlecht formuliert, das wurde in C++11 verbessert.

    Einen Fehler sehe ich aber immer noch nicht.


  • Mod

    SeppJ schrieb:

    Einen Fehler sehe ich aber immer noch nicht.

    Hast du doch schon angesprochen:
    Falls

    __is >> __ch
    

    fehlschlägt, hat __ch weiter einen unbestimmten Wert, die folgende Abfrage wäre also UB. I.d.R. wird der Code sicher trotzdem funktionieren, allerdings wird valgrind damit nicht glücklich.
    Die Zeichextraktoren verhalten sich bei Fehler anders als die arthmetischen Extraktoren, denn im Fehlerfall gibt num_get im Zweifel 0 zurück.


  • Mod

    camper schrieb:

    SeppJ schrieb:

    Einen Fehler sehe ich aber immer noch nicht.

    Hast du doch schon angesprochen:
    Falls

    __is >> __ch
    

    fehlschlägt, hat __ch weiter einen unbestimmten Wert, die folgende Abfrage wäre also UB.

    Laut Standard vielleicht, der Implementierer des GCC weiß aber ganz genau (und wir ebenfalls), dass der Computer nicht wirklich die Festplatte löschen wird, sondern bloß gegen irgendeinen Müllwert vergleicht. Da er beide Pfade abgedeckt hat (entweder ist das Zeichen zufällig '(' oder eben nicht), kann also nichts passieren.

    I.d.R. wird der Code sicher trotzdem funktionieren, allerdings wird valgrind damit nicht glücklich.

    valgrind defineirt aber nicht, was ein Fehler ist oder nicht, sondern im Zweifelsfalle die korrekte Funktion unter allen Umständen. Diese ist gegeben, sofern man den Code nur mit dem GCC (oder einem anderen nicht-geisteskranken Compiler) übersetzt.

    Die Zeichextraktoren verhalten sich bei Fehler anders als die arthmetischen Extraktoren, denn im Fehlerfall gibt num_get im Zweifel 0 zurück.

    Hmm, eigentlich lese ich den Standard so, dass bei char-Typen keine Änderung erfolgt:

    template<class charT, class traits>
    basic_istream<charT,traits>& operator>>(basic_istream<charT,traits>& in,
    charT& c);
    template<class traits>
    basic_istream<char,traits>& operator>>(basic_istream<char,traits>& in,
    unsigned char& c);
    template<class traits>
    basic_istream<char,traits>& operator>>(basic_istream<char,traits>& in,
    signed char& c);

    Effects: Behaves like a formatted input member (as described in 27.7.2.2.1) of in. After a sentry
    object is constructed a character is extracted from in, if one is available, and stored in c. Otherwise,
    the function calls in.setstate(failbit).

    Da steht weder was von num_get, noch dass das c im Fehlerfall überhaupt angepackt wird. Bei den "formatted input membern" steht auch nur allgemeines Gelaber, num_get taucht erst speziell bei den "arithmetic extractors" auf.


  • Mod

    SeppJ schrieb:

    camper schrieb:

    SeppJ schrieb:

    Einen Fehler sehe ich aber immer noch nicht.

    Hast du doch schon angesprochen:
    Falls

    __is >> __ch
    

    fehlschlägt, hat __ch weiter einen unbestimmten Wert, die folgende Abfrage wäre also UB.

    Laut Standard vielleicht, der Implementierer des GCC weiß aber ganz genau (und wir ebenfalls), dass der Computer nicht wirklich die Festplatte löschen wird, sondern bloß gegen irgendeinen Müllwert vergleicht. Da er beide Pfade abgedeckt hat (entweder ist das Zeichen zufällig '(' oder eben nicht), kann also nichts passieren.

    Du bist also gcc-Entwickler? Für mich sieht das erher so aus als ob der Compiler auf die Idee kommen könnte, den Extraktor zu inlinen und dann zu entscheiden, dass der Kontrollpfad, der Fehlschlag anzeigt, nie betreten werden wird, folglich eliminiert werden kann, weil ja andernfalls UB die Folge wäre.
    Wenn das bis jetzt noch nicht passiert ist, dann vielleicht nur, weil noch niemand die richtige Kombination aus -O99 -funroll-everything und -finline-limit=infinite gefunden hat.

    I.d.R. wird der Code sicher trotzdem funktionieren, allerdings wird valgrind damit nicht glücklich.

    valgrind defineirt aber nicht, was ein Fehler ist oder nicht, sondern im Zweifelsfalle die korrekte Funktion unter allen Umständen. Diese ist gegeben, sofern man den Code nur mit dem GCC (oder einem anderen nicht-geisteskranken Compiler) übersetzt.[/quote]Das kommentiere ich jetzt mal nicht.

    SeppJ schrieb:

    Die Zeichextraktoren verhalten sich bei Fehler anders als die arthmetischen Extraktoren, denn im Fehlerfall gibt num_get im Zweifel 0 zurück.

    Hmm, eigentlich lese ich den Standard so, dass bei char-Typen keine Änderung erfolgt:

    genau.



  • Das selbe Problem gibt's dann nochmal mit __re_x im äusseren else Zweig.


  • Mod

    hustbaer schrieb:

    Das selbe Problem gibt's dann nochmal mit __re_x im äusseren else Zweig.

    Das ist dann aber ein arithmetischer Extraktor, der num_get aufruft, und dabei kommt dann 0 heraus.
    Ich habe jetzt erst mal mit

    +      if (__is.fail())
    +        {
    +          __x = _Tp();
    +        }
    +      else if (__ch == '(')
    -      if (__ch == '(')
    

    gepatcht, und das beseitigt das Problem fürs Erste.
    __ch zu intialisieren dürfte auch helfen.



  • camper schrieb:

    hustbaer schrieb:

    Das selbe Problem gibt's dann nochmal mit __re_x im äusseren else Zweig.

    Das ist dann aber ein arithmetischer Extraktor, der num_get aufruft, und dabei kommt dann 0 heraus.

    ich = Brett vorm Kopf
    Das mit num_get hattest du ja schon geschrieben. Ich hab's auch gelesen und mir gedacht "aha, sehr interessant, jaja *mit-dem-kopf-nicke*".
    10 Sekunden später war's anscheinend wieder weg *g*.

    Naja, danke für die wiederholte Erklärung 🙂



  • camper schrieb:

    Für mich sieht das erher so aus als ob der Compiler auf die Idee kommen könnte, den Extraktor zu inlinen und dann zu entscheiden, dass der Kontrollpfad, der Fehlschlag anzeigt, nie betreten werden wird, folglich eliminiert werden kann, weil ja andernfalls UB die Folge wäre.

    Wage ich zu bezweifeln.
    Der Compiler "denkt" ja nicht in UB oder nicht UB. Weil UB ist ja nur ein Standardausdruck. UB gibt es ja nicht. Es ist immer definiert was passiert.

    Auch wenn man uU alle Variablen kennen muss um es vorauszusagen.


  • Mod

    Shade Of Mine schrieb:

    camper schrieb:

    Für mich sieht das erher so aus als ob der Compiler auf die Idee kommen könnte, den Extraktor zu inlinen und dann zu entscheiden, dass der Kontrollpfad, der Fehlschlag anzeigt, nie betreten werden wird, folglich eliminiert werden kann, weil ja andernfalls UB die Folge wäre.

    Wage ich zu bezweifeln.
    Der Compiler "denkt" ja nicht in UB oder nicht UB. Weil UB ist ja nur ein Standardausdruck. UB gibt es ja nicht. Es ist immer definiert was passiert.

    Auch wenn man uU alle Variablen kennen muss um es vorauszusagen.

    Der Compiler "denkt" in impliziten Nebenbedingungen.
    In

    int x; cin >> x;
    if ( x * x >= x )
       cout << x;
    else
       cout << "Überlauf";
    

    "weiß" der Compiler, dass die Bedingung überflüssig ist, und wird sie eliminieren.

    Und - um auf den ursprünglichen Code zurückzukommen - durch statische Analyse zu entdecken, dass in einem Ausführungspfad auf eine nicht initialisierte Variable zugegriffen wird, ist in solchen relative einfachen Fällen durchaus möglich.



  • Ein schlechts Beispiel. Was ist wenn x hinreichend groß ist und ein Overflow entsteht?

    Und die Ausgabe Overflow ist auch blödsinn, weil bei x*x so zu sagen mehrere Overflows entstehen können



  • Ramanujan schrieb:

    Ein schlechts Beispiel. Was ist wenn x hinreichend groß ist und ein Overflow entsteht?

    Dann hat man undefiniertes Verhalten.
    Das heißt im Klartext:
    entweder es gibt keinen Überlauf, dann ist x*x >=x.
    oder es gibt undefiniertes Verhalten, dann kann alles passieren, z.B. x ausgegeben werden oder das übergelaufenene Etwas als >= x gelten. Zusammengenommen ist es völlig legitim, wenn in jedem Fall x ausgegeben wird, also kann der Optimizer sich die Abfrage und den else-Zweig sparen und völlig standardkonform bleiben.



  • Ramanujan schrieb:

    Ein schlechts Beispiel. Was ist wenn x hinreichend groß ist und ein Overflow entsteht?

    Das ist es, worauf camper hinauswill. Ein arithmetischer Überlauf ist an der Stelle undefiniertes Verhalten, deshalb darf der Compiler diesen Fall ignorieren und so tun, als gäbe es hier nie Überlauf. Er darf also annehmen, dass die Bedingung immer true ist.



  • Hm, ok. Ist dann auch sowas undefiniert:

    unsigned u = -1;

    Oder kann man davon ausgehen, dass in u die Zahl 2^32 - 1 steht, falls int 32 bit groß ist?



  • camper schrieb:

    Der Compiler "denkt" in impliziten Nebenbedingungen.

    Ok, ich kann mir vorstellen wie du das theoretisch meinst - ich tue mir lediglich schwer an sowas in der Praxis zu glauben. Denn es setzt ein ziemlich starkes reorganisieren des codes voraus. (ohne dabei performance gewinn zu haben)

    Dennoch sollte man es fixen.


Anmelden zum Antworten