Syntax von Variablen, Initialisierung

Eine Variable wird im Programmcode geschrieben, indem zuerst ein Typ und danach das Symbol der Variablen geschrieben wird. Nach der Deklaration kann die Variable an anderer Stelle mittels Angabe des Symbols angesprochen werden. Eine Variable kann bei ihrer Definition initialisiert werden.

int    a;
double b;
char   c        = 'M';
int*   ptr      = &a;
int    array[3] = {5, 6, 7};

Details

Hier auf dieser Seite wird explizit die Syntax und Semantik von Variablen beschrieben. Für eine allgemeine Übersicht über das Thema Deklaration und Definition wird auf die entsprechende Seite bei den Konzepten verwiesen.

Eine Variable ist ein veränderlicher Wert, welcher im Code per Name angesprochen werden kann. In den Sprachen C und C++ wird strikt jeder Variablen ein Typ zugeordnet. Dieser Typ wird bei einer Variablendeklaration zuerst geschrieben. Danach folgt das Symbol (der Name) der Variablen.

Wird eine Variable definiert, kann ihr stets auch ein Startwert gegeben werden. Hierbei wird nach dem Symbol der Initialwert nach einem Gleichheitszeichen = hingeschrieben. Mehr Erklärungen dazu finden sich weiter unten.

Es können mehrere Variablen gleichzeitig mit demselben Typ deklariert werden, indem die Symbole in einer durch Komma , getrennten Liste aufgeführt werden. Es ist zu beachten, dass es sich hierbei nicht um den Sequenz-Operator handelt, sondern um eine reine Auflistung von Symbolen. Jedes Symbol kann dabei auch einzeln initialisiert werden. Vorsicht ist geboten bei Pointer-Typen, siehe dazu auch die Erklärungen weiter unten.

int   a, b, c;
float x = 4.f, y = 5.f, z = 6.f;

Eine Variablendeklaration, -definition und -initialisierung wird stets mit einem Semikolon ; abgeschlossen werden. Zwar sehen diese Ausdrücke dadurch aus wie normale Anweisungen, sind es jedoch semantisch betrachtet nicht. Eine Anweisung besteht aus einer Verkettung von Operatoren und generiert dementsprechend ausführbaren Code. Deklarationen und Definitionen tun dies jedoch nicht. Eine Variablen-Deklaration dient nur als Hinweis für den Compiler. Variablen-Definitionen stellen höchstens sicher, dass irgendwo genügend Speicherplatz reserviert ist, was jedoch in jedem Falle bereits zur Compilier-Zeit ermittelt werden kann. Ausführbarer Code wird nur dann generiert, wenn zusätzlich zur Definition noch eine Initialisierung stattfindet.

Initialisierung von Variablen

Bei der Definition einer Variablen ist es möglich oder gar erforderlich, sie auch zu initialisieren, sprich, der Variablen einen Start-Wert anzugeben:

int x = 1;
double blue[3] = {0., 0., 1.};
Particle p = oldparticle;
static int numerrors = 0;

Initialisierungen sind bei jeder Variablen-Definition erlaubt. Es sei jedoch darauf hingewiesen, dass dies nur für Definitionen gilt, auf keinen Fall für Deklarationen (siehe Deklaration und Definition). Es ist somit nicht möglich, beispielsweise eine mit dem Keyword extern deklarierte Variable oder eine Variable innerhalb einer Klassendeklaration zu initialisieren. Die entsprechenden Initialisierungen müssen an anderer Stelle (vorzugsweise in einer separaten Implementations-Datei) vorgenommen werden.

Eine Initialisierung sieht mit dem Gleichheitszeichen = und dem abschliessenden Semikolon ; zwar aus wie eine normale Anweisung, ist jedoch semantisch nicht als solche zu betrachten. Vielmehr handelt es sich um eine Angabe für den Compiler, mit welchem Wert eine Variable geladen sein soll BEVOR sie gültig wird. Ein Compiler wird je nach Situation unterschiedlichen Code generieren:

Wurde die Variable mit dem static-Keyword deklariert, so darf die Initialisierung genau nur ein einziges Mal stattfinden. Der Compiler löst dies beispielsweise, indem er alle statischen Variablen im globalen Bereich definiert und noch vor dem Aufruf der main-Funktion die Variablen initialisiert. Eingebaute Typen werden hierbei direkt in das Binary codiert womit überhaupt kein Code für die Initialisierung ausgeführt werden muss.

PODs können mittels Aggregat-Initialisierung mit Werten gefüllt werden. Dadurch können mehrere Werte gleichzeitig initialisiert werden, was durch den Compiler erheblich beschleunigt werden kann. Aggregate sind jedoch explizit nur bei Initialisierungen erlaubt.

Handelt es sich bei dem Typ der Variablen um eine komplexe Klasse mit einem aufwändigen Konstruktor oder gar um ein grosses Array aus solchen Objekten, so kann eine Initialisierung sehr viel Zeit kosten. Es sei angemerkt, dass bei Objekten von Klassen mit virtuellen Methoden der Compiler zudem implizit in jeden Konstruktor (auch einen leeren Standardkonstruktor) die Initialisierung einer versteckten Variablen (die sogenannte vtable) einfügt.

Wenn irgend eine kostspielige Initialisierung innerhalb einer Funktion steht, welche sehr oft aufgerufen wird, so wirkt sich dies dramatisch auf die Laufzeit des Programmes aus. In solchen Fällen könnte die Verwendung des static-Keywords Abhilfe schaffen, es sei jedoch nochmals darauf hingewiesen, dass beim static-Keyword die Initialisierung nur ein einziges Mal stattfindet.

Die seltsame Syntax von Pointer-Deklarationen

Die Sprachen C und C++ definieren die Syntax von Deklarationen dergestalt, dass Pointer, Arrays oder Referenzen zum Symbol zugehörig betrachtet werden und nicht zum Typ. Im folgenden Beispiel lässt die erste Zeile fälschlicherweise vermuten, dass das Pointer-Zeichen syntaktisch zum Typ dazugehört. In der zweiten Zeile ist die syntaktische Zugehörigkeit von der Schreibweise her korrekter.

int* a;
int *b;

Wenn nur eine einzelne Variable deklariert wird, spielt die Schreibweise jedoch keine Rolle, da beide Zeilen von einem Parser genau gleich ausgewertet werden. Probleme gibt es erst, wenn mehrere Symbole gleichzeitig deklariert werden sollen:

One int pointer   and one int
Two int pointers
One int array     and one int
Two int arrays
One int reference and one int
Two int references
int  *a,       b;
int  *c,      *d;
int   e[7],    f;
int   g[7],    h[7];
int  &i = b,   j;
int  &k = b,  &l = b;

Hier wird deutlich, dass die Pointer-, Array- und Referenz-Zeichen syntaktisch zum Symbol, und nicht zum Typ zugehörig betrachtet werden. Diese Zugehörigkeit zum Symbol ist bei erster Betrachtung unintuitiv und hat insbesondere zu früheren Zeiten immer wieder für Verwirrung gesorgt.

Der C90-Standard schrieb vor, dass sämtliche Deklarationen am Anfang eines Blockes zu stehen haben. Dadurch war es früher erforderlich, sämtliche Deklarationen (was Dutzende sein können) an einem Ort aufzuschreiben. Um die Lesbarkeit zu erhöhen, wurden mehrere gleichartige Deklarationen in einer einzigen Zeile geschrieben. Somit war die im letzten Beispiel gezeigte Schreibweise dazumal absolut sinnvoll. Ab dem Standard C99 jedoch wurde die Restriktion der Deklaration am Anfang des Blockes aufgelöst und die Deklarationen konnten dort geschrieben werden, wo sie Sinn machen.

Bei der Entwicklung von Bibliotheken und Frameworks jedoch muss der C90-Standard zur Kompatibilität mit den entsprechenden Compilern häufig auch heute noch eingehalten werden. So wurden im Laufe der letzten Jahrzehnte vermehrt speziell benannte Pointer-Typen mittels typedef eingeführt. Bei Deklaration mehrerer Symbole mit einem solchen Typ sind keine Zugehörigkeits-Verwechslungen mehr möglich.

Solche Framework-Typen werden oftmals durch Suffixe oder Prefixe wie beispielsweise ptr, p oder addr markiert. Die Markierung von Symbolen mit Prefixen und Suffixen war früher sehr verbreitet, insbesondere, da IDEs noch nicht so mächtig waren wie heute und da Compiler keine allzu langen Symbolnamen zuliessen (teilweise nicht mehr wie 8 Zeichen). So steht beispielsweise der Typ LPCWSTR für Long Pointer to Constant Wide String. Während bei Wartung von älterem Code diese Schreibweise nach wie vor üblich ist, ist dieselbe Schreibweise gemäss modernem Stil der Inbegriff von unleserlichem Code.

Empfehlung des Autors

Der Autor dieser Seite hat sich während all der langen Jahre nie an die vermeindlich korrekte Schreibweise des Pointer-Sternchens gewöhnen können. In seinem Verständnis gehört ein Pointer sowohl semantisch betrachtet zum Typ, weswegen auf der gesamten ManderC-Seite normalerweise das Sternchen auch da geschrieben wird.

Nach Auffassung des Autors ist die leidige Schreibweise des Pointer-Sternchens nur noch ein Echo aus früherer Zeit, welches nur dann zu debattierbaren Empfehlungen ausarten kann, wenn mehrere Symbole gleichzeitig zu einem gemeinsamen Typ deklariert werden sollen. Aufgrund der Vermischung von Deklarationen mit ausführbarem Code in C99, verbreiteten Code-Strukturierungs-Guidelines sowie durch die Verwendung von heutzutage bedeutend längeren Symbolnamen sind multiple Deklarasmen jedoch nur noch selten anzutreffen.

Dennoch werden bis zum heutigen Tage tausende unschuldige Programmieranfänger gefoltert und darauf getrimmt, das Sternchen zum Variablennamen, und nicht zum Typ zu schreiben. Begründet wird dies normalerweise damit, dass die Syntax von C so definiert sei. Was jedoch vergessen geht ist der Grund WIESO es so definiert sei. Hier ist die Antwort:

Die Erschaffer der Sprache C dachten, es sei eine gute Idee.

Ihre Überlegung war: Eine Deklaration soll so ähnlich wie möglich aussehen wie die spätere Benutzung im Code. Das Sternchen des Pointers bei der Deklaration wird in C und C++ im Code auch für den Dereferenz-Operator verwendet. Und wenn eine als Pointer deklarierte Variable dereferenziert wird, ergibt sie genau den Typ, der bei der Deklaration steht.

int i;
int a;
int *b;
int **c;

i = a;
i = *b;
i = **c;

Diese Überlegung wurde konsequent auch für die Deklaration von Arrays und Funktionspointer durchgezogen. Nur zeigt sie ihren hässlichen Kopf hauptsächlich bei Variablendeklarationen mit dem Pointer-Sternchen, da die Array-Klammern [] so oder so zum Symbol geschrieben werden müssen. Arrays von Pointern sind viel seltener als normale Arrays und sehen bei Auftreten genauso wie Funktions-Pointer sowieso schon sehr kompliziert aus. Deswegen werden kaum je mehrere Symbole von solchen Typen gemeinsam in einer Zeile deklariert.

Die Überlegung, das Pointer-Sternchen zum Symbol zu schreiben, hat sich bis in die heutige Zeit erhalten. Das Risiko, einen semantischen Fehler zu machen, wenn diese Schreibweise nicht eingehalten wird, ist nach Meinung des Autors jedoch äusserst gering. Der Autor empfielt, die gleichzeitige Deklaration mehrerer Symbole nur in erprobten Ausnahmefällen zu verwenden.