Mein C-Programm hat manchmal einen Speicherzugriffsfehler



  • Hallo!

    Ich habe ein kleines Programm geschrieben, welches meine Daten sicher vernichtet.
    Bei einem größeren Verzeichnis habe ich einen Speicherzugriffsfehler, woran könnte das liegen?
    Ich habe das Programm zur Übung geschrieben.

    Hier mein Code:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <dirent.h>
    
    //PROTOTYPEN
    int dat_o_ord(char *dname);
    int zerstoeren(char *dname);
    int verzeichnis(char *ordner);
    
    int main(int argc, char **argv)
    {
      int x=1;
    
      if(argc<2)
      {
        system("clear");
        printf("Syntax (Beispiel): sdv (datei1) (ordner1) (datei2) (datei3)....\n\n");
       }
    
      else
      {
        while(argc>=(x+1))
        {
          dat_o_ord(argv[x]);
          x++;
        }
    
      }
      return EXIT_SUCCESS;
    
     }
    
    int dat_o_ord(char *dname)  //Datei oder Ordner
    {
      int x;
      struct stat attribut;
      stat(dname, &attribut);
      DIR *dir;
      struct dirent *dirzeiger;
    
       if(attribut.st_mode & S_IFREG) //Wenn Datei
      {
        FILE *datei=fopen(dname, "r");
        if(datei == NULL)
        {
          printf("FEHLER! %s konnte nicht geöffnet werden!\n", dname);
          return EXIT_FAILURE;
        }
        fclose(datei);
        zerstoeren(dname);
      }
    
      else if(attribut.st_mode & S_IFDIR) //Verzeichnis
      {
        if((dir=opendir(dname)) == NULL)
        {
          printf("FEHLER! Verzeichnis: %s konnte nicht geöffnet werden!\n", dname );
          return EXIT_FAILURE;
        }
        verzeichnis(dname);
        x=strcmp(dname, "./");
        if(x==1)
        {
          rmdir(dname);
        }
      }
    }
    
    int zerstoeren(char *dname)
    {
      unsigned long x;
      FILE *datei=fopen(dname, "w+b");
      fseek(datei, 0L, SEEK_END);
      x=ftell(datei);
      fwrite((char *) '\0', 1, x, datei);
      fclose(datei);
      remove(dname);
    }
    
    int verzeichnis(char *ordner)
    {
      DIR *dir;
      int t1, t2, t3;
      struct dirent *dirzeiger;
      char d1[200], d2[200];
    
      if((dir=opendir(ordner)) == NULL)
      {
        printf("FEHLER!!! Verzeichnis: %s - konnte nicht geöffnet werden!\n", ordner);
        return EXIT_FAILURE;
      }
    
      while((dirzeiger=readdir(dir)) != NULL)
      {
        sprintf(d1,"%s", (*dirzeiger).d_name);
        t1=strcmp(d1, "..");
        t2=strcmp(d1, ".");
        t3=strcmp(d1, "");
    
        if(t1 != 0 && t2 != 0)
        {
          sprintf(d2, "./%s/%s", ordner, d1);
          dat_o_ord(d2);
          rmdir(d2);
        }
      }
    }
    

    Ich denke es liegt an der Schleife wo das Verzeichnis wieder nach Datei oder Ordner (doo) gesendet wird und nicht selbst wieder beendet.

    Wie kann ich das besser lösen?
    Die Verzeichnisse dynamisch sichern und erst dann auslesen?
    Ich hoffe das mir jemand helfen kann.

    Vielen Dank im vorraus!



  • Das Problem ist das

    fwrite((char *) '\0', 1, x, datei);
    

    Allgemein:

    size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
    

    fwrite erwartet einen Zeiger (ptr) auf einen Speicherbereich (der deinem Programm gehört), die Länge des Datentyps den du Schreiben möchtest (size), die Anzahl der Daten (count) und den Filedeskriptor (stream).
    fwrite schreibt also aus dem Speicher ab dem Punkt ptr size*count Byte in den stream.

    *(char ) '\0' ist BÖSE den es entspricht NULL. Der Bereich gehört dir gar nie nicht.
    1 Ok ist ja ein char
    x ist die Dateigröße. Wo in deinem Programm hast du soviel Speicher dafür.
    datei OK

    Kurz: fwrite schreibt nicht irgendetwas sondern ein Speicherabbild in die Datei.



  • Kann noch anhängen, daß für charweises Schreiben fputc() angebrachter wäre.
    Noch'n Schwachpunkt:
    Wo überwachst Du, daß Dir d1 und d2 nicht um die Ohren fliegen?



  • pointercrash() schrieb:

    Kann noch anhängen, daß für charweises Schreiben fputc() angebrachter wäre.

    char-weise mit putc dürfte ziemlich langsam sein.

    Besser dürfte sein, mit malloc einen größeren Bereich zu reservieren, mit memset auf 0 zu setzen und den dann so oft schreiben bis die Datei überschrieben ist.



  • if(attribut.st_mode & S_IFREG) //Wenn Datei
    if(attribut.st_mode & S_IFDIR) //Verzeichnis
    

    scheitern für den Fall, dass die Konstanten mehr als 1 Bit gesetzt haben.
    Richtigerweise prüft man so:

    if( (attribut.st_mode & S_IFREG) == S_IFREG )
    

    Auch schreibst du beim <zerstoeren> ans Ende der Datei, was wohl nicht beabsichtigt ist.
    Warum kopierst du von d_name nochmal in einen eigenen Puffer, dass ist nur fehleranfällig, was du auch bewiesen hast.



  • DirkB schrieb:

    pointercrash() schrieb:

    Kann noch anhängen, daß für charweises Schreiben fputc() angebrachter wäre.

    char-weise mit putc dürfte ziemlich langsam sein.

    Unwahrscheinlich. putc ist ein Makro, das praktisch zu einer direkten Zuweisung in den Dateipuffer expandiert.
    fputc ist dasselbe als Funktion, das könnte ein bisschen langsamer sein, aber immer noch nicht dramatisch.



  • Ich meinte den Vergleich von fputc zu fwrite bei vielen Daten. (Ich habe das f bei fputc vergessen)



  • Bashar schrieb:

    DirkB schrieb:

    pointercrash() schrieb:

    Kann noch anhängen, daß für charweises Schreiben fputc() angebrachter wäre.

    char-weise mit putc dürfte ziemlich langsam sein.

    Unwahrscheinlich. putc ist ein Makro, das praktisch zu einer direkten Zuweisung in den Dateipuffer expandiert.
    fputc ist dasselbe als Funktion, das könnte ein bisschen langsamer sein, aber immer noch nicht dramatisch.

    Naja, das ist schon langsamer, abhängig davon, wie groß die writebuffer sind und die Flushzykluszeit. Ich war ein wenig schreibfaul, eigentlich hätte ich noch mitliefern sollen, daß es ungünstig ist, mit "0" zu überlöschen. Das ist leichter rekonstruierbar, als wenn man mit rand() generierte Muster draufschüttet.
    Ich glaub', beim US- Militär gilt eine Löschung nur, wenn ein paarmal (5?) mit zufälligen Mustern überschrieben wird.



  • fwrite schreibt doch in den gleichen Puffer, wieso sollte putc also langsamer sein? BTW geht es mir hier nicht um Haarspalterei, nur um die Aussage (f)putc sei "ziemlich langsam" mit der Andeutung, dass man das nicht verwenden sollte.



  • pointercrash() schrieb:

    ..., eigentlich hätte ich noch mitliefern sollen, daß es ungünstig ist, mit "0" zu überlöschen. Das ist leichter rekonstruierbar, als wenn man mit rand() generierte Muster draufschüttet.
    Ich glaub', beim US- Militär gilt eine Löschung nur, wenn ein paarmal (5?) mit zufälligen Mustern überschrieben wird.

    Einmal überschreiben reicht : http://www.heise.de/newsticker/meldung/Sicheres-Loeschen-Einmal-ueberschreiben-genuegt-198816.html



  • Wutz schrieb:

    if(attribut.st_mode & S_IFREG) //Wenn Datei
    if(attribut.st_mode & S_IFDIR) //Verzeichnis
    

    scheitern für den Fall, dass die Konstanten mehr als 1 Bit gesetzt haben.
    Richtigerweise prüft man so:

    if( (attribut.st_mode & S_IFREG) == S_IFREG )
    

    Richtigerweise prüft man

    if(S_ISREG(attribut.st_mode))
    

    , wie man in POSIX nachlesen kann. POSIX lässt sich nicht darüber aus, ob es sich beim Dateityp-Teil von st_mode um eine Bitmaske handelt (obwohl sie üblicherweise so implementiert ist). Wird beispielsweise hochgezählt - S_IFREG = 1, S_IFLNK = 2, S_IFDIR = 3 - so wird dein Ansatz Verzeichnisse als reguläre Dateien erkennen. Nicht einmal

    if((attribut.st_mode & S_IFMT) == S_IFREG)
    

    ist in POSIX definiert, obwohl das der vor der Einführung der S_IS-Makros übliche Weg war, den Dateityp abzuprüfen und ich sehr erstaunt wäre, eine davon abweichende Implementation zu finden.

    Es ist gut, sich um Korrektheit Gedanken zu machen, nur sollte man dann auch nachschlagen, was korrekt ist.

    Was Performance angeht - ich hab das mal gebencht:

    Mit fwrite:

    real    0m0.294s
    user    0m0.020s
    sys     0m0.248s
    

    Mit fputc:

    real    0m1.259s
    user    0m1.016s
    sys     0m0.244s
    

    Mit putc:

    real    0m1.266s
    user    0m1.008s
    sys     0m0.256s
    

    Code wie folgt:

    #include <stdio.h>
    
    int main() {
      FILE *fd = fopen("test.txt", "w");
      char block[1000] = { };
      int i;
    
      for(i = 0; i < 100000; ++i) {
        fwrite(block, 1, 1000, fd);
      }
    
    /*
      for(i = 0; i < 100000000; ++i) {
        fputc(0, fd);
        // putc(0, fd);
      }
    */
    
      return 0;
    }
    

    System ist Linux 2.6.32 auf x86-64. Zu einem Teil hängt das mit Locking zusammen - benutze ich statt putc das (linux-spezifische) _IO_putc_unlocked-Makro, kommt Folgendes heraus:

    real    0m0.633s
    user    0m0.392s
    sys     0m0.244s
    

    Der Rest dürfte der Unterschied zwischen memcpy und einer Schleife mit Zuweisungen sein (nehme ich an).



  • Bashar schrieb:

    fwrite schreibt doch in den gleichen Puffer, wieso sollte putc also langsamer sein? BTW geht es mir hier nicht um Haarspalterei, nur um die Aussage (f)putc sei "ziemlich langsam" mit der Andeutung, dass man das nicht verwenden sollte.

    Schon, aber mit fwrite() kippst Du 'nen Sack voll Memory mit einem Funktionsaufruf in den Buffer, sonst haste für jeden char einen call samt return. Macht dann, wenn man wirklich mehr putzen mag, schon was aus.

    DirkB schrieb:

    Einmal überschreiben reicht : http://www.heise.de/newsticker/meldung/Sicheres-Loeschen-Einmal-ueberschreiben-genuegt-198816.html

    Upps, das war mir neu. Man lernt nie aus. 😉



  • seldon schrieb:

    Mit fwrite:

    real    0m0.294s
    user    0m0.020s
    sys     0m0.248s
    

    Mit fputc:

    real    0m1.259s
    user    0m1.016s
    sys     0m0.244s
    

    Mit putc:

    real    0m1.266s
    user    0m1.008s
    sys     0m0.256s
    

    Wow, macht doch ziemlich was aus.

    Zu einem Teil hängt das mit Locking zusammen - benutze ich statt putc das (linux-spezifische) _IO_putc_unlocked-Makro[...]

    #define _IO_putc_unlocked(_ch,_fp) (_IO_BE ((_fp)->_IO_write_ptr >= (_fp)->_IO_write_end, 0) \
       ?  __overflow (_fp, (unsigned char) (_ch)) \
       : (unsigned char) (*(_fp)->_IO_write_ptr++ = (_ch)))
    

    Auffallend ist der Test, ob der Schreibzeiger das Pufferende erreicht hat. Das kann fwrite wohl etwas besser handhaben. Trotzdem: Hast du mit Optimierung compiliert?



  • Nein, aber Optimierung macht nicht viel aus. Ergebnisse mit -O3:

    fwrite:

    real    0m0.261s
    user    0m0.016s
    sys     0m0.244s
    

    fputc:

    real    0m1.137s
    user    0m0.880s
    sys     0m0.256s
    

    putc:

    real    0m1.148s
    user    0m0.884s
    sys     0m0.264s
    

    _IO_putc_unlocked:

    real    0m0.486s
    user    0m0.300s
    sys     0m0.188s
    


  • Evtl. bringt es noch was, wenn man mit setvbuf() rumspielt.
    Welche Einstellung da am meisten bringt muss man mal ausprobieren. Da wird es sicher auch einen Unterschied zwischen fputc und fwrite geben.



  • Vielen Dank für die zahlreichen Antworten!

    Ich werde meinen C-Code überarbeiten und falls dann die Speicherzugriffsverletzung noch besteht melde ich mich wieder!


Anmelden zum Antworten