1. Diese Seite verwendet Cookies. Wenn du dich weiterhin auf dieser Seite aufhältst, akzeptierst du unseren Einsatz von Cookies. Weitere Informationen

fgetc hört mitten im Programm auf, den filepointer weiterzuschalten

Dieses Thema im Forum "C/C++" wurde erstellt von Neonof, 14. Februar 2017.

  1. Neonof

    Neonof Grünschnabel

    Hallo, folgendes Problem:

    Ich habe mir vorgenommen, für ein Spiel ein kleines Tool zu schreiben. Dazu werden einige xml-Dateien ausgelesen und nach bestimmten Kriterien gefiltert. Zumindest bin ich bisher nur bis zum Einlesen der ersten Datei gekommen. Nun habe ich diesen Fehler, den ich einfach nicht verstehen will. Beim Einlesen nutze ich Schleifen mit fgetc() um die einzelnen Elemente zu erfassen, wobei als Abbruchbedingung beispielsweise gilt: (!feof(fp) && (fgetc(fp) != delim)). Dabei stellt delim das Zeichen dar, bis zu dem eingelesen werden soll. Soweit funktioniert auch alles schön und gut - bestimmt 100 Einträge lang. Ich habe nun also Schritt für Schritt den Debugger dabei begleitet, wie er den Code durchgeht und dabei mittels fgetpos() beobachtet, wo in die Datei der Filepointer zeigt. Dabei ist mir aufgefallen, dass fgetc() ab dem fehlerhaften Eintrag zwar die Zeichen liefert, den Pointer aber nicht mehr versetzt. Krücken zu bauen, in dem vor und nach der Verwendung von fgetc() mittels fgetpos() und fsetpos() die Position korrigiert wird funktioniert zwar relativ, aber das Wahre ist es auch nicht und an einigen Stellen hilft das auch nicht.

    Ist die Funktion irgendwie unsicher und gibt es eine Alternative?
    Ich habe in einem anderen Forum von einem Zusammenhang von setvbuf() mit fgetc() gelesen. Die Sache mit dem Buffering kenne ich noch nicht. Ich bin ja "noch neu in C". Aber ich kann mir eigentlich auch nicht vorstellen, dass sich diese Einstellungen ohne offensichtlichen Grund mitten beim Durchlauf des Programms ändern sollten. Einen dieser Befehle verwendet habe ich ja auch nicht.

    Zu der verwendeten Software:
    inzwischen CodeLite mit MinGW (TDM-GCC-64)
    Windows 10 x64

    Hier sind jetzt noch einige Krücken und Zeilen drinne, die dazu da waren, den Fehler zu finden. Bei der Version mit den Krücken, die den Fehler eigentlich komplett umgehen sollten, kommt es noch zu anderen merkwürdigen Fehlern. Da beendet er die Funktion readplayerline() mit return 1 und bricht die while-Schleife, in apiloadplayers(), von der sie als Abbruchbedingung aufgerufen wurde dennoch ab.

    Ich denke eigentlich, dass dieses undefinierte Verhalten alles den selben Grund haben wird. Auf jeden Fall würde ich gerne mal verstehen, wie soetwas zustande kommt.

    Vielen Dank schon einmal für die Hilfe :)

    Code (C):
    1. #include <stdio.h>
    2. #include <time.h>
    3. #include <string.h>
    4. #include <stdlib.h>
    5.  
    6. #define READ_STRING_MAX_CHARACTERS 100
    7.  
    8. char readString[READ_STRING_MAX_CHARACTERS], delim[] = {34, 0}, *token;
    9. FILE *fp = NULL;
    10. fpos_t pos1, pos2;
    11.  
    12. /* Statustype:
    13.  * 0        Normal
    14.  * 1    a    Administrator
    15.  * 2    o    Vogelfreier (temporär)
    16.  * 4    v    Urlaubsmodus
    17.  * 8    b    gesperrt
    18.  * 16    i    Inaktiv
    19.  * 32    I    Inaktiv (30)
    20.  * */
    21. struct sAPIplayers
    22. {
    23.     int id;
    24.     char* name;
    25.     int alliance;
    26.     int status;
    27.     struct sAPIplayers* next;
    28. } *APIplayersTemp = NULL;
    29.  
    30. struct sAPIplayersHead
    31. {
    32.     char* xmlVersion;
    33.     char* encoding;
    34.     char* serverId;
    35.     time_t timestamp;
    36.     char* nNsSL;
    37.     char* xmlnsxsi;
    38.     struct sAPIplayers* next;
    39. } *APIplayers = NULL;
    40.  
    41. char* strnewcpy(char*_str_src) {
    42.     size_t len = strlen(_str_src);
    43.     char *newstr = (char*)malloc(len + 1);
    44.     if (newstr == NULL) return NULL;
    45.     strncpy(newstr, _str_src, len);
    46.     newstr[len] = '\0';
    47.     return newstr;
    48. }
    49.  
    50. void frtc(char *dest, int lim, char delim)
    51. // liest einen String bis zu delim ein
    52. {
    53.     char temp;
    54.     int count = 0;
    55.  
    56.     while (!feof(fp) && (count++ < lim))
    57.     {
    58.         fgetpos(fp, &pos1);
    59.         temp = fgetc(fp);
    60.         fgetpos(fp, &pos2);
    61.         if (pos1 == pos2) fseek(fp, 1, SEEK_CUR);
    62.         if (temp == delim) break;
    63.         *dest++ = temp;
    64.     }
    65.  
    66.     *dest = 0;
    67. }
    68.  
    69. int jo(char delim)
    70. // schaltet die Position in der Datei hinter das nächste Vorkommen von delim
    71. {
    72.     fgetpos(fp, &pos1);
    73.     while (!feof(fp) && (fgetc(fp) != delim))
    74.     {
    75.         fgetpos(fp, &pos2);
    76.         if (pos1 == pos2) fseek(fp, 1, SEEK_CUR);
    77.     }
    78.     fgetpos(fp, &pos2);
    79.     if (pos1 == pos2) fseek(fp, 1, SEEK_CUR);
    80.     if (feof(fp)) return EOF;
    81.     return 0;
    82. }
    83.  
    84. int readplayerline()
    85. {
    86.     // neuen Speicherbereich reservieren
    87.     APIplayersTemp = APIplayersTemp->next = (struct sAPIplayers*) malloc(sizeof(struct sAPIplayers));
    88.  
    89.     // Daten einlesen
    90.     // id einlesen
    91.     jo(34);
    92.     frtc(readString, READ_STRING_MAX_CHARACTERS, 34);
    93.     sscanf(readString, "%d", &APIplayersTemp->id);
    94.  
    95.     // name einlesen
    96.     jo(34);
    97.     frtc(readString, READ_STRING_MAX_CHARACTERS, 34);
    98.     APIplayersTemp->name = strnewcpy(readString);
    99.     fseek(fp, 1, SEEK_CUR);
    100.  
    101.     // status einlesen
    102.     APIplayersTemp->status = 0;
    103.     fgetpos(fp, &pos1);
    104.     if (fgetc(fp) == 's')
    105.     {
    106.         fgetpos(fp, &pos2);
    107.         if (pos2 == pos1) fseek(fp, 1, SEEK_CUR);
    108.         jo(34);
    109.         frtc(readString, READ_STRING_MAX_CHARACTERS, 34);
    110.         char* token = readString;
    111.         while (*token != 0)
    112.         {
    113.             if (*token == 'a') APIplayersTemp->status = 1;
    114.             if (*token == 'o') APIplayersTemp->status += 2;
    115.             if (*token == 'v') APIplayersTemp->status += 4;
    116.             if (*token == 'b') APIplayersTemp->status += 8;
    117.             if (*token == 'i') APIplayersTemp->status += 16;
    118.             if (*token++ == 'I') APIplayersTemp->status += 32;
    119.         }
    120.         fseek(fp, 1, SEEK_CUR);
    121.     } else fsetpos(fp, &pos1);
    122.  
    123.     // alliance einlesen
    124.     APIplayersTemp->alliance = 0;
    125.     fgetpos(fp, &pos1);
    126.     if (fgetc(fp) == 'a')
    127.     {
    128.         fgetpos(fp, &pos2);
    129.         if (pos2 == pos1) fseek(fp, 1, SEEK_CUR);
    130.         jo(34);
    131.         frtc(readString, READ_STRING_MAX_CHARACTERS, 34);
    132.         sscanf(readString, "%d", &APIplayersTemp->alliance);
    133.         fseek(fp, 2, SEEK_CUR);
    134.     } else {
    135.         fgetpos(fp, &pos2);
    136.         if (pos2 == pos1) fseek(fp, 1, SEEK_CUR);
    137.     }
    138.  
    139.     APIplayersTemp->next = NULL;
    140.  
    141.     printf("Spieler geladen: ID: %7d\tName: %s\n", APIplayersTemp->id, APIplayersTemp->name);
    142.  
    143.     fseek(fp, 1, SEEK_CUR);
    144.     char a = fgetc(fp);
    145.     printf("%c\n", a);
    146.     if (a == 'p') return 1;
    147.     return 0;
    148. }
    149.  
    150. fpos_t fpos()
    151. // zeigt mit der Watches-Funktion von CodeLite die derzeitige Position in der Datei
    152. {
    153.     fpos_t pos;
    154.     fgetpos(fp, &pos);
    155.     return pos;
    156. }
    157.  
    158. int apiloadplayers()
    159. {
    160.     // Datei öffnen
    161.     fp = fopen("players.xml", "r");
    162.     if (fp == NULL)
    163.     {
    164.         printf("Fehler: ""players.xml"" wurde nicht gefunden!\n");
    165.         return -1;
    166.     }
    167.  
    168.     // Speicher für den Header reservieren
    169.     APIplayers = (struct sAPIplayersHead*) malloc(sizeof(struct sAPIplayersHead));
    170.  
    171.     // Header einlesen
    172.     fgets(readString, READ_STRING_MAX_CHARACTERS, fp);
    173.     strtok(readString, delim);
    174.     APIplayers->xmlVersion = strnewcpy(strtok(NULL, delim));
    175.     strtok(NULL, delim);
    176.     APIplayers->encoding = strnewcpy(strtok(NULL, delim));
    177.  
    178.     fseek(fp, 21, SEEK_CUR);
    179.     frtc(readString, READ_STRING_MAX_CHARACTERS, 34);
    180.     APIplayers->xmlnsxsi = strnewcpy(readString);
    181.  
    182.     fseek(fp, 32, SEEK_CUR);
    183.     frtc(readString, READ_STRING_MAX_CHARACTERS, 34);
    184.     APIplayers->nNsSL = strnewcpy(readString);
    185.  
    186.     fseek(fp, 12, SEEK_CUR);
    187.     frtc(readString, READ_STRING_MAX_CHARACTERS, 34);
    188.     sscanf(readString, "%lli", &APIplayers->timestamp);
    189.  
    190.     fseek(fp, 11, SEEK_CUR);
    191.     frtc(readString, READ_STRING_MAX_CHARACTERS, 34);
    192.     APIplayers->serverId = strnewcpy(readString);
    193.  
    194.     // player einlesen
    195.     APIplayersTemp = APIplayers;
    196.     while (readplayerline()) ;
    197.  
    198.     printf("""players.xml"" erfolgreich geladen!\n");
    199.     fclose(fp);
    200.     return 0;
    201. }
    202.  
    203. int main()
    204. {
    205.     apiloadplayers();
    206.     getchar();
    207.     return 0;
    208. }
     

    Anhänge:

    Zuletzt bearbeitet: 14. Februar 2017
  2. sheel

    sheel I love Asm Administrator

    Hi

    wäre es möglich, die XML-Datei auch zu bekommen?
     
  3. Neonof

    Neonof Grünschnabel

    Nun sollte die verfügbar sein.
     
    sheel gefällt das.
  4. sheel

    sheel I love Asm Administrator

    edit: Ah, seh deinen neuen Beitrag erst... Folgendes wurde vorher geschrieben

    Sonst, was mir bisher auffällt:

    Denkst du nicht, eine XML-Lib oder zumindest ordentliches Parsen wäre besser als so fixe Sachen wie "jetzt 21 Byte weitergehen"? Ein Leerzeichen mehr in der Datei macht so alles kaputt.

    Egal wie es geparst wird, trenn das Auslesen vom Verarbeiten.

    feof/fgetc ist aus vielen Gründen problematisch (Seltsamkeiten in Windows mit -1, Langsamkeit, !feof ist keine Garantie für erfolgreiches Lesen, usw.). Besser mit fread eine größere Menge auf einmal einlesen und sich nebenbei auch von fread's Returnwert den Erfolg/Fehlschlag sagen lassen. ... Wenn die Dateien nicht zu groß werden, frag am Anfang die Dateilänge ab und lies einfach alles auf einmal in ein mallokiertes char-Array.

    Globale Variablen generell zu verwenden ist meistens schlecht. Besser alle weg und stattdessen lokale Vars und Funktionsparameter verwenden. zB. wenn du in einer Funktion etwas in pos1 speicherst, frtc aufrufst, und pos1 dann wieder verwendest (was in deinem Code auch wirklich vorkommt) ... ist die äußere Funktion wirklich in jedem dieser Fälle so programmiert, dass ein von frtc reingeschriebener Wert erwartet wird? Oder wurde nur zeitweise vergessen, dass frtc pos1 ja überschreibt?

    Bei den ganzen Dateifunktionen etc. (auch sscanf usw.) sollten die Returnwerte kontrolliert werden. Gut möglich dass da was nicht stimmt.

    Die globale Variable token wird nirgends verwendet.

    Du hast 3 malloc's, aber 0 free's.

    Code (Text):
    1. printf("""players.xml"" erfolgreich geladen!\n");
    macht nicht was du denkst. Verwenden
    Code (Text):
    1. printf(" \"players.xml\" erfolgreich geladen!\n");
     
    Neonof gefällt das.
  5. Neonof

    Neonof Grünschnabel

    Die Sache mit den pos1 etc... eigentlich sollte es das gar nicht geben. Das war eine Krücke um den Fehler von fgetc zu umgehen. Zum Ausprobieren war es global einfach weniger Arbeit... die Anwendung von frtc stimmt dennoch in jedem Fall.

    Das mit dem Freigeben des dynamisch reservierten Speichers fehlt deshalb, weil ich mich an dem Fehler aufgehängt habe. Das Programm ist ja lange nicht fertig.

    Soweit auf jeden Fall schon einmal vielen Dank für die Hinweise. Diese Einzelheiten werde ich natürlich nebenbei ausbessern.

    Zu der Nutzung von fgetc:
    An vordefinierte Funktionen hatte ich ehrlich gesagt noch gar nicht gedacht.
    fread oder fgets hatte ich erst vor zu verwenden, war dann aber nicht umsetzbar, weil fgets die einzelnen Elemente alle als eine Zeile erkennen würde und ich das Programm möglichst resourcenschonend schreiben wollte. fread benötigt ja eine feste Anzahl von Zeichen, die ausgelesen werden sollen. Da ich nicht sagen kann, wie lang beispielsweise ein Spielername sein wird, kann ich damit nicht zuverlässig je ein Element auslesen. Merkwürdig ist es dennoch. Bisher erklären diese Einzelheiten ja noch nicht, wie es passieren kann, dass sich die Funktionsweise von fgetc plötzlich verändert...

    Eine xml-Lib sollte das Problem wohl lösen und zudem sehr viel effizienter arbeiten.
    Ich habe dazu auf die Schnelle jetzt erst einmal diese Übersicht zu verschiedenen XML-Parser-Libraries gefunden. Die werde ich mir morgen mal ansehen. Eine bestimmte Empfehlung?
     
  6. cwriter

    cwriter Erfahrenes Mitglied

    Vom Ressourcen (Zeit) -Aufwand her ist fgetc() so ziemlich das Schlimmste...
    Die Reihenfolge (schnellste zuerst): fread(), fscanf(), fgets(), fgetc().
    Der Grund dafür ist schlicht, dass fgetc() bei jedem Aufruf an das Betriebssystem übergibt, sodass Kontextwechsel u.ä. nötig werden.

    Lies es ein, dann mach Stringmanipulationen. Braucht etwas mehr RAM, ja, aber ist insgesamt besser.

    Die ist immer am Besten.

    Gruss
    cwriter
     
    Neonof gefällt das.
  7. Neonof

    Neonof Grünschnabel

    Weiß einer von euch, ob es da bestimmte Standarts gibt, die allgemein bekannt und am funktionellsten sind oder sucht man sich für jeden Bedarf einfach irgendeine passende Librarie heraus?
    Ich habe da ja diese Übersicht einmal verlinkt.

    Mit Ressourcenschonend meinte ich auch den Arbeitsspeicher. Aber ich glaube, ich unterschätze einfach, wie viel so ein Gigabyte eigentlich ist xD

    Gruß,
    Neonof
     
  8. cwriter

    cwriter Erfahrenes Mitglied

    Was ist schon "allgemein bekannt"? RapidXML und TinyXML sind beide mehr oder weniger bekannt, die anderen auf deiner Liste eher weniger. Ich habe mal für ein Projekt selbst ein XML-Parser geschrieben, der allerdings nicht komplett war, also nicht alle XML-Features unterstützte (halt nur das, was ich auch brauchen wollte).

    Es ist immer gut, Ressourcen zu schonen. Würdest du auf einem Mikroprozessor mit 512 Bytes an RAM arbeiten, dann würde ich verstehen, wenn du nicht zuerst alles einlesen und dann verarbeiten willst. Bei modernen x86-Maschinen ist aber schon lange nicht mehr die Kapazität der Hardware wichtig, sondern vor allem die Zeit, die die Verarbeitung benötigt. Daher gilt: So viel wie möglich in den RAM schieben, aber nur solange, wie es nützlich ist. D.h.: Nimm dir ruhig den Platz, den du zum Arbeiten brauchst. Aber räume ihn, sobald du es nicht mehr benötigst.

    Gruss
    cwriter
     
    Neonof gefällt das.
Die Seite wird geladen...
Ähnliche Themen - fgetc hört mitten
  1. aminskee
    Antworten:
    18
    Aufrufe:
    2.825
  2. cwriter
    Antworten:
    4
    Aufrufe:
    876
  3. eeemoh
    Antworten:
    2
    Aufrufe:
    976
  4. prinzessin4444
    Antworten:
    3
    Aufrufe:
    1.692
  5. FBIagent
    Antworten:
    7
    Aufrufe:
    1.534