Twitter und Facebook-Anbindung
X
Tweet Follow @twitterapi
!!! Anbindung an twitter und facebook öffnen !!!

Wenn Ihnen mein Online-Buch gefällt,
dann bedanken Sie sich doch mit einer kleinen Spende...

8.4 Strukturen

8.4 Strukturen

Ein großer Nachteil an Feldern ist, dass man dort immer nur eine Sorte an Werten ablegen kann und dass der Zugriff auf jene mit ein wenig Denkarbeit verbunden ist, da man bei 0 anfängt zu zählen. Dazu kommt, das eine Zuordnung fehlt, also was steht in welchem Feld.

Hier kommen die Strukturen ins Spiel. Wie der Name bereits verrät, kann man "Eigenschaften" Werte zuordnen.

Zum Seitenanfang
Zum Inhaltsverzeichnis

8.4.3 Strukturen für Profis

8.4.3 Strukturen für Profis

Wenn wir jetzt also das Byte Padding im Hinterkopf haben, sollten wir den Aufbau einer Struktur durchdenken. Strukturen werden in der Regel immer am Stück gehalten und in der Reihenfolge gespeichert, in der sie definiert wurden. Das hat zur Folge, dass ein Byte Padding auftritt und mehr Speicherplatz benötigt wird, als man bräuchte. Dem kann man entgegentreten, indem man die Reihenfolge ändert. Hier mal ein Beispiel.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
					
struct SLarger {
	char	a;	// 1 Byte
	int	b;	// 4 Byte
	char	c;	// 1 Byte
	short	d;	// 2 Byte
};

struct SSmaler {
	char	a;	// 1 Byte
	char	c;	// 1 Byte
	short	d;	// 2 Byte
	int	b;	// 4 Byte
};

// ...

printf("SLarger = %2i Byte\n", sizeof(SLarger));
printf("SSmaler = %2i Byte\n", sizeof(SSmaler));
					

Ausgabe:

SLarger = 12 Byte
SSmaler =  8 Byte
		

Wie Sie sehen können, besitzen beide Strukturen zwar den gleichen Inhalt, werden aber mit einer unterschiedlichen Größe im Speicher verwaltet. Folgende Grafik veranschaulicht dies noch einmal.

Speicherverwaltung von Strukturen mit Byte Padding

Die roten Bereiche sind die künstlich aufgefüllten Blöcke. Aber was hat das jetzt für folgen? Nur der extreme Programmierer setzt sich jedes Mal mit Taschenrechner hin und rechnet aus, in welcher Reihenfolge er was definiert. Sie können diesen Aspekt vorerst vernachlässigen. Viel wichtiger ist es, dass Ihr Programm läuft. Hinterher kann man optimieren. Dazu kommt, dass derartige Optimierungen eigentlich nur ins Gewicht fallen, wenn man sehr sehr viele dieser Strukturen benötigt (z.B. ein Array mit einer Million solcher Elemente). Falls ihr Programm aber mal schneller laufen könnte und oder zu viel Arbeitsspeicher benötigt wird, sind dies die ersten Ansatzpunkte im Quellcode, an denen man Zeit und Platz gutmachen kann.

Eine weitere Schlussfolgerung aus diesem Kapitel soll sein, dass die Größen von Arrays möglichst immer eine Zweierpotenz sein sollte. Es werden somit unnötige RAM Zugriffe gespart und jeder Zugriff nutzt den Bus vollständig aus.

Zum Seitenanfang
Zum Inhaltsverzeichnis

8.4.2 Byte Padding

8.4.2 Byte Padding

Bisher habe ich ein paar Sachen verschwiegen, welche essentiell sind, wenn man richtig schnelle und performante Programme entwickeln möchte. Und zwar geht es mir um die interne Speicherverwaltung. Ich werde jetzt nicht die komplette Speicherverwaltung erklären. Abgesehen davon, dass ich Sie damit jetzt nur langweilen würde, ist dies auch nicht notwendig.

Ich hatte ja schon erwähnt, dass ein Boolean ein Byte groß ist, obwohl ein Bit ausreichend wäre und hatte dies ein wenig darauf geschoben, dass dieser Datentyp neu sei und aus Abwärtskompatibilitätsgründen so groß ist. Nun, eigentlich steckt dahinter noch ein weiterer viel wichtigerer Grund. Der Arbeitsspeicher ist u.a. in Blöcke eingeteilt, welche ein Byte oder ein vielfaches von einem Byte groß sind. Dies liegt daran, dass jeder Bereich adressiert werden muss, um Daten abzulegen bzw. sie holen zu können. Würde man jedes einzelne Bit adressieren wollen, bräuchte man viel größere Adressräume und viel Sinn würde dies auch nicht machen, da man eh in 99,99% der Fälle größere Bereiche benötigt.

Des Weiteren werden Daten immer erst auf Größe analysiert, bevor sie abgelegt werden. Zudem werden sie immer so platziert, dass sie immer an einer Adresse liegen, welche ein Vielfaches ihrer Größe entspricht. Wie Sie sich denken können folgt daraus, dass die Daten nicht ganz zusammenhängend im Speicher liegen. Man spricht von einer Fragmentierung. Der Platz zwischen den Variablen kann jetzt aber nicht einfach so genutzt werden, sondern wird mit Nullen aufgefüllt. Dies nennt man das Byte Padding.

Speicherverwaltung mit unterschiedlichen Datentypen

Aber warum werden die Variablen so im Speicher abgelegt, wenn man doch dadurch Platz verschwendet? Der Grund ist genauso einfach wie genial. Immer wenn die CPU auf den RAM zugreift, bzw. Daten verlangt, werden intern immer gleich mehrere Blöcke mit einem Schlag geholt (meistens 64 oder 128, je nach Busbreite). Das macht auch Sinn, weil viele Variablen eh größer als ein Block sind und so wird sie mit einem einzigen s.g. "fetch" geholt, statt vieler einzelner. Warum das gut ist? Die Zeitspanne zwischen dem Anfordern und dem Erhalt, dauert für die CPU eine halbe Ewigkeit und je mehr Zugriffe man benötigt, desto langsamer wird ein Programm (und das erheblich – deshalb gibt es in der CPU auch einen Cache).

Aber was hat das damit zu tun, dass die Variablen dann an einer bestimmten Adresse stehen müssen? Stellen Sie sich vor, die CPU kann immer nur 64 Blöcke mit einem mal holen und Sie hätten eine Variable, welche vier Blöcke benötigt und an den Adressen 62 bis einschließlich 65 stehen würde. Die Konsequenz daraus ist, dass man zwei Zugriffe benötigen würde (den von 0 bis 63 und den von 64 bis 127), obwohl einer reichen würde. Wenn diese Variable also an einer durch vier teilbaren Stelle liegt (60 oder 64), reicht wieder ein Zugriff. Sie fragen sich jetzt vielleicht, warum dann nicht z.B. Block 10 bis 73 geholt wird? Abgesehen von anderen Mechanismen bei der Speicherverwaltung (siehe Paging und Cachingverfahren) hat dies einen ganz einfachen Grund. Der Computer kann super gut mit Werten umgehen, welche einer Zweierpotenz entsprechen. Rechenoperationen mit diesen Werten können sehr effizient durchgeführt werden und sind leicht in Hardware zu gießen. Alle Anderen Zahlen würden einen viel höheren Rechenaufwand bedeuten und das gesamte System ausbremsen.

Zum Seitenanfang
Zum Inhaltsverzeichnis

8.4.1 Definition und Umgang mit Strukturen

8.4.1 Definition und Umgang mit Strukturen

Das Schema sieht so aus: struct <NameDesStructTypes> { <Structdefinition> };

Mit "Strukturdefinition" ist gemeint, dass hier jetzt eine reine von Variablen deklariert (nicht initialisiert) werden, also erst Datentyp und dann Variablenname.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
					
struct SDatum {
	int iTag;
	int iMonat;
	int iJahr;
};

struct SPerson {
	int	iAlter;
	float	fGewicht;
	SDatum	sGeburtstag;
};

SPerson sThomas;				// Der Typname hat ein großes S und die Variable ein kleines s

sThomas.iAlter			= 26;
sThomas.iGewicht		= 85.3;
sThomas.sGeburtstag.iTag	= 6;
sThomas.sGeburtstag.iMonat	= 9;
sThomas.sGeburtstag.iJahr	= 1984;

sSwillingVonThomas		= sThomas;	// ACHTUNG - In anderen Sprachen geht das nicht

printf("Zwilling von Thomas wurde am %02i.%02i.%i geboren.",	sSwillingVonThomas.sGeburtstag.iTag,
								sSwillingVonThomas.sGeburtstag.iMonat,
								sSwillingVonThomas.sGeburtstag.iJahr);
					

Ausgabe:

Zwilling von Thomas wurde am 06.09.1984 geboren.
		

Wie ich gezeigt habe, kann man zum einen auf die Unterelemente einer Struktur mit einem "." zugreifen und Strukturen lassen sich auch ineinander verschachteln. Sie können sich das in etwa wie mit einer Festplatte vorstellen, wobei jedes Verzeichnis eine Struktur ist und die enthaltenen Dateien eine Eigenschaft dieser Struktur. Nur benutzt man bei den Strukturen ein Punkt anstelle eines Backslashs (nicht C:\Windows\Fonts\arial.tft sondern C.Windows.Fonts.arial_tft).

Zum Seitenanfang
Zum Inhaltsverzeichnis

© Copyright by Thomas Weiß, 2009 - 2012