4 Gewinnt



  • void get_input( char buffer[], unsigned int amount )
    {
        ...
        while( (tmp_c=getchar()) != EOF and tmp_c != '\n'  )
        {
            if( counter != amount )
            {
                buffer[counter] = tmp_c;
                counter++;
            }
        }
        buffer[counter] = '\0';
    }
    

    UB liegt vor, wenn du z.B. bei char input[5] eine Eingabe >4 Zeichen machst, dann ist counter == amount und mit

    buffer[5] = '\0'
    

    greifst du auf undefinierten Speicher zu (hinter dem Array) == UB.

    const char *getinput(char *s, int n)
    { /* liefert immer einen String und leert stdin; zusätzl. könnte man für zu lange Eingaben eine Fehlerbehandlung ergänzen */
      char b[20];
      assert(n > 1);
      sprintf(b, "%%%d[^\n]", n - 1);
      if (1 != scanf(b, s))
        return "";
      while (!ferror(stdin) && !feof(stdin) && getchar() != '\n');
      return s;
    }
    

    - scanf liefert bei %s/%[] immer ein terminierendes '\0', wenn scanf das Feld ohne Fehler verarbeiten konnte
    - die Eingabeauswertung ist deswegen suboptimal, weil auch hier wieder UB droht; bei Eingabe von "Exit" z.B. würde atoi() 0 liefern, 0-1 = -1 und du rechnest weiter mit -1 als Index, was sicher zu Unglück führt, hier fehlt also eine ausführliche Abfrage



  • Wutz schrieb:

    void get_input( char buffer[], unsigned int amount )
    {
        ...
        while( (tmp_c=getchar()) != EOF and tmp_c != '\n'  )
        {
            if( counter != amount )
            {
                buffer[counter] = tmp_c;
                counter++;
            }
        }
        buffer[counter] = '\0';
    }
    

    UB liegt vor, wenn du z.B. bei char input[5] eine Eingabe >4 Zeichen machst, dann ist counter == amount und mit

    buffer[5] = '\0'
    

    greifst du auf undefinierten Speicher zu (hinter dem Array) == UB.

    Eine Verständnisfrage. Habe ich hier nicht sogar zweimal UB provoziert? Einmal das Zugreifen das du bereits erwähnt hast, aber ist meine Überprüfung hier nicht per se auch schon falsch? Fiel mir beim drüber lesen auf.

    while( (tmp_c=getchar()) != EOF and tmp_c != '\n'  )
        {
            if( counter != amount )
    

    Sobald ich amount erreicht habe, schreibt er dieses Byte zwar nicht mehr in den Buffer, aber danach allerdings schreibt er fröhlich weiter wenn die Eingabe > amount ist, oder vertue ich mich hier?


  • Mod

    So ist es; habe ich doch auch schon gesagt. Ich hätte vielleicht nicht "nirgends" zu deiner Längenprüfung sagen sollen, sondern "falsch".



  • Moin,

    wutz schrieb:

    const char *getinput(char *s, int n)
    { /* liefert immer einen String und leert stdin; zusätzl. könnte man für zu lange Eingaben eine Fehlerbehandlung ergänzen */
      char b[20];
      assert(n > 1);
      sprintf(b, "%%%d[^\n]", n - 1);
      if (1 != scanf(b, s))
        return "";
      while (!ferror(stdin) && !feof(stdin) && getchar() != '\n');
      return s;
    }
    

    - scanf liefert bei %s/%[] immer ein terminierendes '\0', wenn scanf das Feld ohne Fehler verarbeiten konnte
    - die Eingabeauswertung ist deswegen suboptimal, weil auch hier wieder UB droht; bei Eingabe von "Exit" z.B. würde atoi() 0 liefern, 0-1 = -1 und du rechnest weiter mit -1 als Index, was sicher zu Unglück führt, hier fehlt also eine ausführliche Abfrage

    Es hat ein bisschen gedauert, bis ich die exakte Funktionsweise deines snippet erfasst habe. Wobei mich der Ausdruck

    [^\n]
    

    noch immer irritiert, weil das nach einem RE aussieht. Konnte zu dieser Ausdrucksweise aber keine Referenzen finden wenn ich für diesen Ausdruck Google bemühe (außer Verweise auf Regex Libs). Hab auch http://en.cppreference.com/ die Output Funktionen durchforstet, allerdings finde ich dort auch nur die typischen Format specifier die immer genannt werden.

    Kannst du mir bitte sagen, unter was für einem Stichwort das fällt damit ich danach suchen kann?

    Zu deinem Einwand mit atoi gebe ich dir prinzipiell recht, dass ist designmäßig unschön. Allerdings habe ich zur Vorsicht (weil die Index -1 Rechnung schon vorgekommen ist) in Zeile 63 vom oben geposteten Code bewusst zumindest den Bereich der Eingabe überprüft, sodass (meiner Meinung nach) zumindest mit dem Index nicht mehr außerhalb des Speicherbereichs des Arrays zugegriffen werden kann. Oder habe ich hier was übersehen?

    Geändert wird es trotzdem, allerdings geht es mir hauptsächlich ums Verstehen.

    SeppJ schrieb:

    So ist es; habe ich doch auch schon gesagt. Ich hätte vielleicht nicht "nirgends" zu deiner Längenprüfung sagen sollen, sondern "falsch".

    Mein Verständnisfehler dann, Verzeihung. Hat einen Moment zum durchsickern gebraucht wie es scheint. ^^

    Danke nochmal an euch für die Geduld.



  • Standard C hat keine RE.
    Wenn du

    [^\n]
    

    nicht verstanden hast, hast du die Funktion auch nicht verstanden.

    Suche mal unter

    scanf scanset
    


  • Hallo fairiestoy,

    es geht auch nicht um die Output-Funktionen, sondern um die Eingabefunktion scanf (s. unter Conversion specifier "[set]")

    Die sprintf-Funktion baut nur den "format specifier" für scanf zusammen.



  • fairiestoy schrieb:

    Allerdings habe ich zur Vorsicht (weil die Index -1 Rechnung schon vorgekommen ist) in Zeile 63 vom oben geposteten Code bewusst zumindest den Bereich der Eingabe überprüft, sodass (meiner Meinung nach) zumindest mit dem Index nicht mehr außerhalb des Speicherbereichs des Arrays zugegriffen werden kann. Oder habe ich hier was übersehen?

    Nutzereingaben (und deren Eingabefehlerhandling) sind meist aufwändig, und insbesondere wenn sich die Kriterien mal ändern/erweitern nur aufwändig anzupassen.
    Deshalb sieht man genau für solche Stellen dann Code vor, der lesbar/wartbar/erweiterbar ist und das nicht nur für den Original-Autor, z.B.:

    enum { RAUS, REST, INDEX, FEHLER };
    int auswertung(const char*s)
    {
      if (!strcmp(s, "exit")) return RAUS;
      if (!strcmp(s, "rest")) return REST;
      if (strchr("1234567", *s) && !s[1]) return INDEX;
      return FEHLER;
    }
    ...
      int r;
      const char*a;
      do
      switch (r = auswertung(a = getinput((char[20]){ 0 }, 20)))
      {
      case RAUS: puts("exit"); break;
      case REST: puts("rest"); break;
      case INDEX: {int i = atoi(a); printf("%d", i); } break;
      case FEHLER: puts("fehler"); break;
      }
      while (r == FEHLER);
    


  • Wutz schrieb:

    ...
      int r;
      const char*a;
      do
      switch (r = auswertung(a = getinput((char[20]){ 0 }, 20)))
      {
      case RAUS: puts("exit"); break;
      case REST: puts("rest"); break;
      case INDEX: {int i = atoi(a); printf("%d", i); } break;
      case FEHLER: puts("fehler"); break;
      }
      while (r == FEHLER);
    

    Kleine Verständnisfrage zwischendurch:

    Warum ist es hier erlaubt mit dem zurückgegebenen Zeiger ( a ) von getinput() weiter zu arbeiten? Erstreckt sich der Gültigkeitsbereich des temporären char[20] über das gesamte switch-statement?



  • Der Gültigkeitsbereich eines Objektes erstreckt sich (außer global) immer ab Definition bis zum Ende des Codeblocks/compound statement, hier also im Bereich von do-while und nicht switch.



  • Danke, ich hätte gedacht, dass das array nach dem Funktionsaufruf wieder zerstört wird, weil es nur ein temporary ist. Da habe ich mich dann wohl geirrt...



  • Wobei du jetzt im Gebrauch der Begriffe aufpassen musst: ein compound literal ist kein temporäres Objekt im Sinn des Standards, mit temporary lifetime ist im Standard ganz was anderes gemeint.



  • Ups!
    Ja, das hat mir gefehlt. Habe es recherchiert. Vielen Dank!



  • Ich danke auch.
    Ja ja, kommt schon mal vor.
    Ich bedanke mich für die qualifizierten Fragen, d.h. der Frager hat den Code mal wirklich durchdacht und nicht nur irgendwas aus Google oder sonstwo nachgeplappert.


Anmelden zum Antworten