Structs
Wenn mehrere Variablen zusammengefasst einem gewissen Zweck dienen, bilden sie eine Informationseinheit. Eine Informationseinheit kann in C mittels des struct
-Typs gebildet werden, welcher mehrere verschieden geartete Variablen zusammenfasst und sie mittels Symbolen zugänglich macht. Im umgangssprachlichen Gebrauch wird dieses Konstrukt Struct
genannt. Innerhalb eines Structs werden die zusammengeclusterten Variablen Felder
genannt.
Bei C++ wurden Structs zu Klassen erweitert. Eine Klasse speichert zusätzlich zu den Feldern dazugehörige Funktionen, die sogenannten Methoden
. Wenn Klassen ineinander verschachtelt werden, werden Methoden von übergeordneten an untergeordnete Klassen vererbt
. Untergeordnete Klassen können dabei sogenannt virtuelle
Methoden überschreiben und mit ihrer eigenen Implementation belegen. Damit für Instanzen mit virtuellen Methoden während der Laufzeit des Programmes die korrekten Methoden aufgerufen werden können, benötigt jede solche Instanz eine sogenannte vtable
(virtuals table), welche speichert, welche Methoden zum dynamischen Typ gehören.
Details
Grundsätzlich gilt: Ein Struct sowie eine Klasse fasst mehrere verschiedenartige Variablen zusammen. Eine Variable, welche mit einem struct
- oder class
-Typ definiert wurde, zeigt somit an einen Speicherblock, wo mehrere Werte verpackt sind. Beim Ansprechen eines Feldes fügt der Compiler der Start-Adresse des Blockes einen Offset hinzu und ermittelt somit die Speicheradresse des gesuchten Feldes.
Wie genau die Felder jedoch tatsächlich im Speicher angeordnet sind, darüber herrscht (zumindest beim Autor) eine gewisse Unsicherheit und ist vermutlich zumindest teilweise abhängig vom verwendeten System und Compiler.
Nach Wissen des Autors werden Structs und Klassen stets als zusammengehöriger Block betrachtet. Somit spielt es keine Rolle, wo ein Struct alloziiert wird (Heap oder Stack), die Anordnung der Felder innerhalb des Speicherblockes bleibt immer gleich. Des weiteren werden die einzelnen Felder (nach Wissen des Autors) stets in der Reihenfolge innerhalb des Speicherblockes angeordnet, wie sie in den Sourcecode geschrieben wurden. Dadurch wird die Möglichkeit gegeben, die Speicher-Anordnung zu steuern und gewisse Eigenheiten der Platzierung der Variablen durch kleine Schwindeleien zu nutzen. Solche Spielereien werden heutzutage jedoch kaum mehr gemacht und gelten zudem als gefährlich, weswegen hier nicht weiter darauf eingegangen wird.
Die Offsets der einzelnen Felder werden von einem Compiler (nach Vermutung des Autors) stets aligniert
. Dies bedeutet, dass jeder Multi-Byte-Wert an einer Adresse beginnen muss, welche durch die Anzahl Bytes des Wertes ohne Rest teilbar ist. Ein 4-Byte-Wert muss somit beispielsweise an einer 4er-Adresse liegen. Dadurch kann es sein, dass bei Hintereinanderreihung zweier unterschiedlich grosser Typen zwischen den Werten zusätzliche Bytes eingefügt werden müssen, um das Alignment zu garantieren. Dieses Hinzufügen von Füll-Bytes wird Padding
genannt. Padding-Bytes sind als unbenutzt
zu betrachten, der Inhalt ist nicht definiert.
short a: 2 Bytes
double b: 8 Bytes
char c: 1 Byte
float d: 4 Bytes
char e: 1 Byte
short f: 2 Bytes
double g: 8 Bytes
Bei einem Sparc-Prozessor beispielsweise ist diese Alignierung zwingend notwendig, ansonsten entsteht ein Laufzeitfehler. Bei einem Intel-Prozessor müsste dies nicht zwingend der Fall sein, allerdings sind bei einem Misalignment
zwei Speicherzugriffe für ein Feld notwendig. Der Autor vermutet, dass C-Compiler generell (egal für welchen Prozessor) Felder stets alignieren.
Die Versuchung besteht, Variablen im Sourcecode manuell so anzuordnen, dass möglichst wenig Padding notwendig ist. Allerdings sind die Effekte bei modernen Computern und Compilern vernachlässigbar und führen nur in den seltensten Fällen zu einer Performance-Steigerung.
Nächstes Kapitel: Logik