Icon Ressource

[C] Benutzereingaben oder Textdateien zeilenweise einlesen

Q 1: Welche Funktionen bietet C an?
A: Die entsprechenden Funktionen finden sich im Header stdio.h und müssen in der Lage sein, an einem Zeilenumbruch abzubrechen (Zeichenkette lesen) oder einen Zeilenumbruch zu detektieren (zeichenweise lesen)
fgetc - Zeichen von einem Stream (Datei oder Benutzereingabe) lesen
fgets - Zeichenkette von einem Stream (Datei oder Benutzereingabe) lesen
getc - Zeichen von einem Stream (Datei oder Benutzereingabe) lesen
getchar - Zeichen von stdin (Benutzereingabe) lesen
gets - Zeichenkette von stdin (Benutzereingabe) lesen (obsolet)
fscanf - Formatierte Daten von einem Stream (Datei oder Benutzereingabe) lesen
scanf - Formatierte Daten von stdin (Benutzereingabe) lesen

Q 2: Welche Funktionen davon sollte ich NICHT verwenden?
A: Wir wissen nicht was ein Benutzer eingeben wird oder was in einer Textdatei zu finden ist. Auch wenn wir einen Benutzer bitten nur eine bestimmte Eingabe vorzunehmen oder wenn wir annehmen in einer Datei befinden sich nur Zeilen, die eine bestimmte Länge nicht überschreitet, können wir nicht sicherstellen, dass das auch tatsächlich so sein wird. Alles was passieren kann, wird passieren, es ist nur eine Frage der Zeit. Also ist es die Aufgabe des Programmierers, sein Programm so abzusichern, dass Fehler beim Lesen der Daten abgefangen werden und keinesfalls Speicherverletzungen vorkommen können, die einen Programmabsturz oder Schlimmeres verursachen könnten.
Wenn wir also davon ausgehen müssen, dass wir nicht wissen wie lang die eingelesene Zeichenkette sein wird, dürfen wir keinesfalls Funktionen verwenden, die die Eingabe nicht limitieren. Dies betrifft Funktionen, die eine Zeichenkette einlesen, ohne dass man ihnen die maximale Länge übergeben kann.
- char* gets(char* str)
Wie man sieht wird lediglich ein Pointer auf einen Speicherbereich übergeben. Wie groß dieser ist, kann die Funktion nicht feststellen. So etwas schreit regelrecht nach einem Pufferüberlauf. Diese Funktion ist also ein absolutes No Go und darum auch zu Recht bereits aus dem C-Standard geflogen.
- int fscanf(FILE* stream, const char* format, ... )
Formatierte Daten zu lesen, ist oft bequem. Für das Einlesen einer Zeichenkette (mit Typbezeichner %s) gilt hier aber das gleiche wie bei gets(). Es ist immerhin möglich im Typbezeichner die Länge der zu lesenden Zeichenkette anzugeben, diese muss der Puffergröße - 1 entsprechen. Für numerische Typen ist die Funktion ggf. verwendbar, da dort die die Größe des Speicherbereiches durch den Typ selbst bekannt ist. Wird die Typbreite durch die Eingabe überschritten, ist der konvertierte Wert jedoch falsch. Die Funktion selbst bietet keine Möglichkeit diesen Fehler zu erkennen. Es gibt außerdem keinen Rückschluss darauf, bei welchem Zeichen die Konvertierung abgebrochen wurde. Eine Analyse der eingelesenen bzw. im Stream verbleibenden Zeichen ist erforderlich.
- int scanf(const char* format, ... )
Hier gelten die gleiche Einschränkung, wie für fscanf(). Zusätzlich ist man lediglich in der Lage, vom stdin zu lesen.

Q 3: Dann bleibt also nur fgets, um eine Zeichenkette zu lesen?
A: Ja, wenn man eine Zeichenfolge als Block einlesen will.
- char* fgets (char *str, int num, FILE* stream)
Hier muss man den Parameter num angeben, der die Größe des Speicherbereichs angibt. Eine Speicherverletzung ist somit ausgeschlossen. Es gibt aber einiges zu beachten:
  • Der Speicherbereich muss groß genug sein, um die Zeile, den Zeilenumbruch (der immer mit eingelesen wird) und die Nullterminierung aufzunehmen.
  • Ist der Speicherbereich zu klein, werden num-1 Zeichen eingelesen und die Nullterminierung gesetzt. Die restlichen Zeichen und der Zeilenumbruch verbleiben im Eingabepuffer und warten darauf gelesen zu werden.

Q 3.1: Woher weiß ich, ob der Speicherbereich groß genug gewählt ist.
A: Wie bei der Antwort auf Q2 schon angemerkt, du kannst es nicht wissen. Es gibt drei Herangehensweisen:
  1. Je größer der Puffer gewählt wird, desto geringer ist die Wahrscheinlichkeit dass dessen Grenzen erreicht werden. Wenn man nur kurze Eingaben, wie "ja" oder "nein" erwartet, ist ein Puffer für 100 Zeichen sicher ausreichend groß gewählt.
  2. Wenn Eingaben, die eine bestimmte Länge überschreiten, bei der nachfolgenden Verarbeitung sowieso zum Fehler führen würden, muss man den Puffer nicht unnötig groß wählen. Soll die Eingabe bspw. zu einem int konvertiert werden, so benötigt man maximal 10 Stellen für die Ziffern + 1 Stelle für ein eventuelles Vorzeichen + 1 Stelle für den Zeilenumbruch + 1 Stelle für die Nullterminierung (wenn man von einer Implementierung eines int mit einer Breite von 32 Bit ausgeht).
  3. Zum Teil hat das Betriebssystem bereits Limits, die man nicht überschreiten kann. Bspw. kann eine Benutzereingabe in das Windows Konsolefenster max. 4094 Zeichen lang sein. Weitere Tastatureingaben werden ignoriert, lediglich der Abschluss der Zeile mit Enter wird noch akzeptiert. Somit ist eine Puffergröße von 4096 Zeichen (wegen Zeilenumbruch und Nullterminierung) ausreichend.

Q 3.2: Falls die Größe des Speicherbereichs überschritten wird, wie werde ich die verbleibenden Zeichen im Eingabepuffer los?
A: Man ließt immer wieder, dass man dies mit fflush(stdin) erledigen könnte. Das ist nicht korrekt und ist nur für Windows garantiert. Der C Standard geht hier von undefiniertem Verhalten aus. Man sollte also einen Weg wählen, der plattformunabhängig funktioniert. Z.B.:
while ((int ch = fgetc(pFile)) != '\n' && ch != EOF);

Q 4: Was ist nun aber, wenn ich wirklich nicht abschätzen kann, wie lang die Zeile sein wird?
A: Dann bleibt dir immer noch das zeichenweise Einlesen. Speicher kannst du bspw. mit malloc() und realloc() dynamisch reservieren. Sobald ein Zeilenumbruch gelesen wird oder das Streamende erreicht ist, brichst du das Einlesen ab.
- int fgetc(FILE* stream) und int getc(FILE* stream)
Beide Funktionen sind völlig äquivalent. Es wird jeweils ein Zeichen aus dem Stream gelesen und dessen ASCII Wert zurück gegeben.
- int getchar(void)
Eine weitere Möglichkeit, es wird aber lediglich vom stdin gelesen.

Q 5: Wenn die Eingabefunktionen für formatierte Daten nicht alle Fehler abfangen, wie soll ich dann meine Eingaben zu einem numerischen Wert konvertieren?
A: Der Header stdlib.h bietet die strto...() Funktionen (z.B. long int strtol(const char* str, char** endptr, int base) für die Konvertierung zu einem long int). Mit diesen Funktionen ist man in der Lage zu erkennen, ob die gesamte Zeichenkette konvertierbar war oder vorher abgebrochen wurde (endptr). Außerdem wird errno gesetzt, wodurch indiziert wird, ob die Typbreite überschritten wurde.
Verwende niemals die ato...() Funktionen. Damit ist dies alles nicht möglich, nicht einmal die Unterscheidung, ob der Rückgabewert 0 ist, weil die Eingabe zu 0 konvertiert wurde oder weil ein Fehler aufgetreten ist.

Q 6: Alle Eingabefunktionen benötigen Enter oder einen Zeilenumbruch um zurück zu geben. Gibt es auch eine Funktion, die bei einem beliebigen Tastendruck den ASCII Wert sofort zurück gibt?
A: Grundsätzlich nein. Keine der Standardfunktionen zeigt so ein Verhalten. Die getch() Funktion wird aber verbreitet angewendet. Sie findet sich unter Windows im Header conio.h und bei *nixoiden Betriebssystemen im (n)curses.h.

Q 7: Gibt es weitere plattform- oder compilerspezifische Funktionen, die Anwendung finden könnten?
A: Natürlich. Beispielhaft:
- Die getline() Funktion als GNU-Erweiterung, die speziell zu diesem Zweck entwickelt wurde.
- Unter Windows gibt es spezifische Funktionen mit Anhang _s (z.B. gets_s() als Pendant zur gets() Standardfunktion). Diese Funktionen haben eine veränderte Syntax, um Speicherverletzungen zu verhindern, werden bislang aber nur vom VC (MS Visual Studio) unterstützt. Zu finden sind sie in den MS Docs als Verlinkung in den Referenzen der Standardfunktionen.

Q 8: Viel Theorie. Wie würde denn die Umsetzung in einem Beispielcode aussehen?
A: Wie man die Umsetzung angeht, hängt davon ab, auf welche Fehlermöglichkeiten man eingehen will, ob man performancekritischen Code schreibt etc. Das Folgende ist also wirklich nur als Beispiel und nicht als Nonplusultra anzusehen.

C:
#include <stdio.h>  // ReadLineCopy,  ReadLineAlloc
#include <stdlib.h> // ReadLineAlloc, StringToLong
#include <string.h> // ReadLineCopy,  StringToLong
#include <ctype.h>  // StringToLong
#include <errno.h>  // StringToLong


/** \brief  Zeile einer Datei oder Benutzereingabe als nullterminierten String lesen
*
*  Der Filestream wird solang gelesen, bis ein Zeilenumbruch eingelesen wurde,
*    das Ende des Streams oder die Puffergröße erreicht ist.
*  Der Zeilenumbruch ist nicht Bestandteil des Strings.
*
*  \param  pFile_in        Filepointer des zu lesenden Dateistreams (stdin für Tastatureingabe)
*                            erforderlich
*  \param  szLine_inout    Charpointer auf einen Puffer in den die eingelesene Zeichenfolge kopiert wird
*                          muss ausreichend Elemente für den einzulesenden String incl. Nullterminierung haben
*                            erforderlich
*  \param  cSize_in        Anzahl Elemente im Puffer
*                            erforderlich
*  \param  pcchLength_out  Pointer auf ein size_t, das die Länge der eingelesenen Zeichenfolge zugewiesen bekommt
*                            darf NULL sein
*
*  \return int             1 bei Fehler, sonst 0
*/
int ReadLineCopy(FILE *const pFile_in, char *const szLine_inout, const size_t cSize_in, size_t *const pcchLength_out)
{
  size_t cLen = 0; // Stringlänge
  int ch = EOF; // ASCII Wert eines Zeichens

  if (pcchLength_out) // wenn ein Pointer für den Längenwert übergeben wurde
    *pcchLength_out = 0; // Wert im Fehlerfall

  if (!szLine_inout || !cSize_in) // falls kein Puffer übergeben wurde oder die Größe des Puffers mit 0 angegeben wurde
    return 1; // Fehler, raus hier

  *szLine_inout = 0; // Wert im Fehlerfall

  if (!pFile_in) // falls kein Filepointer übergeben wurde
    return 1; // Fehler, raus hier

  if (fgets(szLine_inout, cSize_in, pFile_in) == NULL) // Zeile lesen. Im Fehlerfall ...
  {
    clearerr(pFile_in); // EOF und Fehlerindikatoren löschen
    *szLine_inout = 0; // falls szLine_inout bereits geändert wurde, rückgängig machen
    return 1; // Fehler, raus hier
  }

  if (!(cLen = strlen(szLine_inout))) // Länge der Eingabe. Falls die Länge 0 ist ...
    return 1; // Fehler, raus hier

  if (szLine_inout[cLen - 1] != '\n') // falls das letzte Zeichen kein Zeilenumbruch ist, war ungenügend Platz um den gesamten String einzulesen
  {
    if (pcchLength_out) // wenn ein Pointer für den Längenwert übergeben wurde
      *pcchLength_out = cLen; // Länge zuweisen

    if ((ch = fgetc(pFile_in)) == '\n' || ch == EOF) // Wenn das nächste Zeichen ein Zeilenumbruch oder das Streamende ist
    {
      clearerr(pFile_in); // EOF und Fehlerindikatoren löschen
      return 0; // OK und raus
    }

    while (!ferror(pFile_in) && (ch = fgetc(pFile_in)) != '\n' && ch != EOF); // Eingabepuffer leeren
    clearerr(pFile_in); // EOF und Fehlerindikatoren löschen
    return 1; // Fehler, raus hier
  }

  szLine_inout[--cLen] = 0; // Länge dekrementieren, Zeilenumbruch durch Nullterminierung ersetzen
  if (pcchLength_out) // wenn ein Pointer für den Längenwert übergeben wurde
    *pcchLength_out = cLen; // Länge zuweisen

  return 0; // OK und raus
}



/** \brief  Zeile einer Datei oder Benutzereingabe als nullterminierten String lesen
*
*  Der Filestream wird zeichenweise gelesen, solange bis entweder ein Zeilenumbruch
*    eingelesen wurde oder das Ende des Streams erreicht ist.
*  Speicher wird dynamisch alloziert. Der Zeilenumbruch ist nicht Bestandteil des Strings.
*
*  \param  pFile_in        Filepointer des zu lesenden Dateistreams (stdin für Tastatureingabe)
*                            erforderlich
*  \param  pszLine_out     Pointer auf einen Charpointer, der den Speicherbereich der eingelesenen Zeichenfolge zugewiesen bekommt
*                            darf NULL sein
*  \param  pcchLength_out  Pointer auf ein size_t, das die Länge der eingelesenen Zeichenfolge zugewiesen bekommt
*                            darf NULL sein
*
*  \return int             1 bei Fehler, sonst 0
*/
int ReadLineAlloc(FILE *const pFile_in, char **const pszLine_out, size_t *const pcchLength_out)
{
  size_t ic = 0; // Index im Puffer - später Anzahl Zeichen
  int ch = EOF; // hält später den ASCII Wert des eingelesenen Zeichens
  char *szBuffer = NULL, *pchExtend = NULL; // Pointer für den Puffer und für eine Puffererweiterung deklarieren

  if (pcchLength_out) // wenn ein Pointer für den Längenwert übergeben wurde
    *pcchLength_out = 0; // Wert im Fehlerfall

  if (pszLine_out) // wenn ein Pointer für die Zeichenfolge übergeben wurde
  {
    *pszLine_out = NULL; // Wert im Fehlerfall
    if (!(szBuffer = (char*)malloc(1))) // Speicher für den Puffer reservieren; wenn malloc() nicht erfolgreich war
      return 1; // Fehler, raus hier
  }

  if (!pFile_in || ferror(pFile_in) || feof(pFile_in)) // wenn der Pointer auf den Dateistream NULL ist, ein Fehler im Stream aufgetreten ist oder das Ende des Streams erreicht ist
  {
    clearerr(pFile_in); // EOF und Fehlerindikatoren löschen
    free(szBuffer); // Speicher freigeben
    return 1; // Fehler, raus hier
  }

  while ((ch = fgetc(pFile_in)) != '\n' && ch != EOF) // lese das (nächste) Zeichen, iteriere solang das Zeichen kein Zeilenumbruch ist und nicht das Ende des eingehenden Streams erreicht ist
  {
    if (ferror(pFile_in)) // falls Fehler im Stream
    {
      clearerr(pFile_in); // EOF und Fehlerindikatoren löschen
      free(szBuffer); // Speicher freigeben
      return 1; // Fehler, raus hier
    }

    if (pszLine_out) // wenn ein Pointer für die Zeichenfolge übergeben wurde
    {
      szBuffer[ic] = ch; // weise das Zeichen der aktuellen Position (ic=Index) im Puffer zu
      if (!(pchExtend = (char*)realloc(szBuffer, ic + 2))) // falls die Erweiterung der Puffergröße um 1 fehlgeschlagen ist
      {
        free(szBuffer); // Speicher freigeben
        return 1; // Fehler, raus hier
      }
      szBuffer = pchExtend; // neuen Pointer der Variablen szBuffer zuweisen
    }

    ++ic; // erhöhe Index um 1 (++ic=nächster Index=Stringlänge)
  }

  if (pszLine_out) // wenn ein Pointer für die Zeichenfolge übergeben wurde
  {
    szBuffer[ic] = 0; // Nullterminierung setzen
    *pszLine_out = szBuffer; // Pointer zum Puffer geht nach draußen
  }

  if (pcchLength_out) // wenn ein Pointer für den Längenwert übergeben wurde
    *pcchLength_out = ic; // Anzahl eingelesener Zeichen geht nach draußen

  return 0; // 0 wenn OK
} // ACHTUNG: Reservierten Speicher in der aufrufenden Funktion freigeben!


/** \brief  C-String zu Long Integer
*
*  Konvertierung der eingehende Zeichenfolge zum long int mittels strtol() Funktion.
*
*  \param  szNum_in      Zeichefolge, die als long int konvertiert werden soll
*                          erforderlich
*  \param  iBase_in      Basis des Zahlensystems, das für die Konvertierung herangezogen wird
*                          erforderlich
*  \param  pNum_out      Pointer auf ein long int, das den konvertierten Wert zugewiesen bekommt
*                          erforderlich
*  \param  pszEnd_out    Pointer auf einen Charpointer, an dem die Konvertierung abgebrochen wurde
*                          darf NULL sein
*
*  \return unsigned int  Rückgabewert als Bitmaske
*                        0 wenn fehlerfrei, sonst Flags:
*                        2^0 - Whitespaces am Stringanfang,
*                        2^1 - Stringende nicht numerisch,
*                        2^2 - nicht numerisch interpretierbar oder numerischer Stringteil außerhalb Typbreite,
*                        2^3 - Fatal Error
*/
unsigned StringToLong(const char *const szNum_in, const int iBase_in, long *const pNum_out, char **const pszEnd_out)
{
  const char *szTrunc = NULL; // bekommt einen Pointer zum ersten Zeichen in szNum_in das kein Whitespace ist
  char *pchEnd = NULL; // der Variablen pchEnd wird strtol() den Pointer zuweisen, an dem die Verarbeitung abgebrochen wurde
  unsigned rgbitRet = 0; // zurückgegebene Fehlerbits

  if (pNum_out)
    *pNum_out = 0; // Wert im Fehlerfall

  if (pszEnd_out)
    *pszEnd_out = NULL; // Wert im Fehlerfall

  if (!szNum_in || !pNum_out || iBase_in < 0 || iBase_in > 36 || iBase_in == 1)
    return 8; // wenn einer der erforderlichen Pointer NULL ist oder iBase_in nicht zwischen 0 und 36 liegt bzw. 1 ist, Fatal Error und raus hier

  szTrunc = szNum_in; // Pointer zuweisen

  while (isspace(*szTrunc))
    ++szTrunc; // führende Whitespaces "abschneiden", danach zeigt der Pointer auf das erste Zeichen innerhalb szNum_in das kein Whitespace ist

  errno = 0; // der Integerwert errno wird durch strtol() mit dem Wert ERANGE befüllt, wenn der Bereich eines Long Integer überschritten wird
  *pNum_out = strtol(szNum_in, &pchEnd, iBase_in); // die eigentliche C Funktion String zu Long Integer

  if (ERANGE == errno)
    rgbitRet |= 4; // Zahl außerhalb Typbreite?

  if // hier soll betrachtet werden, ob strtol() 0 zurückgibt, weil "0" gelesen wurde, oder weil kein numerischer String zu extrahieren war - Bsp.: "0$" OK, "$" nicht OK
  (
    *pNum_out == 0 // sollte strtol() etwas anderes als 0 zurückgeben, OK und der Rest braucht nicht betrachtet zu werden
    && ! // das ! beachten
    (
      szTrunc[0] == '0' // wenn das erste Zeichen '0' ist, dann OK
      || // oder
      (
        (szTrunc[0] == '+' || szTrunc[0] == '-') // wenn das erste Zeichen '+' oder '-' ist ...
        && szTrunc[1] == '0' // ... und das zweite Zeichen '0', dann auch OK
      )
    )
  )
    rgbitRet |= 4; // Ergo: Folgt nach ggf. abgeschnittenen führenden Whitespaces ein Teilstring, der nicht numerisch interpretiert werden konnte?

  if (*pchEnd)
    rgbitRet |= 2; // Abbruch nicht an Nullterminierung?

  if (isspace(*szNum_in))
    rgbitRet |= 1; // Whitespace am Stringanfang?

  if (pszEnd_out)
    *pszEnd_out = pchEnd; // Abbruchpointer zuweisen

  return rgbitRet;
}



// extra kurz, um Fehler generieren zu können
#define BUFCOUNT 10
// wenn StringToLong() eine Zahl von 1 bis 3 zurückgibt, war strtol() in der Lage einen Long Integer vom Stringanfang zu extrahieren
#define OK  0
#define ERR 4

int main(void)
{
  char rgchStr[BUFCOUNT] = {0}, *szStr = NULL, *szRem = NULL;
  size_t cchLen = 0;

  long lNum = 0;
  unsigned rgbitFlags = -1, i = 0;
  const char *const rgszErrMsg[4] = {"vorangestellte Whitespaces", "Abbruch vor Stringende", "nicht als Long Integer interpretiertbar", "Anwender-/Verarbeitungsfehler"};
  const char *const rgszAckMsg[4] = {"keine vorangestellten Whitespaces", "Abbruch am Stringende", "als Long Integer interpretiertbar", "kein Anwender-/Verarbeitungsfehler"};

/* ReadLineCopy */
  printf("~~~~~~~~~~~~~~~~~~~~~~~~~~\nEingabe: ");
  if (0 == ReadLineCopy(stdin, rgchStr, BUFCOUNT, &cchLen)) // Benutzereingabe einholen und auf Verarbeitungsfehler prüfen.
    printf("\nReadLineCopy - OK:\n Laenge: %u\n String: '%s'\n", cchLen, rgchStr); // Die Eingabe passt in den Puffer.
  else if (*rgchStr)
    fprintf(stderr, "\nReadLineCopy - Fehler:\n Laenge: %u\n String: '%s'\n", cchLen, rgchStr);  // Die Eingabe passt nicht in den Puffer.
  else
    fprintf(stderr, "\nReadLineCopy - Fataler Fehler\n"); // Syntaxfehler, ungültiger Stream o.Ä.

/* ReadLineAlloc */
  printf("\n~~~~~~~~~~~~~~~~~~~~~~~~~~\nEingabe: ");
  if (0 == ReadLineAlloc(stdin, &szStr, &cchLen)) // Benutzereingabe einholen und auf Verarbeitungsfehler prüfen.
  {

    printf("\nReadLineAlloc:\n Laenge: %u\n String: '%s'\n", cchLen, szStr); // Anzeigen, was wir bekommen haben.

/* StringToLong */
    rgbitFlags = StringToLong(szStr, 10, &lNum, &szRem); // Versuch die Benutzereingabe als Long Integer zu interpretieren.

    // Beispiel, wie man auf die einzelnen Flags zugreifen könnte.
    puts("\n~~~~~~~~~~~~~~~~~~~~~~~~~~\nStringToLong:\n Fehlerflags");
    for (; i < 4; ++i)
    {
      if ((rgbitFlags >> i) & 1)
        fprintf(stderr, "  1 - %s\n", rgszErrMsg[i]);
      else
        printf("  0 - OK (%s)\n", rgszAckMsg[i]);
    }

    if (rgbitFlags & 2) // Ausgabe des Strings, an dem die Konvertierung abgebrochen wurde.
      printf(" Restlicher String:\n  '%s'\n", szRem); // Hier muss szStr noch gültig sein, da szRem auf einen Teilbereich des dafür allozierten Speichers zeigt.

    free(szStr); // Speicher (reserviert in ReadLineAlloc()) freigeben, da szStr nicht länger benötigt wird!

    // Beispiel, wie man den Rückgabewert weiterhin interpretieren kann.
    if (rgbitFlags == OK)
    {
      printf(" Gesamter String konnte als Long Integer interpretiert werden:\n  %ld\n", lNum);
      return EXIT_SUCCESS;
    }
    else if (rgbitFlags < ERR)
      {
        printf(" Long Integer wurde aus Teilstring extrahiert:\n  %ld\n", lNum);
        return EXIT_SUCCESS;
      }
      else
      {
        fprintf(stderr, " String/-anfang konnte nicht als Long Integer interpretiert werden.\n");
        return EXIT_FAILURE;
      }
  }

  fprintf(stderr, "ReadLineAlloc: Verarbeitungsfehler.\n");
  return EXIT_FAILURE;
}

Addendum:
Auch wenn ich oben etwas von Zeichenketten, Zeichenfolgen oder Strings geschrieben habe - einen Stringtyp kennt C leider nicht.
  • Like
Reaktionen: Mat und lord_haffi
Autor
german
Aufrufe
405
Erstellt am
Letzte Bearbeitung
Bewertung
5,00 Stern(e) 1 Bewertung(en)

Weitere Ressourcen von german

Oben Unten