C Noob Problemm

Affe007

Lt. Commander
Registriert
März 2004
Beiträge
1.463
Ich probier gerade so ein bissel rum und versuche ein String aus einer Datei einzulesen, der durch ein Leerzeichen getrennt ist. Wie mach ich das :D, das Beispiel hat keinen tieferen Sinn, ich will nur wissen wie man es machen könnte.

Code:
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
  char zeichen[100]={};
  FILE *fp = fopen("file.txt","w+");
  if (fp == NULL) 
    printf("Datei kann nicht gelesen werden!");
  else
  {
    fprintf(fp, "%s" "%c" "%s","Hallo",0x20,"Welt!");  
    fseek(fp,0L,SEEK_SET);
    fscanf(fp,"%s",zeichen);
    fclose(fp);
  }
  printf("%s\n",zeichen);
  system("PAUSE");	
  return 0;
}
 
Mal ein paar Anmerkungen zu deinem Code:

Code:
char zeichen[100]={};
                 [COLOR="SeaGreen"]^^^ das brauchst du hier nicht[/COLOR]

  FILE *fp = fopen("file.txt","w+");
  if (fp == NULL) 
    printf("Datei kann nicht gelesen werden!");
  else
  {
    fprintf(fp, "%s" "%c" "%s","Hallo",0x20,"Welt!");
                [COLOR="SeaGreen"]^^^^^^^^^^^^^^ kann zusammengefasst werden zu "%s%c%s" [/COLOR]
    fseek(fp,0L,SEEK_SET);
    fscanf(fp,"%s",zeichen);
                   [COLOR="#ff0000"]^^ sehr gefährlich wenn 'zeichen' nicht ausreichend Platz reserviert hat.[/COLOR]
    fclose(fp);
  }
  printf("%s\n",zeichen);
  system("PAUSE");	
  return 0;
}

Das ganze sieht dem Beispiel aus der Visual Studio Hilfe sehr ähnlich. Allerdings würde ich von der Verwendung von fscanf dringend abraten, wenn du Dateiinhalte lesen willst, deren Länge dein Programm nicht kennt. Wieviel Platz muss 'zeichen' dann reservieren? Die sicherere Methode ist fgetc. Auch dazu gibt es ein Beispiel in der MSDN.

j o e
 
Danke ich werd mal probieren :D


EDIT:
Code:
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
  char zeichen[100], i=0, c;
  FILE *fp = fopen("file.txt","w+");
  if (fp == NULL) 
    printf("Datei kann nicht gelesen werden!");
  else
  {
    fprintf(fp, "%s%c%s","Hallo",0x20,"Welt!");  
    fseek(fp,0L,SEEK_SET);
    do
    {
      c=fgetc(fp);
      zeichen[i]=c;
      i++;
    }
    while((c != EOF) && (i<99));
    fclose(fp);
  }
  printf("%s\n",zeichen);
  system("PAUSE");	
  return 0;
}

k hab mein Prob gelöst, jetzt aber zu der Länge bei unbekannten Dateien. Ich muss ja immernoch ein Array deklarieren, ist es auch möglich, dass so zu lösen, dass jede unbekannte Datei eingelesen werden kann mit einem Array der entsprechenden Größe?
 
Zuletzt bearbeitet:
Das genau ist die Kunst. :) Entweder du ermittelst vor dem Einlesen die Größe der Datei und reservierst dann zur Laufzeit deines Programms den benötigten Speicher (dynamische Speicherverwaltung, Pointer, malloc und free sind dazu die Stichwörter), oder du liest die Datei häppchenweise ein und verarbeitest die Daten auch so.

In jedem Fall solltest du davon ausgehen, dass die Datei immer größer sein kann als der Speicher den du für ein Array reservierst - möglicherweise auch größer als der Speicher, den du dynamisch mit malloc reservieren kannst.

Ein paar Anmerkungen zu deinem Code habe ich noch:

1.
while((c != EOF) && (i<99));

Warum fragst du i nicht auf <100 ab?

2.
Der Inhalt von zeichen ist kein null-terminierter String. Die Ausgabe mit printf ergibt, je nach dem wie der Speicher des arrays vorbelegt ist, ziemlichen Unsinn.

3.
Dein Programm schreibt EOF als letztes Zeichen in das Array zeichen. Frage dich ob du das willst.

j o e
 
Zuletzt bearbeitet:
Danke für die Hilfe habs jetzt auch mit dynamischer Speicherverwaltung hinbekommen... mit ein wenig probieren :D Noch eine Frage hätte ich siehe Code!
Code:
#include <stdio.h>
#include <stdlib.h>

int zeichen()
{
  int n=0;
  FILE *fp = fopen("file.txt","r");
  while(fgetc(fp) != EOF) n++;
  fclose(fp);
  return (n+1);
}

int main(int argc, char *argv[])
{
  char *inhalt, *anfang, c;
  int i;
  inhalt=(char*) malloc(zeichen());
  anfang=inhalt;
  FILE *fp = fopen("file.txt","w+");
  if (fp == NULL) 
    printf("Datei kann nicht gelesen werden!");
  else
  {
    fprintf(fp, "%s%c%s","Hallo",0x20,"Welt!");  
    fseek(fp,0L,SEEK_SET);
    while(1)
    {
      c=fgetc(fp);
      if (c != EOF)
      {
        *inhalt=c;
        inhalt++;
      }
      else
      {
        *inhalt='\0';
        break;
      }
    }
    fclose(fp);
  }
  printf("%s\n",anfang); [COLOR="Red"]warum eigentlich nicht *anfang??? versteh ich nicht so ganz, ich will doch den Inhalt nicht die Adresse  ausgeben[/COLOR]
  free(inhalt);
  system("PAUSE");	
  return 0;
}
 
Zuletzt bearbeitet:
Also zuerst mal zu deiner Frage:
Der Formatbezeichner "%s" weist den Compiler an den Speicherbereich ab einer angegebenen Adresse als String zu interpretieren. Der Compiler erwartet hierzu eine Adresse ab der er zeichenweise im Speicher liest, bis er auf eine binäre Null stößt. Er ermittelt also selbst den Speicherinhalt und benötigt nur die Startadresse.

Wenn du selbst auf Stringinhalte zugreifen wolltest, würdest du das evlt. so lösen:
Code:
unsigned char *myString = "Hello World";
unsigned char myCharacter;
unsigned int i = 0;
.
.
.
myCharacter = *myString; // hole erstes Zeichen

while( myCharacter != 0 ) {
    printf( "%c", myCharacter ); // oder was auch immer du mit dem Zeichen anstellen willst
    i++;
    myCharacter = *(myString+i); // hole nächstes Zeichen
}
Hierbei greifst du selbst zeichenweise auf den Speicherinhalt zu indem du mit dem Stern dereferenzierst. Mit "%s" gibst du dem Compiler an, dass er das für dich tun soll, ihm genügt die Adresse, den Inhalt ermittelt er selbst.


Ein paar Anmerkungen mal wieder zu deinem Code.

1. Die Funktion zeichen gibt 1 zurück, wenn sie die Datei nicht öffnen konnte. Sie sollte aber eher 0 zurückgeben.

2. Ich weiß, dass das nur ein Testprogramm ist, trotzdem stelle ich fest, dass du erst die Größe der Datei ermittelst, dann ihren Inhalt änderst (evtl. auch die Größe, z.B. wenn die Datei vorher noch gar nicht existierte) und dann in einen Speicher einliest der nicht zwangsläufig genug Platz zur Verfügung stellt, da sich beim Schreiben die Datei ja vergrößert haben könnte. Zum Testen solltest du vom Programmablauf her erst die Datei mit Inhalt erzeugen und mit fclose wieder schließen, dann die Größe ermitteln und danach den Inhalt lesen.

3. Die Anweisung free(inhalt) am Ende des Programms ist tödlich! Dass du den reservierten Speicher wieder freigeben musst, ist absolut korrekt, allerdings zeigt inhalt schon lange nicht mehr auf den Anfang des allozierten Bereichs. Du musst free aber genau die Adress mitgeben, die dir malloc geliefert hat und das ist in deinem Fall anfang. Als Tip würde ich empfehlen, auf Konstrukte wie irgendeinPointer++ zu verzichten und lieber mit einer Zählvariablen auf den Pointerinhalt zuzugreifen, z.B. *(irgendeinPointer + i); i++;.

j o e
 
Vielen dank für die Hilfe, vor allem weils eigentlich nur ein kleines Progi zum rumprobieren war :D

Code:
#include <stdio.h>
#include <stdlib.h>

FILE *fp;

int zeichen()
{
  unsigned int n = 0;
  fp = fopen("file.txt","r");
  if(fp == NULL) 
  {
    printf("Datei kann nicht gelesen werden!");
    fclose(fp);
    return(0);
  } 
  else
  {
    while(fgetc(fp) != EOF) n++;
    fclose(fp);
    return(n + 1);
  }
}

int main(int argc, char *argv[])
{
  char *inhalt, c;
  unsigned int i = 0;
  /* In Datei schreiben */
  fp = fopen("file.txt","w");    
  fprintf(fp, "%s", "Hallo Welt! Bla Blub, Blub Bla......."); 
  fclose(fp);
  /* Speicher reservieren für String */
  inhalt = (char*) malloc(zeichen());
  /* Datei lesen */
  fp = fopen("file.txt","r");    
  if (fp == NULL) 
    printf("Datei kann nicht gelesen werden!");
  else
  {
    while(1)
    {
      c = fgetc(fp);
      if(c != EOF)
        *(inhalt + i) = c;
      else
      {
        *(inhalt + i) = '\0';
        break;
      }
      i++;
    }
    fclose(fp);
  }
  /* Ausgabe */
  printf("%s\n\n",inhalt);
  free(inhalt);
  system("PAUSE");	
  return(0);
}
 
Zuletzt bearbeitet:
Ein Fehler ist noch drin:
Code:
int zeichen()
{
  unsigned int n = 0;
  fp = fopen("file.txt","r");
  if(fp == NULL) 
  {
    printf("Datei kann nicht gelesen werden!");
    fclose(fp);   [COLOR="Red"]<- Das hier verursacht eine Access Violation da fp==NULL[/COLOR]
    return(0);
  } 
  else
  {
    while(fgetc(fp) != EOF) n++;
    fclose(fp);
    return(n + 1);
  }
}

Daraus ergibt sich noch ein Folgefehler, nämlich, dass im folgenden dann mit inhalt = (char*) malloc(zeichen()); null Byte alloziert werden und später mit der Anweisung *(inhalt + i) = '\0'; in nicht reservierten Speicher geschrieben wird. Was dabei passiert kann man kaum vorhersagen, jedenfalls kann so ein Fehler zu Effekten führen, die sich auf vollkommen andere Programmteile auswirken. Die Verwendung von Pointern ist die empfindlichste und unsicherste Methode, die C zur Verfügung stellt und ist deshalb in anderen Sprachen wie z.B. Java in der Form nicht enthalten. Man sollte also bei der Verwendung von Pointern höllisch aufpassen was man tut und immer alle Rückgabewerte von Funktionen prüfen, die Einfluss auf Pointer haben und entsprechende Fehlerbehandlungen implementieren. Sogenannte RAM-Schreiber und Memory Leaks sind wirklich fies und in der Regel extrem schwer zu finden.

Desweiteren ist das Beenden der while-Schleife mittels break nicht unbedingt der beste Stil. Ich würde das in deinem Fall so lösen:
Code:
    c = fgetc(fp);
    while(c != EOF)
    {
      *(inhalt + i) = c;
      i++;
      c = fgetc(fp);
   }
   *(inhalt + i) = '\0';
Endloschleifen, aus deren Schleifenkörper herausgesprungen wird, lassen sich einfach schlechter lesen und verstehen.

Ich weiß, das ganze ist nur ein kleines Testprogramm zum rumspielen, deshalb kannst du es so lassen wie es ist, es funktioniert und macht nur dann Fehler, wenn man es bewusst und mit Gewalt dazu veranlasst. :) Nur mir geht es manchmal so, dass aus sowas dann doch ein richtiges Programm wird, oder ich Teile davon in anderen Programmen verwenden will. Dann ist es angnehm zu wissen, dass keine Fehler mehr drin sind.

j o e
 
Zuletzt bearbeitet:
Weiterer Fehler:

Return-Typ von fgetc ist int, nicht char. Enthält entweder EOF, ein Wert < 0, oder aber einen unsigned character (0-MAX_CHAR). Das macht Fehlerprüfung uneindeutig bis unmöglich.

Nachdem EOF auftritt, muss mit ferror geprüft werden, ob es tatsächlich EOF oder ein Lesefehler war. Beide Fälle werden durch das zurückgeben von EOF aus einer Leseroutine signalisiert.

Der Return-Typ von zeichen ist int. Negative Dateilängen gibt's nicht, zumal du intern mit unsigned int arbeitest. Falsche Semantik und potenzieller Datenverlust.

Das Einlesen aller Zeichen zur Bestimmung der Dateilänge ist grausam. Zum einen musst du davon ausgehen, dass du den Inhalt nie komplett im Speicher halten kannst. Zum anderen musst du dir verdeutlichen was ein Stream ist. Ein abstrakter Datenstrom. Du musst davon ausgehen, dass ein Stream tendenziell kein Ende hat (die Tastatur ist z.B. so ein Fall), zum anderen dass du einen Stream nicht einfach zurückspulen und mehrfach lesen kannst - vor allem wenn es nicht nötig ist. Wenn an der Standardeingabe die Tastatur hängt, kannst du die nicht zurückspulen ;) Genauso wenig wie ein Bandlaufwerk. Versuche immer, mit möglichst einem Lesevorgang auszukommen. In deinem Fall kannst du dir den Speicher entweder blockweise lesen, oder aber direkt wieder ausgeben. Es ist nicht wirklich nötig, die gesamte Datei in den Speicher zu prügeln. (Ja ich weiß, es ist ein Testprogramm, aber auch da muss man wichtige Grundsätze nicht vernachlässigen ;)) (Falls du die Dateilänge wirklich unbedingt brauchst (in deinem Beispiel braucht man sie nicht), dann benutz eine Betriebssystem-Funktion, auch wenn du dadurch plattformunabhängigkeit verlierst)

@Joe67: Dass bei fclose( 0) eine AV auftritt, ist nicht festgelegt. Es ist schlichtweg undefiniert (was heißen kann, das sogar ein sinnvolles Verhalten implementiert sein könnte - aber keine Garantie auf garnichts) und deshalb sollte man sowas nie tun.

@Affe: Auch wenn's nicht unbedingt nett klingt (... tut's wohl nie *G*), das ist lediglich konstruktive Kritik ;) Soweit isses doch schonmal garnicht schlecht.
 
Zuletzt bearbeitet:
Zurück
Oben