Problem mit Zeiger -> Programm stürzt ab


  • Mod

    DirkB schrieb:

    Und dafür gibt es auch ein Limit. Das ist aber größer als 255.

    Nicht einmal das. Die meisten Dateisysteme können beliebig tief verschachtelt werden (oder zumindest sehr, sehr tief, bis ihnen der Platz für Verwaltungsdaten ausgeht). Bloß die System-APIs setzen ein paar Obergrenzen, sofern man diese benutzt. Da ist es dann recht kunterbunt und systemabhängig. Da nehme ich mir heraus, irgendeine Magic Number zu wählen, die voraussichtlich niemals vorkommen wird. Aber 255 kann ich mir schon vorstellen, dass das leicht mal vorkommt. Selbst die ganz, ganz alte Windows-API konnte angeblich schon 260 Zeichen Pfadlänge. Neuerdings angeblich 32767 Unicode-Zeichen (was immer das genau heißen mag. Jedenfalls ist es deutlich mehr als 255.)



  • Also das ist cool:

    scanf(" %1000[^\n]", name);
    

    In meinen Unterlagen steht (sinngemäß):

    ...kann nur einen Wert, bis zum nächsten Leerzeichen (Whitespace) einlesen...

    Mit [^\n] kann man diesen Umstand offenbar umgehen.

    Steht das irgendwo geschrieben, oder ist das ein „Insidertrick“

    Grüße, Felted



  • SeppJ schrieb:

    destination=fopen(strcat(new_name, name), "w");
    
      if(!source)
        {
          puts("Datei kann nicht geoeffnet werden.");
          return 1;
        }
    
      printf("Demo, dass strcat nun funktioniert: new_name = \"%s\"\n", new_name);
      return 0;
    }
    

    Ist es Absicht, dass du kein fclose(destination) aufrufst? Mag zwar formal korrekt sein, aber für einen Anfänger hätte ich das lieber explizit hingeschrieben.


  • Mod

    Klar, es gibt zu allen Funktionen der Standardbibliothek Referenzen. Zunächst mal natürlich im ISO-Standard selbst, aber besser aufbereitet zum Beispiel hier:
    http://www.cplusplus.com/reference/clibrary/

    Das ist zwar eigentlich eine C++-Referenz, aber der Teil über die C-Standardbibliothek (die zu großen Teilen in C++ vorhanden ist), ist so geschrieben, dass die Beispiele auch für C funktionieren.

    Da findet man:
    http://http://www.cplusplus.com/reference/cstdio/scanf/

    Somit entziffern wir, was ich da überhaupt gemacht habe:

    scanf(" %1000[^\n]", name);
    

    Zunächst einmal sehen wir, dass das Leerzeichen am Anfang durchaus eine Bedeutung hat:

    Whitespace character: the function will read and ignore any whitespace characters encountered before the next non-whitespace character (whitespace characters include spaces, newline and tab characters -- see isspace). A single whitespace in the format string validates any quantity of whitespace characters extracted from the stream (including none).

    Dann kommt ein Prozentzeichen, welches einläütet, dass die Eingabe irgendwie interpretiert werden soll. Auf das Prozentzeichen folgt eine Zahl, das muss also ein width-Feld sein:

    Specifies the maximum number of characters to be read in the current reading operation (optional)

    Erst danach kommt das Zeichen, welches bestimmt, was überhaupt gelesen werden soll. Das ist hier kein 's', für das deine Bemerkung bezüglich Leerzeichen (allgemeiner: Whitespace) richtig wäre, sondern '[', gefolgt von '^', welches ganz etwas anderes bedeutet:

    [^characters] Negated scanset: Any number of characters none of them specified as characters between the brackets.

    Somit ist auch der Rest klar: '\n' ist das einzige Zeichen vor der schließenden Klammer, ist also das Zeichen, bei dem abgebrochen werden soll. '\n' ist ein Zeilenumbruch.

    Insgesamt übergeht der Ausdruck also erst alle Whitespace-Zeichen, die eventuell am Anfang eingegeben werden. Dann wird alles gelesen, bis ein Zeilenumbruch erreicht wird oder 1000 Zeichen gelesen wurden. Der Zeilenumbruch wird zwar eingelesen, aber nicht mit abgespeichert.

    1234567 schrieb:

    Ist es Absicht, dass du kein fclose(destination) aufrufst? Mag zwar formal korrekt sein, aber für einen Anfänger hätte ich das lieber explizit hingeschrieben.

    Nein, das ist falsch aus dem Original übernommen. Und da ich sonst hauptsächlich C++ mache, fällt mir ein fehlendes close nicht auf, mir fiel eher im Gegenteil das explizite open auf 🙂 .



  • Wow, super Hilfe. Vielen Dank!
    Felted



  • SeppJ schrieb:

    scanf(" %1000[^\n]", name);
    

    Zunächst einmal sehen wir, dass das Leerzeichen am Anfang durchaus eine Bedeutung hat

    Der Haken daran ist, dass du mit %[] Whitespaces einschließt, mit " " aber führende Whitespaces in der Eingabe ausschließt.
    Also entweder ganz zulassen oder ganz ausschließen.


  • Mod

    Wutz schrieb:

    SeppJ schrieb:

    scanf(" %1000[^\n]", name);
    

    Zunächst einmal sehen wir, dass das Leerzeichen am Anfang durchaus eine Bedeutung hat

    Der Haken daran ist, dass du mit %[] Whitespaces einschließt, mit " " aber führende Whitespaces in der Eingabe ausschließt.

    Genau das möçhte ich aber.

    Also entweder ganz zulassen oder ganz ausschließen.

    Wieso? Das ist doch genau das gewünschte: Dateinamen können nicht mit Leerzeichen anfangen (oder können sie doch?), wohl aber welche enthalten.

    P.S.: Anscheinend können Dateinamen doch mit Leerzeichen beginnen. Cool 🕶 . Dann war die Aktion mit dem Überspringen der Leerzeichen unnötig, sogar hinderlich. Habe diese Regel intuitiv angenommen, hätte vorher mal nachschlagen sollen, was wirklich richtig ist.



  • Hallo,

    habe noch eine Frage zu strcat.

    So stürzt das Programm ab:

    // Programm stürzt ab
        char titel[] = "Buch";
        char zusatz[] = " fuer Anfaenger";
        strcat (titel, zusatz);
        printf("%s \n", titel);
        getch();
    }
    

    Das liegt wohl daran, dass das Array titel[] nur genau so groß ist, um den String Buch aufnehmen zu können. Dementsprechend kann die Funktion strcat das Array nicht mehr erweitern und es kommt zum Absturz. Richtig?

    Frage: Wieso stürzt hier das Programm nicht ab?

    // Programm stürzt nicht ab
        char titel[] = "Buch";
        char zusatz[1000] = " fuer Anfaenger";
        strcat (titel, zusatz);
        printf("%s \n", titel);
        getch();
    }
    

    Hier ist das Array titel[] doch nach wie vor zu klein, um das Array zusatz[1000] aufnehmen zu können, oder?

    Und noch ein paar Anfängerfragen.
    In meinen Unterlagen zu strcat steht:

    *char *strcat (char *str1, const char str2);

    Das Sternchen(*) steht ja für einen Zeiger.

    Die Parameter str1 und str2 müssen aber keine Zeiger sein, damit es funktioniert.
    Frage: Wieso wird dann bei der Beschreibung der Funktion dies mit einem * dargestellt?

    Frage: Wieso funktioniert:

    // Funktioniert
        char titel[1000] = "Buch";
        char *zusatz = " fuer Anfaenger";
        strcat (titel, zusatz);
        printf("%s \n", titel);
        getch();
    

    aber

    // Programm stürzt ab
        char *titel = "Handbuch ";
        char zusatz[1000] = " fuer Anfaenger";
        strcat (titel, zusatz);
        printf("%s \n", titel);
        getch();
    

    nicht?

    Grüße und Danke, Felted



  • felted schrieb:

    Das liegt wohl daran, dass das Array titel[] nur genau so groß ist, um den String Buch aufnehmen zu können.

    Richtig.

    Frage: Wieso stürzt hier das Programm nicht ab?

    // Programm stürzt nicht ab
        char titel[] = "Buch";
        char zusatz[1000] = " fuer Anfaenger";
        strcat (titel, zusatz);
        printf("%s \n", titel);
        getch();
    }
    

    Hier ist das Array titel[] doch nach wie vor zu klein, um das Array zusatz[1000] aufnehmen zu können, oder?

    Es wäre zwar schön, wenn jedes falsche Programm einen Absturz verursachen würde, aber leider ist das nicht so. Du verursachst hier (und auch bei dem ersten Programm) undefiniertes Verhalten, das heißt, dass im Prinzip alles passsieren kann, der Sprachstandard legt kein Verhalten fest. Von nichts bis Absturz ist alles drin.
    Die konkrete Erklärung geht über das Speicherlayout, also an welcher Stelle im Speicher die Arrays liegen, aber das kann sich von Compiler zu Compiler, auch mit unterschiedlichen Optimierungsleveln oder Debugeinstellungen ändern und taugt daher nicht wirklich zum Verständnis.

    Und noch ein paar Anfängerfragen.
    In meinen Unterlagen zu strcat steht:

    *char *strcat (char *str1, const char str2);

    Das Sternchen(*) steht ja für einen Zeiger.

    Ja.

    Die Parameter str1 und str2 müssen aber keine Zeiger sein, damit es funktioniert.

    Doch, eigentlich schon. Wenn die Argumente keine Zeiger sind, müssen sie wenigstens in Zeiger umwandelbar sein.

    Arrays sind immer in einen Zeiger auf ihr erstes Element konvertierbar.

    Frage: Wieso funktioniert:

    // Funktioniert
        char titel[1000] = "Buch";
        char *zusatz = " fuer Anfaenger";
        strcat (titel, zusatz);
        printf("%s \n", titel);
        getch();
    

    Weil titel genug Platz hat.

    // Programm stürzt ab
        char *titel = "Handbuch ";
        char zusatz[1000] = " fuer Anfaenger";
        strcat (titel, zusatz);
        printf("%s \n", titel);
        getch();
    

    nicht?

    Weil titel auf ein (konstantes!) Zeichenkettenliteral zeigt. Veränderungen von Zeichenkettenliteralen verursachen undefiniertes Verhalten.



  • "Programm stürzt ab" ist vom Standard nicht spezifiziert.
    Spezifiziert ist undefiniertes Verhalten, so auch in deinem Fall:
    Das Beschreiben von undefiniertem Speicher führt zu undefiniertem Verhalten gemäß C-Standard, das Programm zur Laufzeit kann abstürzen, muss aber nicht.

    felted schrieb:

    *char *strcat (char *str1, const char str2);

    Das Sternchen(*) steht ja für einen Zeiger.

    Die Parameter str1 und str2 müssen aber keine Zeiger sein, damit es funktioniert.

    Wieso nicht?

    char titel[1000] = "Buch";
        char *zusatz = " fuer Anfaenger"; /* Zeiger auf einen Speicherbereich, der mit dem Inhalt des Stringliterals befüllt ist und beim Ändern einzelner Zeichen zu undefiniertem Verhalten führt */
    

    d.h. also wenn du <zusatz> beschreibst oder es indirekt durch eine Funktion machen lässt, liegt undefiniertes Verhalten vor, genauso (wie oben erwähnt) wie wenn du in undefinierte Speicherbereiche reinschreibst/reinschreiben lässt.
    C-Funktionen prüfen zur Laufzeit nicht, ob der übergebene Zeiger auch auf ausreichenden oder beschreibbaren Speicher verweist.
    Das Programm kann dann abstürzen muss es aber nicht.

    Deshalb gibt es den Qualifizierer const, der anzeigt, dass dieser Speicherbereich nicht beschreibbar ist, viele Compiler warnen hier, wenn sie es merken.

    char *s = "irgendwas";
    /* ist immer gleichbedeutend mit: */
    const char *s = "irgendwas";
    

    d.h. der referenzierte Speicherbereich, auf den s verweist, darf niemals beschrieben werden, sonst UB.



  • SeppJ schrieb:

    P.S.: Anscheinend können Dateinamen doch mit Leerzeichen beginnen. Cool 🕶

    Dateinamen können (zumindest unter Unix) auch Newlines enthalten. Eigentlich alles außer '/' und '\0'. Ob man das in seinem Code nun unbedingt unterstützen sollte, ist die andere Frage. Drüber stolpern sollte man allerdings auch nicht. 😉


Anmelden zum Antworten