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

Neonof

Mitglied
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 :)

C:
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>

#define READ_STRING_MAX_CHARACTERS 100

char readString[READ_STRING_MAX_CHARACTERS], delim[] = {34, 0}, *token;
FILE *fp = NULL;
fpos_t pos1, pos2;

/* Statustype:
 * 0        Normal
 * 1    a    Administrator
 * 2    o    Vogelfreier (temporär)
 * 4    v    Urlaubsmodus
 * 8    b    gesperrt
 * 16    i    Inaktiv
 * 32    I    Inaktiv (30)
 * */
struct sAPIplayers
{
    int id;
    char* name;
    int alliance;
    int status;
    struct sAPIplayers* next;
} *APIplayersTemp = NULL;

struct sAPIplayersHead
{
    char* xmlVersion;
    char* encoding;
    char* serverId;
    time_t timestamp;
    char* nNsSL;
    char* xmlnsxsi;
    struct sAPIplayers* next;
} *APIplayers = NULL;

char* strnewcpy(char*_str_src) {
    size_t len = strlen(_str_src);
    char *newstr = (char*)malloc(len + 1);
    if (newstr == NULL) return NULL;
    strncpy(newstr, _str_src, len);
    newstr[len] = '\0';
    return newstr;
}

void frtc(char *dest, int lim, char delim)
// liest einen String bis zu delim ein
{
    char temp;
    int count = 0;
 
    while (!feof(fp) && (count++ < lim))
    {
        fgetpos(fp, &pos1);
        temp = fgetc(fp);
        fgetpos(fp, &pos2);
        if (pos1 == pos2) fseek(fp, 1, SEEK_CUR);
        if (temp == delim) break;
        *dest++ = temp;
    }
 
    *dest = 0;
}

int jo(char delim)
// schaltet die Position in der Datei hinter das nächste Vorkommen von delim
{
    fgetpos(fp, &pos1);
    while (!feof(fp) && (fgetc(fp) != delim))
    {
        fgetpos(fp, &pos2);
        if (pos1 == pos2) fseek(fp, 1, SEEK_CUR);
    }
    fgetpos(fp, &pos2);
    if (pos1 == pos2) fseek(fp, 1, SEEK_CUR);
    if (feof(fp)) return EOF;
    return 0;
}

int readplayerline()
{
    // neuen Speicherbereich reservieren
    APIplayersTemp = APIplayersTemp->next = (struct sAPIplayers*) malloc(sizeof(struct sAPIplayers));
 
    // Daten einlesen
    // id einlesen
    jo(34);
    frtc(readString, READ_STRING_MAX_CHARACTERS, 34);
    sscanf(readString, "%d", &APIplayersTemp->id);
 
    // name einlesen
    jo(34);
    frtc(readString, READ_STRING_MAX_CHARACTERS, 34);
    APIplayersTemp->name = strnewcpy(readString);
    fseek(fp, 1, SEEK_CUR);
 
    // status einlesen
    APIplayersTemp->status = 0;
    fgetpos(fp, &pos1);
    if (fgetc(fp) == 's')
    {
        fgetpos(fp, &pos2);
        if (pos2 == pos1) fseek(fp, 1, SEEK_CUR);
        jo(34);
        frtc(readString, READ_STRING_MAX_CHARACTERS, 34);
        char* token = readString;
        while (*token != 0)
        {
            if (*token == 'a') APIplayersTemp->status = 1;
            if (*token == 'o') APIplayersTemp->status += 2;
            if (*token == 'v') APIplayersTemp->status += 4;
            if (*token == 'b') APIplayersTemp->status += 8;
            if (*token == 'i') APIplayersTemp->status += 16;
            if (*token++ == 'I') APIplayersTemp->status += 32;
        }
        fseek(fp, 1, SEEK_CUR);
    } else fsetpos(fp, &pos1);
 
    // alliance einlesen
    APIplayersTemp->alliance = 0;
    fgetpos(fp, &pos1);
    if (fgetc(fp) == 'a')
    {
        fgetpos(fp, &pos2);
        if (pos2 == pos1) fseek(fp, 1, SEEK_CUR);
        jo(34);
        frtc(readString, READ_STRING_MAX_CHARACTERS, 34);
        sscanf(readString, "%d", &APIplayersTemp->alliance);
        fseek(fp, 2, SEEK_CUR);
    } else {
        fgetpos(fp, &pos2);
        if (pos2 == pos1) fseek(fp, 1, SEEK_CUR);
    }
 
    APIplayersTemp->next = NULL;
 
    printf("Spieler geladen: ID: %7d\tName: %s\n", APIplayersTemp->id, APIplayersTemp->name);
 
    fseek(fp, 1, SEEK_CUR);
    char a = fgetc(fp);
    printf("%c\n", a);
    if (a == 'p') return 1;
    return 0;
}

fpos_t fpos()
// zeigt mit der Watches-Funktion von CodeLite die derzeitige Position in der Datei
{
    fpos_t pos;
    fgetpos(fp, &pos);
    return pos;
}

int apiloadplayers()
{
    // Datei öffnen
    fp = fopen("players.xml", "r");
    if (fp == NULL)
    {
        printf("Fehler: ""players.xml"" wurde nicht gefunden!\n");
        return -1;
    }
 
    // Speicher für den Header reservieren
    APIplayers = (struct sAPIplayersHead*) malloc(sizeof(struct sAPIplayersHead));
 
    // Header einlesen
    fgets(readString, READ_STRING_MAX_CHARACTERS, fp);
    strtok(readString, delim);
    APIplayers->xmlVersion = strnewcpy(strtok(NULL, delim));
    strtok(NULL, delim);
    APIplayers->encoding = strnewcpy(strtok(NULL, delim));
 
    fseek(fp, 21, SEEK_CUR);
    frtc(readString, READ_STRING_MAX_CHARACTERS, 34);
    APIplayers->xmlnsxsi = strnewcpy(readString);
 
    fseek(fp, 32, SEEK_CUR);
    frtc(readString, READ_STRING_MAX_CHARACTERS, 34);
    APIplayers->nNsSL = strnewcpy(readString);
 
    fseek(fp, 12, SEEK_CUR);
    frtc(readString, READ_STRING_MAX_CHARACTERS, 34);
    sscanf(readString, "%lli", &APIplayers->timestamp);
 
    fseek(fp, 11, SEEK_CUR);
    frtc(readString, READ_STRING_MAX_CHARACTERS, 34);
    APIplayers->serverId = strnewcpy(readString);
 
    // player einlesen
    APIplayersTemp = APIplayers;
    while (readplayerline()) ;
 
    printf("""players.xml"" erfolgreich geladen!\n");
    fclose(fp);
    return 0;
}

int main()
{
    apiloadplayers();
    getchar();
    return 0;
}
 

Anhänge

  • players.xml
    144,7 KB · Aufrufe: 2
Zuletzt bearbeitet:
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:
printf("""players.xml"" erfolgreich geladen!\n");
macht nicht was du denkst. Verwenden
Code:
printf(" \"players.xml\" erfolgreich geladen!\n");
 
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?
 
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.
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.

Da ich nicht sagen kann, wie lang beispielsweise ein Spielername sein wird, kann ich damit nicht zuverlässig je ein Element auslesen.
Lies es ein, dann mach Stringmanipulationen. Braucht etwas mehr RAM, ja, aber ist insgesamt besser.

Eine xml-Lib sollte das Problem wohl lösen und zudem sehr viel effizienter arbeiten.
Die ist immer am Besten.

Gruss
cwriter
 
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
 
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?
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).

Mit Ressourcenschonend meinte ich auch den Arbeitsspeicher. Aber ich glaube, ich unterschätze einfach, wie viel so ein Gigabyte eigentlich ist xD
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
 
Zurück