Hier habe ich aus "The C book" alles zusammengefaßt, was man nicht intuitiv erwartet:
- Funktionsaufrufe sind immer call-by-value, das Zeug landet dabei auf dem Stack
- Alle Deklarationen in einer Funktion müssen zusammen erfolgen; nach der ersten Anweisung, die keine Deklaration ist, darf keine Deklaration mehr kommen
- Der Scope einer Deklaration beinhaltet den zugehörigen {}-Block und alle {}-Blöcke darin
- Funktionen in Funktionen sind verboten, daher ist ihr Scope auch immer global (Ausnahme static)
- static begrenzt den Scope von globalen Variablen und Funktionen auf die aktuelle c-Datei
- Eine normale Variable, die global deklariert wird, wird damit auch definiert, da sie dabei immer (zur Not mit 0) initialisiert wird. Will man dies verhindern, weil man weiß, daß sie schon woanders definiert/initialisiert wird, setzt man extern davor
- In Funktionen funktioniert static wie erwartet (Wert zwischen Aufrufen behalten)
- Auf Literale kann nicht gezeigt werden: pointer_auf_int = &3; geht nicht
- Der Compiler darf die Wirkung von ++ und — sammeln, bis der ganze Ausdruck ausgewertet ist
- Bei gleicher Operatorrangfolge werden Ausdrücke von rechts nach links(!) ausgewertet
- Pointer sind (fast) immer Pointer auf einen bestimmten Typ
- ++pointer_auf_array_element_vom_typ_x zeigt auf das nächste Element, und zwar unabhängig von der Größe von Typ x, andere Operatoren (Addition, Subtraktion, Vergleich) funktionieren analog
- Dies funktioniert logischerweise nicht mehr, wenn der Pointer auf einen generischen Pointer (void *) gecastet wurde, dann muß die Größe berücksichtigt werden
- Meist wird ein Array zur Benutzung implizit auf einen Pointer gecastet, der auf das erste Element zeigt: pointer_auf_int = array_von_ints;
- Dazu darf nicht der &-Operator verwendet werden, denn das bedeutet etwas anderes (nämlich "Pointer auf das ganze Array", s.u.): pointer_auf_int = &array_von_ints; geht nicht, es muß heißen pointer_auf_array_von_ints = &array_von_ints;
- Der implizite Cast passiert auch, wenn man versucht, ein Array als Funktionsargument zu übergeben
- Und er passiert selbst dann, wenn man bei der Funktionsdeklaration angibt, daß ein Array erwartet wird (funktion(int array_von_ints[10]) ist gleichbedeutend mit funktion(int* array_von_ints)), Arrays liegen offenbar nicht gerne auf Stacks
- Pointer auf ganze Arrays (mit & erzeugt, s.o.) braucht man i.d.R. nur in Verbindung mit mehrdimensionalen Arrays
- Pointer auf Typ X können auf Pointer auf const Typ X gecastet werden, dies bringt i.d.R. zum Ausdruck, daß man in einer Funktion nicht beabsichtigt, den Wert zu ändern
- Andersrum gehts übrigens nicht
- Ein String-Literal ist quasi eine Konstante vom Typ char[Länge+1] die die Buchstaben und ein abschließendes \0 enthält
- Nullterminierte Strings sind eine Konvention, C an sich zwingt niemanden, sie zu benutzen (wohl aber die Library-Funktionen)
- Wenn int hallo(int a) eine Funktion deklariert, deklariert int (*pointer_auf_hallo)(int a) einen Pointer auf eine solche Funktion
- Das Ziel des Pointers wird dann einfach mit pointer_auf_hallo = hallo; zugewiesen (ähnlich wie beim Array)
- Aufgerufen werden kann die Funktion logischerweise mit (*pointer_auf_hallo)(1), aber auch mit pointer_auf_hallo(1)
- Man schreibt typedef struct strukturname { … } strukturname damit man später mit strukturname struktur deklarieren kann, ansonsten müßte man mit struct strukturname struktur deklarieren (Strukturnamen haben von Natur aus ihren eigenen Namespace)
- Strukturmember werden mit struktur.strukturmembername angesprochen
- Man kann Strukturen übergeben (by value), das kopiert aber die ganze Struktur auf den Stack
- (*pointer_auf_struktur).member <=> pointer_auf_struktur->member
- pointer_auf_const_strukturname->member sollte konstant (unmanipulierbar) sein, manchen Compilern ist das aber egal
- Für Hin- und Rückpointer zwischen zwei Strukturtypen muß man eine Struktur unvollständig deklarieren: struct strukturname1; struct strukturname2 { struct strukturname1* pointer_auf_strukturname1; … }; struct strukturname1 { … };
- Das stimmt gar nicht, die wird nämlich durch ihre Erwähnung bereits unvollständig deklariert.
- struct-Typen haben einen Scope
- union ist wie struct, nur daß man immer nur einen Member benutzen kann, weil alle sich denselben Speicherplatz teilen
- typedefs können nicht unvollständig deklariert werden, wenn man eine unvollständig deklarierte struct nutzen will, muß man das mit ihrem echten Namen ("struct strukturname") tun
- Man kann mit typedef Funktionstypen (z.B. "int (int, int)") neue Namen geben, das macht aber nur Sinn, wenn man mit Pointern auf Funktionen dieses Typs zugreifen will: typedef int mein_funktionstyp(int, int);
Nun gibt es keine Syntax um eine Funktion vom Typ mein_funktionstyp zu definieren.
Wohl aber kann eine Variable vom Typ "Pointer auf Funktion vom Typ mein_funktionstyp" deklariert werden (mein_funktionstyp* pafvtmf;)
Diese kann nun mit einem Pointer auf eine solche Funktion gefüllt werden:
int eine_funktion(int, int); //Eine solche Funktion deklarieren
pafvtmf = eine_funktion; //Funktionsnamen werden implizit auf Poiter gecastet - Übersichtlicher ist es aber ohnehin, direkt den Pointer zu definieren:
typedef int *pointer_auf_solche_funktion(int,int);