C-Programmierung: FAQ

Hinweis: Weitere Fragen bitte einfach in eine Kategorie oder ganz unten einfügen. Es müssen dabei keine Antworten mit angegeben werden.

Fragen zu diesem BuchBearbeiten

Ich habe in einem anderen Buch gelesen, dass ...Bearbeiten

In diesem Fall solltest du hingehen und den Abschnitt verbessern. Allerdings gibt es eine ganze Reihe sehr populärer Irrtümer über C und du solltest deshalb vorher anhand des Standards überprüfen, ob die Aussage tatsächlich zutrifft. Hier nur ein unvollständige Liste der populärsten Irrtümer:

  • Ein Programm beginnt mit void main(void) , main() usw. – Dies entspricht nicht dem (C99)-Standard. Dort ist festgelegt, dass jedes Programm mit int main() oder int main(void) oder int main(int argc, char *argv[]) beginnen muss, auch wenn viele Compiler void dennoch akzeptieren.Die Definition mit main() war früher (C89/ANSI C) gültig, da beim Fehlen eines Rückgabetyps angenommen wurde, dass die Funktion int zurückliefert.
  • Der Variablentyp xyz hat die Größe von xyz Byte. – Auch dies ist falsch. Der Standard legt für Basis-Datentypen (char,short,int,long,float,double...) lediglich Mindest-Wertebereiche fest, nur für char wird 1 Byte Typgröße vorgeschrieben. Mit dem C99-Standard wurden Typen wie beispielsweise int8_t oder int16_t eingeführt, die eine feste (Bit)Größe besitzen.
  • Eine Variable des Typs char ist 8 Bit breit. – Auch dies ist genaugenommen nicht richtig. Einige Autoren behaupten dann noch, dass ein Byte in C eine beliebige Zahl von Bits haben kann. Richtig dagegen ist, dass in C ein Byte mindestens aus 8 Bit bestehen muss. Tatsächlich kann man dies aber häufig vernachlässigen; K&R erwähnen dies in ihrem Buch auch nicht gesondert. Wer dennoch hochportable Programme schreiben möchte und die Anzahl der Bits benötigt, kann dies wie folgt ermitteln:
#include <limits.h> // für CHAR_BIT
size_t size = sizeof(datentyp) * CHAR_BIT;
  • Ein Array zerfällt immer in einen Zeiger auf das erste Element – Der Standard beschreibt Ausnahmen, ebenso wie ein Array nie als LValue (linker Wert einer Zuweisung) verwendet werden kann.

Variablen und KonstantenBearbeiten

Es heißt, dass der Ausdruck sizeof(char) immer den Wert 1 liefert, also der Typ char immer die Größe von 1 Byte hat. Dies ist aber unlogisch, da UNICODE Zeichen 16 Bit und damit 2 Byte besitzen. Hier widerspricht sich der Standard doch, oder?Bearbeiten

Nein, tut er nicht. Der Denkfehler liegt darin anzunehmen, dass ein UNICODE Zeichen in einem char abgelegt werden muss. In der Regel wird es aber in einem wchar_t abgelegt. Dies ist laut C - Standard ein ganzzahliger Typ, dessen Wertebereich ausreicht, die Zeichen des größten erweiterten Zeichensatzes der Plattform aufzunehmen. Per Definition liefert sizeof(char) immer den Wert 1.

Welche Größe hat der Typ int auf einem 64 Bit Prozessor ?Bearbeiten

Der Standard legt die Größe des Typs int nicht fest. Er sagt nur: A "plain" int object has the natural size suggested by the architecture of the execution environment (Abschnitt 6.2.5 Types Absatz 4). Man könnte daraus schließen, dass int auf einer 64 Bit Plattform 64 Bit groß ist, was aber nicht immer der Fall ist.

Es ist mir immer noch nicht ganz klar, was dieses EOF Zeichen bedeutet.Bearbeiten

EOF ist ein negativer Integerwert, der von einigen Funktionen geliefert wird, wenn das Ende eines Stroms erreicht worden ist. Bei Funktionen, mit denen auf Dateien zugegriffen werden kann, ist EOF das End of File – also das Dateiende.

Der Denkfehler einiger Anfänger liegt vermutlich darin, dass EOF Zeichen grundsätzlich mit dem Dateiende gleichzusetzen. EOF kennzeichnet aber ganz allgemein das Ende eines Stroms, zu dem auch der Eingabestrom aus der Standardeingabe (Tastatur) gehört. Deshalb kann auch getchar EOF liefern.

OperatorenBearbeiten

Mir ist immer noch nicht ganz klar, warum a = i + i++ ein undefiniertes Resultat liefert. Der ++ - Operator hat doch eine höhere Priorität als der + -Operator.Bearbeiten

Ja, es ist richtig, dass der ++ Operator eine höhere Priorität als der + -Operator hat. Der Compiler errechnet deshalb zunächst das Ergebnis von i++ . Allerdings müssen die Nebenwirkungen erst bis zum Ende des Ausdrucks ausgewertet worden sein.

Anders ausgedrückt: Der C-Standard schreibt dem Compiler nicht vor, wann er den Wert von i ändern muss. Dies kann sofort nach der Berechnung von i++ sein oder erst am Ende des Ausdrucks beim Erreichen des Sequenzpunktes.

Dagegen hat

b = c = 3;
a = b + c++;

ein klar definiertes Ergebnis (nämlich 6), da die Variable c nicht an einer anderen Stelle vor dem Sequenzpunkt verändert wird.

Es ist etwas anders: Das Inkrement kennt zwei unterschiedliche Anwendungen, nämlich before (++i) und after (i++) bei der Durchführung, daher hat

b = c = 3;
a = b + ++c;

auch ein klar definiertes Ergebnis (nämlich 7)!

ZeigerBearbeiten

Ist bei malloc ein Cast unbedingt notwendig? Ich habe schon öfter die Variante zeiger = (int*) malloc(sizeof(int) * 10); genauso wie zeiger = malloc(sizeof(int) * 10); gesehen.Bearbeiten

Für diese Antwort muss man etwas ausholen: In der ersten Beschreibung der Sprache von K&R gab es noch keinen Zeiger auf void . Deshalb gab malloc einen char* zurück und ein cast auf andere Typen war notwendig. Es konnte deshalb nur die erste Variante zeiger = (int*) malloc(sizeof(int) * 10); eingesetzt werden.

Mit der Standardisierung von C wurde der untypisierte Zeiger void* eingeführt, der automatisch bei Zuweisung auf jeden anderen Datenzeigertyp konvertiert wird, d.h. er ist immer zu jedem anderen Datenzeigertyp kompatibel.
Daher ist kein expliziter Cast mehr notwendig und es kann die zweite Variante zeiger = malloc(sizeof(int) * 10); benutzt werden.
K&R benutzen in ihrem Buch allerdings auch in der 2. Auflage die erste der beiden Varianten und behaupten, dass dieser Typ explizit in den gewünschten Typ umgewandelt werden muss; sie korrigieren dies aber im Errata zum Buch:

The remark about casting the return value of malloc ("the proper method is to declare ... then explicitly coerce")
needs to be rewritten. The example is correct and works, but the advice is debatable in the context of the
1988-1989 ANSI/ISO standards. It's not necessary (given that coercion of void * to ALMOSTANYTYPE * is automatic),
and possibly harmful if malloc, or a proxy for it, fails to be declared as returning void *. The explicit cast can cover up
an unintended error. On the other hand, pre-ANSI, the cast was necessary, and it is in C++ also.

Dies ist aber einer der wenigen Fehler des Buches und wird vom ANSI C Standard nicht gefordert. Leider wird diese falsche Behauptung oft von vielen Büchern übernommen.

Es gibt allerdings dennoch einen Grund void* zu casten, und zwar dann wenn ein C++-Compiler zum Übersetzen benutzt werden soll. Da wir uns in diesem Buch allerdings an ANSI C halten, benutzen wir keinen Cast.