Probleme bei Programmieraufgabe



  • Hallo, zusammen!

    So viel vorweg:
    Ich bin kompletter Anfänger in C und auch sonst kein Programmierprofi.
    Daher wäre ich echt dankbar, wenn ich hier Hilfe finden würde, ich gebe mir auch Mühe diese gut umzusetzen und dazuzulernen.

    Zum Problem:
    Ich habe die Aufgabe, ein Programm in C (nicht C++!) zu schreiben, welches eine txt-Datei nach einer Gerätenummer durchsuchen soll, die entsprechende Zeile im Programm löschen soll und eine neue txt-Datei erstellen soll, in der die Einträge der Zeile formatiert untereinander angeordnet werden.
    Sinn des Ganzen ist es, die neue Datei als eine Art Geräteettiket ausdrucken zu können.

    Ich schreibe und kompiliere in Orwell Dev C++ (dürfte die neueste Version sein).

    Eine beispielhafte gewünschte Anfangsdatei:

    000000005;ZKG22;03B1;1.25;zkg22-a1.bin;A
    000000006;ZKG22;03B1;1.25;zkg22-a1.bin;A
    000000007;ZKG22;03B1;1.25;zkg22-a1.bin;A
    000000008;ZKG22;03B1;1.25;zkg22-a1.bin;A
    000000010;ZKG22;03B1;1.25;zkg22-a1.bin;A
    000000011;ZKG22;03B1;1.25;zkg22-a1.bin;A
    000000012;ZKG22;03B1;1.25;zkg22-a1.bin;A
    000000013;ZKG22;03B1;1.25;zkg22-a1.bin;A
    

    Das bisherige Programm:

    #include <stdio.h>
    #include <string.h>
    
    void main(void)
    {
      // Variablen
      char geraetenummer[9];
      char geraetetyp[5];
      char schluessel[4];
      char startwert[4];
      char firmware[12];
      char kennzeichen[1];
    
      char satz[250];      // ganze Zeile
      char suchnummer[9];   // gesuchtes Gerät
      int  gefunden = 0;   // Flag, ob gefunden
    
      // Zeichenketten fuer Gesamtdatei
      char dateiinhalt[1000];       // Maximalgroesse der Datei 
      dateiinhalt[0] = '\0';        
    
      // Einlesen der Datei 
      // Oeffnen  
      FILE *datei;
      if ((datei = fopen("Beispielliste.txt", "r")) == NULL)
      {
        printf("\nDatei kann nicht zum Lesen geoeffnet werden!\n");
        return;
      }
      // komplette Liste anzeigen
      printf("Alle Saetze der Datei:\n");
      while((fgets(satz, 250, datei)) != NULL )
        printf("%s", satz);
      // Datei wieder schliessen
      if (fclose(datei) == EOF)
        printf("\nFehler beim Schliessen der Datei!\n");
    
      // Abfrage nach zu druckender Geraetenummer
      printf("Bitte den Namen des zu loeschenden Geraets eingeben:\n");
      gets(suchnummer);
    
      // Datei erneut öffnen
      // gewünschtes Gerät in Zeichenarray schreiben
      if ((datei = fopen("Beispielliste.txt", "r")) == NULL)
      {
        printf("\nDatei kann nicht zum Lesen geoeffnet werden!\n");
        return;
      }
      while((fgets(satz, 250, datei)) != NULL )   // alle Zeilen lesen
      {
        sscanf(satz, "%s", geraetenummer);  // Geraetenummer lesen
        printf("\n%s\n", geraetenummer);
        if (strcmp(geraetenummer, suchnummer) != 0)  // falls keine Uebereinstimmung
          {strcat(dateiinhalt, satz); }     // an Zeichenarray anfuegen
        else
          gefunden = 1;
      }
      if (fclose(datei) == EOF)
        printf("\nFehler beim Schliessen der Datei!\n");
    
      // Datei neu schreiben, falls Geraetenummer gefunden
      if (gefunden == 1)
      {
        if ((datei = fopen("Beispielliste.txt", "w")) == NULL)
        {
          printf("\nDatei kann nicht zum Lesen geoeffnet werden!\n");
          return;
        }
        if(fprintf(datei, "%s", dateiinhalt) < 0)
          printf("Fehler beim Schreiben der Zeichenkette in Datei");
        else
          printf("Satz mit eingegebenem Namen wurde geloescht.");
        if (fclose(datei) == EOF)
          printf("\nFehler beim Schliessen der Datei!\n");
      }
      else
        printf("Eingegebener Name wurde nicht gefunden.");
    }
    

    Bisher habe ich versucht, nur die Funktion des Löschens umzusetzen.
    Das Problem: An einem Rechner mit Windows 7 und 64bit hat das Programm funktioniert, an zwei Rechnern mit 32bit und Windows XP nicht und auch nicht an einem Linux-Rechner.
    Bei Windows XP kommt immer eine Fehlermeldung namens:
    "Ein Fehler ist aufgetreten, das Programm musste beendet werden. Fehlerbericht senden..."
    Beim Kompilieren wird mir kein Fehler angezeigt.

    Das Programm zeigt wie gewünscht die gesamte Liste an, es kommt die Benutzeraufforderung zur Eingabe der Gerätenummer, nachdem er die erste Zeile der Liste durchsucht hat, kommt aber die Fehlermeldung.

    Das formatierte Untereinanderschreiben in eine neue Datei habe ich noch nicht programmiert, da fehlt mir aber auch die Idee, wie ich die Zeilen in die einzelnen Variablen wie Gerätenummer, Firmwaredatei, usw. aufteilen kann, um diese Variablen dann untereinander formatiert auszugeben.

    Ich wäre wirklich dankbar, wenn hier jemand Fehler im Programm entdecken würde bzw. ein einfacher Weg für das Programm ist natürlich genauso erwünscht.
    Ebenso würden mir Tipps zum Umsetzen des zweiten Programmteils (formatiertes Untereinanderschreiben) wirklich weiterhelfen.

    Vielen Dank schonmal im Voraus, ich wäre euch da wirklich super dankbar. 🙂 👍



  • Wo immer du auch C gerade lernst - rennt weg. Nein, wirklich!

    Wo lernt man heutzutage noch gets zu verwenden? Was zum Teufel?!

    C_AMATEUR schrieb:

    Ich schreibe und kompiliere in Orwell Dev C++ (dürfte die neueste Version sein).

    Wenn der Compiler dir keine Fehlermeldung für gets raushaut, ist er scheiße, so einfach ist das!

    Mein Compiler sagt mir das sogar zweimal:

    Warnung: »gets« is deprecated (declared at /usr/include/stdio.h:638) [-Wdeprecated-declarations]
       gets(suchnummer);
    
    warning: the `gets' function is dangerous and should not be used.
    

    Warum öffnest du die Datei zweimal? Auf Systemen, die dir Dateigrößen anzeigen lassen können, kannst du stat verwenden und dir damit die Dateigröße holen und dann Speicher reservieren. Und selbst auf Systemen, wo das nicht möglich ist (werden nicht viele sein, aber es gibt sie), kannst du fseek verwenden, um den Deskriptor zurückzusetzen.

    Deine sscanf -Argumente sind fehlerhaft, egal, wie oft ich 000000005 angegeben habe, hat das Programm den Datensatz nicht gefunden. WAS daran nicht stimmt, lass ich dich aber selbst herausfinden.

    C_AMATEUR schrieb:

    "Ein Fehler ist aufgetreten, das Programm musste beendet werden. Fehlerbericht senden..."
    Beim Kompilieren wird mir kein Fehler angezeigt.

    Dann hast du einen Logikfehler drin. Eigentlich ganz einfach. Debugger laufen lassen und prüfen, wo's kracht. Bei mir auf meiner Linux-Maschine ist nichts kaputtgegangen, aber das muss nichts heißen. Und richtig in den Code gestiegen bin ich auch nicht.



  • Deine Arrays sind für C-Strings zu klein.

    "000000005" sind 9 Ziffern und ein '\0' Zeichen für das Stringende.

    gets ist böse und daher im neusten C-Standard abgekündigt.

    scanf liest bei %s bis zum nächsten Whitespace (Leerzeichen, Tabulator, Zeilenende).
    Für die ganze Zeile ist aber dein Array geraetenummer viel zu klein.



  • C_AMATEUR schrieb:

    ein Programm in C (nicht C++!)

    Stelle sicher, dass deine IDE auch entsprechend konfiguriert ist.

    Du hast Probleme mit der Typisierung von C (die technische Umsetzung ist auch verbesserungswürdig aber hier erstmal unwichtiger).
    Ohne Typen läuft in C nichts, dein Anwender macht eine Eingabe die du als String liest (gets) und mit strcmp vergleichst, d.h. was passiert wohl, wenn du mit strcmp z.B. "5" mit "000000005" vergleichst? Das wird nie funktionieren.
    Für numerische Vergleiche bietet sich an, die Werte auch als int (oder verwandt) zu definieren und dann auch den Vergleich mit int durchzuführen, das verhindert z.B. deinen Fehler bei den führenden Nullen.

    Du solltest mit dem Datendesign beginnen, also für jedes Feld eine Variable mit passendem Typ definieren. Überlicherweise verwendet man in C dafür struct, bei dir also statt

    char geraetenummer[9];
      char geraetetyp[5];
      char schluessel[4];
      char startwert[4];
      char firmware[12];
      char kennzeichen[1];
    

    besser

    typedef struct {
      unsigned long geraetenummer;
      char geraetetyp[6];
      char schluessel[5];
      double startwert;
      char firmware[13];
      char kennzeichen;
    } Geraet;
    

    Diesen struct-Typ kannst du sehr gut für deine formatierte Ausgabe verwenden.
    Zusätzlich prüft sscanf auch die Umwandlungen und kann somit Fehler dabei aufzeigen. Außerdem kann in deinem (einfachen) Anwendungsfall ein 1x sscanf auch gleich das splitten bei ";" übernehmen. (Z.19)
    Einlesen tust du dann zeilenweise mit sscanf (nach fgets), damit kannst du einfach die einzelnen Werte umwandeln und den struct-Elementen zuweisen.
    Das ist dann schon das Programmdesign: implementiere für jede Aufgabe (mind.) eine Funktion, bei dir also z.B.

    void roheAusgabe(FILE*f)
    {
      char z[1000];
      rewind(f); /* auf Anfang */
      while( fgets(z,1000,f) )
      ...
    }
    void formatierteAusgabe(Geraet g)
    {
      printf("%lu | %s | %s | %f | %s | %c\n",g.geraetenummer,g.geraetetyp,......)
    }
    void gefilterteFormatierteAusgabe(FILE*f,unsigned long nr)
    {
      char z[1000];
      Geraet g;
      rewind(f); /* auf Anfang */
      while( fgets(z,1000,f) )
      {
         if( 6==sscanf(z,"%lu;%5[^;];%4[^;];%lf;%12[^;];%c",&g.geraetenummer,g.geraetetyp......) )
         {
           if( nr != g.geraetenummer ) /* hier dein Test */
              formatierteAusgabe(g);
         }
      }
      ...
    }
    int main()
    {
      unsigned long nr;
      FILE *f = fopen("blabla.txt","r");
    
      roheAusgabe(f);
      while( 1!=scanf("%lu",&nr) );
    
      gefilterteFormatierteAusgabe(f,nr);
    
      fclose(f);
      return 0;
    }
    


  • Ich mach das ja nur noch als Hobby und um andere Forenmitglieder aufzuheitern/nerven, aber das kommt mir auf den ersten überfliegenden Blick recht fragwürdig vor:

    if (fclose(datei) == EOF)
    


  • Ich mach das ja nur noch als Hobby und um andere Forenmitglieder aufzuheitern/nerven, aber das kommt mir auf den ersten überfliegenden Blick recht fragwürdig vor:

    wieso?

    http://www.cplusplus.com/reference/cstdio/fclose/

    If the stream is successfully closed, a zero value is returned.
    On failure, EOF is returned.



  • Hallo, alle zusammen!

    Erstmal vielen Dank für eure zahlreichen Kommentare. 👍

    Soviel vorweg:
    Ich habe gets als ganz normale Funktion gelernt, wenn die wirklich gefährlich ist, kann ich das auch nicht nachvollziehen warum.
    Unser Dozent kennt sowieso scheinbar nur die älteren C-Versionen vor 2000 und entsprechend manche Funktionen nicht. 😕

    Orwell Dev C++ ist die einzige integrierte Entwicklungsumgebung, die ich benutzen darf. Eigentlich sollte ich sogar in Notepad schreiben und dass dann per MinGW-Compiler per Eingabeaufforderung von Windows kompilieren.
    Das ist mir aber definitiv zu nervig.
    Da ich das Programm als C-File abspeichere, durfte das auch C sein und kein C++ akzeptieren.
    Da mein Dozent zur Bewertung das Programm auch mit MinGW kompiliert, muss das Programm auch mit dem Kompiler funktionieren, sonst hilft mir das alles leider nicht. ⚠

    Zur Umsetzung des Programms:
    Es ist stark an einem Beispielprogramm orientiert, das eine ähnliche Aufgabe (Tabelle mit Name, Telefonnummer, E-Mail-Adresse; löscht die Zeile mit eingegebenem Namen) erfüllt und das wir bekommen haben.

    Jedenfalls danke für eure Verbesserungen. Ich werde mir das Programm in aller Ruhe mal durchschauen und versuchen es zu verstehen.

    Weitere Tipps sind natürlich gern gesehen, da ich im Programmieren generell wirklich nicht so begabt bin, fällt mir das Alles ziemlich schwer.
    Ich werde aber mein Bestes geben, ich erwarte nicht, dass ihr meine Aufgabe macht. 😉



  • C_AMATEUR schrieb:

    Ich habe gets als ganz normale Funktion gelernt, wenn die wirklich gefährlich ist, kann ich das auch nicht nachvollziehen warum.
    Unser Dozent kennt sowieso scheinbar nur die älteren C-Versionen vor 2000 und entsprechend manche Funktionen nicht. 😕

    Also kurz: dein Dozent ist vollkommen inkompetent und sollte alles, aber auf keinen Fall Leute unterrichten.

    Großartig.

    https://security.web.cern.ch/security/recommendations/en/codetools/c.shtml

    The stdio gets() function does not check for buffer length and always results in a vulnerability.

    Wenn ich du wäre, würde ich deinem Dozenten richtig die Meinung geigen. Meine Fresse, tut mir Leid, aber dieses Maß an Inkompetenz regt mich auf gerade. Seit wie vielen verschissenen Jahren ist bekannt, dass gets ein radioaktiver Haufen Sondermüll ist? Und dann bekommen da Hunderte, vielleicht sogar Tausende Leute beigebracht, dass das "nur eine ganze normale Funktion" ist.

    C_AMATEUR schrieb:

    Orwell Dev C++ ist die einzige integrierte Entwicklungsumgebung, die ich benutzen darf. Eigentlich sollte ich sogar in Notepad schreiben und dass dann per MinGW-Compiler per Eingabeaufforderung von Windows kompilieren.

    Arbiträre Restriktionen, super!



  • C_AMATEUR schrieb:

    Soviel vorweg:
    Ich habe gets als ganz normale Funktion gelernt, wenn die wirklich gefährlich ist, kann ich das auch nicht nachvollziehen warum.

    Dann lies dir mal https://www.c-plusplus.net/forum/338375 durch.

    Ganz konkret bei dir: Was ist, wenn der Nutzer mehr als 8 Zeichen eingibt?

    Dann wird über die Arraygrenzen hinaus geschrieben. Und gets bietet keine Möglichkeit, dies zu unterbinden.



  • Gibt es eigentlich eine Seite, auf der man solche Informationen zusammengefasst findet? Also kritische C Befehle?





  • Swordfish schrieb:

    ähm. LESEN!!

    dachschaden schrieb:

    https://security.web.cern.ch/security/recommendations/en/codetools/c.shtml

    Ja das hatte ich schon gesehen, ich kann mir nur nicht vorstellen, dass das alle sind. Ich hatte auch mal gelesen, dass memcpy() genauso höchst gefährlich sei und es ist nicht aufgeführt, daher dachte ich, die Liste ist womöglich nicht vollständig(wurde glaube ich auch aus dem jetzigen Standard entfernt, vielleicht steht es deshalb nicht dabei).



  • C_Newbe schrieb:

    Ich hatte auch mal gelesen, dass memcpy() genauso höchst gefährlich sei und es ist nicht aufgeführt, daher dachte ich, die Liste ist womöglich nicht vollständig(wurde glaube ich auch aus dem jetzigen Standard entfernt, vielleicht steht es deshalb nicht dabei).

    Bei memcpy wird die Länge des Speicherbereichs mit angegeben. Daher ist das kein Problem.
    Wenn die Länge größer ist als der Zielbereich, dann hat der Programmierer Scheiße gebaut.
    Das kann man bei strcpy auch vorher testen.

    Bei gets hast du keine Kontrolle, weil es eine Nutzereingabe ist.



  • DirkB schrieb:

    C_Newbe schrieb:

    Ich hatte auch mal gelesen, dass memcpy() genauso höchst gefährlich sei und es ist nicht aufgeführt, daher dachte ich, die Liste ist womöglich nicht vollständig(wurde glaube ich auch aus dem jetzigen Standard entfernt, vielleicht steht es deshalb nicht dabei).

    Bei memcpy wird die Länge des Speicherbereichs mit angegeben. Daher ist das kein Problem.
    Wenn die Länge größer ist als der Zielbereich, dann hat der Programmierer Scheiße gebaut.
    Das kann man bei strcpy auch vorher testen.

    Bei gets hast du keine Kontrolle, weil es eine Nutzereingabe ist.

    Also ist die Liste auf der CERN Seite so ziemlich vollständig?



  • Hallo, zusammen!

    Wollte damit sagen, dass ich nicht nachvollziehen kann, warum er uns das dann beibringt.

    Ich bin nicht in der Position, ihm das so sagen zu können.
    Auch wenn sein Kenntnisstand wohl laut euren Aussagen mangelhaft ist, weiß ich zum Thema C aber immernoch wesentlich weniger.

    Zum von Wutz geposteten Programm:
    Bisher blicke ich da ehrlich gesagt noch nicht komplett durch.
    Ich hoffe, das ändert sich noch.
    Wie gesagt, ich bin kompletter Anfänger in C, von daher steht für mich da erstmal ein kompletter Haufen von Zahlen und Buchstaben, da muss ich mir wohl noch etwas Zeit nehmen um das Zusammenspiel der einzelnen Funktionen zu verstehen.
    Nur dass ich das nicht falsch verstanden habe:
    Lauffähig ist das von Wutz gepostete Programm ja noch nicht? Nicht, dass die IDE jetzt schon lauffähige Programme mit Fehlern beim Kompilieren anzeigt. 😕


  • Mod

    C_AMATEUR schrieb:

    Wollte damit sagen, dass ich nicht nachvollziehen kann, warum er uns das dann beibringt.

    Das liegt da dran, dass C dem Programmierer sehr viel Freiheit und damit auch sehr viel Verantwortung überträgt. Man kann schnell viel falsch machen, vieles davon auch noch so, dass es zunächst richtig aussieht und der Fehler im Details versteckt ist. Viele Lehrer haben nie richtig C gelernt. Sie sind keine Profi-Programmierer, die sich täglich mit diesen Fallstricken beschäftigen, sondern haben entweder selber von solch oberflächlichen Lehrern gelernt. Oder sie haben nur Erfahrung mit irgendwelchen Lehrsprachen wie Pascal (die sehr viel vergebender für den Anfänger sind, dafür aber andere Nachteile haben) und meinen nun, dass sie auch C lehren könnten, indem sie nur BEGIN...END durch { } ersetzen, ohne sich tiefgehend mit der Sprache zu beschäftigen.

    Lauffähig ist das von Wutz gepostete Programm ja noch nicht? Nicht, dass die IDE jetzt schon lauffähige Programme mit Fehlern beim Kompilieren anzeigt. 😕

    Nein, Wutz hat nur ein paar Schlüsselteile gezeigt, wie es besser ginge. Der Hilfesuchende muss dann mitdenken und den Code verstehen, bevor er ihn ergänzen und somit benutzen kann. Daher werden hier bewusst selten vollständige Lösungen gepostet, weil erfahrungsgemäß viele Leute dann bloß den Code blind übernehmen, ohne sich damit zu beschäftigen.



  • Nein, das Beispiel ist (bewusst) nicht lauffähig.
    Allerdings ist dieses Beispiel sehr einfach gehalten (main+3 Funktionen), das sollte jetzt nicht so schwer verständlich sein, zumal die Programmstruktur sprechend ist.



  • Gast3 schrieb:

    Ich mach das ja nur noch als Hobby und um andere Forenmitglieder aufzuheitern/nerven, aber das kommt mir auf den ersten überfliegenden Blick recht fragwürdig vor:

    wieso?

    http://www.cplusplus.com/reference/cstdio/fclose/

    If the stream is successfully closed, a zero value is returned.
    On failure, EOF is returned.

    Mag ja recht sein, ich hab es aber nie so gesehehen und finde es immernoch etwas fragwürdig. Aber ich bin nun auch nicht der Hellste.



  • Hallo EOP,

    störst du dich an dem Wort EOF (end of file)? Dies ist einfach als -1 definiert, d.h. die Beschreibung hätte man auch so schreiben können:

    If the stream is successfully closed, a zero value (0) is returned.
    On failure, -1 is returned.



  • Th69 schrieb:

    Hallo EOP,

    störst du dich an dem Wort EOF (end of file)? Dies ist einfach als -1 definiert, d.h. die Beschreibung hätte man auch so schreiben können:

    If the stream is successfully closed, a zero value (0) is returned.
    On failure, -1 is returned.

    Der C-Standard ist etwas großzügiger:

    C99-standard schrieb:

    EOF
    which expands to an integer constant expression, with type int and a negative value, that
    is returned by several functions to indicate end-of-file, that is, no more input from a
    stream;

    C99-standard schrieb:

    The fclose function returns zero if the stream was successfully closed, or EOF if any
    errors were detected.


Log in to reply