|
| a | b | c | d | e | f | g | h | i | j |\0 | x | y | z |\0 | | |
^s1 ^s2
printf("%s\n",s1); --> "abcdefghij"
printf("%s\n",s2); --> ""
Natürlich hängt es sehr von der Speicherorganisation ab, wo die Variablen
im einzelnen abgelegt sind (oft sind sie auf eine Wortgrenze ausgerichtet, besitzen
also etwas Spielraum!).
Gibt man einen solchen String an eine Funktion weiter, so ist innerhalb der
Funktion nur die Startadresse bekannt (Feldname ist Anfangsadresse). Man kann
dann nur mit einem weiteren Argument überprüfen, ob der Speicherbereich
überschritten wird:
int getline(char buf[], int len); /* Deklaration (Prototyp) von getline */
main ()
{
int ll;
char string[80];
ll = getline(string, sizeof string); /* Aufruf von getline */
return 0;
}
int getline(char s[], int lim);
{
int i = 0;
for(i=0; i<lim-1; i++)
s[i] = ....;
s[i] = '\0';
return i;
}
Folgende Regeln lassen sich daraus ableiten:
- Felder immer groß genug wählen
- Feldgrenzen immer abfragen
- Bei Strings auf die abschließende'\0'achten
Werden diese Hinweise beachtet, können viele Speicherabstürze
vermieden werden.
Falsch gesetzte Semikolons
Falsche Einrückung
Bei der if-Kontrollstruktur ist oft nicht klar, zu welchem if ein else gehört:
if (i > j)
if (k < 100)
tuwas();
else
tuwasanderes();
Es hat hier den Anschein, als ob das else zum ersten if gehört - das ist falsch.
Ein else gehört immer zum nächst höheren if, das noch kein else hat -
also zum zweiten if! Korrektur durch eine Block-Klammer:
if (i > j)
{
if (k < 100)
tuwas();
}
else
tuwasanderes();
Die switch-Falle
Eine weitere Kontrollstruktur kann Kummer machen: switch. Wird hier ein break vergessen,
so arbeitet C sequentiell weiter, auch wenn andere Marken anstehen. Jede Auswahl sollte
also normalerweise mit break abgeschlossen werden.
switch (c)
{
case '1': tuwas(); break;
case '2': tudies(); break;
case '3': tujenes(); break;
default: printf("error\n"); break;
}
Vielleicht kann hier der Präprozessor helfen:
#define CASE break;case
#define DEFAULT break;default
Dann kann die Auswahl so aussehen:
switch (c)
{
CASE '1': tuwas();
CASE '2': tudies();
CASE '3': tujenes();
DEFAULT: printf("error\n");
}
Float-Ausdrücke
Eine weitere Falle ist folgende Rechnung:
double d = 3/4;
Hier wird zunächst 3 durch 4 geteilt - was für Integerwerte natürlich
0 ist. Dann wird dieses Ergebnis d zugewiesen als 0.000000 (über eine implizite
Typumwandlung) und bleibt somit natürlich 0. Abhilfe schafft die Verwendung
der richtigen Konstanten 3.0 und 4.0 als double (zumindest eine):
double d - 3.0/4.0;
Vergleichsoperator vs. Zuweisungsoperator
In Vergleichen vergißt man leicht, daß der Vergleichsoperator = = und der
Zuweisungsoperator = unterschiedliche Funktionen haben. Eine (scheinbare) Bedingung
while (i = 20) weist der Variablen i den Wert 20 zu, was logisch gesehen
wahr (ungleich 0) ist, also wieder eine Endlosschleife ergibt. Natürlich
kann dies (z. B. bei Strings) auch positev genutzt werden:
char s1(100), s2[100];
/* s1 nach s2 kopieren (bis Stringende '\0') */
while (s2[i] = s1[i])
i++;
Reihenfolge der Auswertung
- i++ * i++ undefiniert
- Reihenfolge nur definiert bei &&, ||,
Komma und ?:, dort sogar bedingte Auswertung
- Alle Argumente einer Funktion werden in unbestimmerter Reihenfolge vor
dem Aufruf ausgewertet
Eingabe-Probleme
Desweiteren gibt es oft bei der Bibliotheksfunktion scanf Schwierigkeiten.
Vergißt man doch allzuleicht das Adreß-Zeichen & bei den Argumenten.
Weniger durchschaubar ist aber, daß scanf das abschließende \n im
Tastaturpuffer läßt, so daß ein nachfolgender getchar()-Aufruf
dies als erstes geliefert bekommt. Möchte man scanf und getchar
mischen, empfiehlt es sich, den Tastaturpuffer zu löschen:
#include
int i, c, ret;
ret = scanf("%d",&i); /* gepufferte Eingabe */
if(ret != 1) error(); /* Fehlerbehandlung */
while (getchar() != '\n'; /* Eingabepuffer leeren */
getchar(); /* Einzelzeichen lesen */
Das Überprüfen des Return-Codes ist außerdem fast ein Muß,
sofern man nicht sowieso die ganze Zeile einliest und untersucht:
fgets(buf, MAX, stdin);
sscanf(buf, .... );
Dann kann auch auf versehentliche Leerzeilen reagiert werden.
Preprozessor
- Ein mit #if ausgeklammertem Text muß aus gültigen
Preprozessortokens bestehen
- Makros können ebenfalls Probleme bereiten: #define sqr(x) x = x*x
Hier wird ein Square-Makro definiert, der das Quadrat einer Zahl ermitteln soll.
Dies funktioniert auch für Variablen ganz gut: int i = 10; ... sqr(i); ...
Nun steht in i das Quadrat, also 100. Benutzt man dieses Makro aber für einen Wert
(sqr(10);), so macht die Zuweisung Probleme. Also sollte man die Zuweisung im
Makro unterlassen: #define sqr(x) x*x
- i = sqr(10); funktioniert nun. Ruft man dieses Makro nun aber mit einem
Ausdruck auf, gibt es wieder Probleme: i = sqr(2 + 8); wird zu 2 + 8 * 2 + 8
ersetzt und da Punktrechnung vor Strichrechnung geht, ist das Ergebnis 2 + 16 + 8 = 26,
also falsch. Die zweite Regel sollte also lauten, Klammern zu verwenden:
#define sqr(x) ((x)*(x))
Doch damit ist beispielsweise sqr(++i); noch nicht gelöst, da nun ++i
zweimal ausgeführt wird, und zwar ++i * ++i. Dieses Problem ist mit Makros nicht
zu lösen. Also keine Zuweisungen oder andere Seiteneffekte in Makroaufrufen!
Konstante und Variable
- Bei const char *a und char *b ist a=b
ok (Erlaubnis zum Ändern wird nicht übernommen), b=a ist
dagegen falsch (Erlaubnis zum Ändern wird fälschlicherweise gegeben)
- Bei const int a und int b ist a=b
falsch (Unerlaubte Änderung), b=a ist erlaubt (Verarbeitung nur
einer Kopie)
(Null-)Pointer
- NULL ist nur #define NULL 0 - Automatische Typumwandlung
- Pointer sind keine Integer-Werte.
- Ein Zeiger muß immer mit einer gültigen Speicherplatzadresse
initialisiert sein.
Arrays und Pointer
- In einer Datei char a[5], in der anderen extern char
*a ist falsch, extern char a[] ist richtig
- Äquivalenz der Deklarationen char a[] und char
*a nur bei formalen Argumenten von Funktionen
- char b[][] und char **b nicht äquivalent,
letzteres verwendet real im Speicher vorhandene Pointer
- Aufzeichnen: Realer Speicher bei char **a, char
*a[], char (*a)[], char a[][]
- Dynamische Allozierung mehrdimensionaler Felder: Am besten Allozieren
eines Feldes mit Pointern und Allozieren jeder einzelnen Zeile
Dynamische Speicherverwaltung
- char *s; gets(s); ist falsch, da kein Speicher bereitsteht
- char *s="Hal",*t="lo!",*u=strcat(*s,*t) ist genauso falsch
- Mit free() freigegebener Speicher darf nicht mehr angesprochen werden
|
|
|