Christian Seiler: Einige C-Fragen

Beitrag lesen

Hallo,

Da hätte ich jetzt noch glatt etwas vergessen.
Angenommen ich habe ein String mit dem Format: NAME,ORT,ALTER

Warum funktioniert dann folgendes nicht:

char *string = "NAME,ORT,ALTER";
char *name; // soll enthalten NAME
char *ort; // soll enthalten ORT
char *alter; // soll enthalten ALTER

sscanf(string, "%s,%s,%s", name, ort, alter);

Das ist GANZ BÖSE. EXTREM BÖSE. Aus zwei Gründen:

  1. Von scanf sollte man die Finger lassen. Die Funktion ist in meinen Augen die schlimmste Erfindung im C-Standard schlechthin. Zum einen ist sie ein potentielles Sicherheitsrisiko (siehe unten) und zum anderen gaukelt sie einem vor, das Umgekehrte von printf() zu sein, was sie aber nicht wirklich ist (und was auch nicht wirklich geht). %s hört bei scanf() nämlich entweder auf, wenn die maximallänge Erreicht ist (%20s) oder wenn ein Whitespace-Zeichen kommt (Leerzeichen, Neue Zeile, Tab).

  2. name, ort und alter sind bei Dir als Variablen nicht initialisiert. Sie zeigen also auf einen beliebigen Speicherbereich. Der wird dann mit dem scanf()-Ergebnis noch vollgeschrieben. Damit überschreibst Du beliebigen Speicher Deines Programms (u.U. halt auch den Stack und damit Rücksprungadressen - Hallo liebe Sicherheitslücke) oder das ganze segfaultet eben. Zudem kann man mit scanf() extrem leicht vergessen, eine Begrenzung für die Maximallänge des Puffers mit anzugeben (Du tust es hier ja auch nicht). Keine gute Idee.

Wenn Du den obigen String richtig parsen willst, dann mache das lieber manuell, dann weißt Du nämlich auch, was Du tust.

Hier gibt es zwei Varianten:

  1. Du allozierst immer automatisch auf dem Heap Puffer mit malloc(), die groß genug sind, den Teilstring zu halten. Du solltest diese Puffer nach Verwendung halt wieder per free() loswerden.

  2. Du legst auf dem Stack Puffer an, die eine feste Größe (zur Compilezeit) haben und kopierst halt maximal so viel hinein, wie der Puffer groß ist.

Zu Variante 1:

Mittels strstr() kannst Du die Position eines Substrings in einem String bestimmen. Damit kannst Du sehen, wo die "," vorkommen.

Das folgende Beispiel verdeutlicht beide Varianten. Es ist nicht extrem schön und man kann das ganze mit ordentlich Pointerarithmetik noch viel eleganter machen, ich hoffe jedoch, dass es die Herangehensweise an so ein Problem in C verdeutlicht und verständlich macht, wo die Fallstricke liegen.

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
  
// So gewählt, dass NAME und ORT reinpassen, ALTER aber nicht mehr  
#define PUFFER_GROESSE 5  
  
const char *daten = "NAME,ORT,ALTER";  
  
void ausgabe (char *name, char *ort, char *alter) {  
 printf ("Name: '%s', Ort: '%s', Alter: '%s'\n", name, ort, alter);  
}  
  
void fehler (void) {  
 printf ("Fehler!\n");  
}  
  
void heap(void) {  
 char *name, *ort, *alter;  
 const char *pos1, *pos2;  
 pos1 = daten;  
 pos2 = strstr (pos1, ",");  
 // , nicht gefunden  
 if (!pos2) {  
  fehler ();  
  return;  
 }  
 // richtige größe allozieren; pos2 - pos1 ist die länge des Strings vor dem  
 // Komma, +1 wegen 0-Byte am Ende  
 // calloc verwenden, damit der Speicher mit 0-Bytes initialisiert ist  
 // (alternativ: malloc() und letztes Byte auf 0 setzen)  
 name = calloc(1, (size_t)(pos2 - pos1 + 1));  
 // Daten kopieren  
 strncpy (name, pos1, (size_t)(pos2 - pos1));  
 // Nächste position bestimmen  
 pos1 = pos2 + 1;  
 pos2 = strstr (pos1, ",");  
 if (!pos2) {  
  fehler ();  
  return;  
 }  
 ort = calloc(1, (size_t)(pos2 - pos1 + 1));  
 strncpy (ort, pos1, (size_t)(pos2 - pos1));  
  
 pos1 = pos2 + 1;  
 // Wir nehmen an, dass es kein weiteres Komma gibt, daher suchen wir nicht  
 // danach, sondern kopieren einfach den Rest des Strings  
 alter = calloc(1, strlen(pos1) + 1);  
 // der Puffer ist groß genug, also können wir durchaus strcpy() verwenden  
 // das ANSONSTEN aber böse ist  
 strcpy (alter, pos1);  
  
 // Fertig  
 ausgabe (name, ort, alter);  
  
 // Puffer wieder loswerden  
 free (name);  
 free (ort);  
 free (alter);  
}  
  
void puffer(void) {  
 char name[PUFFER_GROESSE], ort[PUFFER_GROESSE], alter[PUFFER_GROESSE];  
 const char *pos1, *pos2;  
 size_t laenge;  
  
 // Alle Puffer mit 0-Bytes initialisieren  
 memset (name, 0, sizeof(name));  
 memset (ort, 0, sizeof(ort));  
 memset (alter, 0, sizeof(alter));  
  
 pos1 = daten;  
 pos2 = strstr (pos1, ",");  
 // , nicht gefunden  
 if (!pos2) {  
  fehler ();  
  return;  
 }  
 // Daten kopieren (maximal aber Puffergröße)  
 laenge = (size_t)(pos2 - pos1);  
 if (laenge > PUFFER_GROESSE - 1) {  
  laenge = PUFFER_GROESSE - 1;  
 }  
 strncpy (name, pos1, laenge);  
 // Nächste position bestimmen  
 pos1 = pos2 + 1;  
 pos2 = strstr (pos1, ",");  
 if (!pos2) {  
  fehler ();  
  return;  
 }  
 laenge = (size_t)(pos2 - pos1);  
 if (laenge > PUFFER_GROESSE - 1) {  
  laenge = PUFFER_GROESSE - 1;  
 }  
 strncpy (ort, pos1, laenge);  
  
 pos1 = pos2 + 1;  
 // Hier müssen wir wieder strncpy verwenden weil wir den Puffer nicht  
 // dynamisch angelegt haben  
 laenge = strlen (pos1);  
 if (laenge > PUFFER_GROESSE - 1) {  
  laenge = PUFFER_GROESSE - 1;  
 }  
 strncpy (alter, pos1, laenge);  
  
 // Fertig  
 ausgabe (name, ort, alter);  
  
 // Puffer existieren auf dem Stack, müssen nicht aufgeräumt werden  
}  
  
int main (int argc, char **argv) {  
 heap ();  
 puffer ();  
 return 0;  
}

Allgemein kann man sagen: Wenn man mit reinem C Strings manipulieren will, sucht man sich am besten eine Bibliothek, die einem die ganze "Drecksarbeit" abnimmt. Die C-Standardbibliothek ist in der Hinsicht eher schlecht als recht und sehr viele Stringfunktionen sind in irgend einer Hinsicht böse, auch wenn es nicht den Anschein hat (Threadsicherheit und Reentrancy sind auch gerne nochmal ein Problem, ich denke da an strtok() o.ä.). Hinzu kommt noch, dass man mit 8-Bit-Strings nicht mehr alles abdecken kann und dass die ganze C-Standardbibliothek nicht die leiseste Ahnung von Unicode etc. hat.

Insofern: Wenn Du viel mit Strings machen willst, dann Suche Dir entweder eine brauchbare Bibliothek, die Dir die Drecksarbeit abnimmt oder nimm eine andere Programmiersprache als C. Bei allem anderen schießt Du Dir nur selbst in die Knie (siehst ja an meinem Beispiel, dass es nicht ganz einfach ist, einen derartigen String richtig zu parsen).

Viele Grüße,
Christian