Mandalex

Hier wird beschrieben, wie verschiedenartige Daten in Assembler im Speicher angelegt werden. Dies ist ein ziemlich wichtiges Thema, denn solange einem nicht genau bekannt ist, nach welchem Verfahren die Daten abgespeichert wurden und wo genau sie stehen, kann man kein Programm schreiben, ohne dass man irgendwann auf Fehler stösst, die manchmal sehr, sehr schwer zu korrigieren sind.

Grundsätzlich einmal zum Speicher, auch RAM genannt: Man ist sich mittlerweile gewohnt, Speicher mit 32 Bit zu adressieren. Dadurch können bis zu 4 GB Speicher adressiert werden, was momentan noch für die meisten Leute ausreichend ist. Speicher wird grundsätzlich linear durchnummeriert (mal abgesehen von Paging oder alternativ-Speicher, aber das ist ein anderes Thema), das heisst, man beginnt bei der tiefen Adresse 0x00000000 und zählt nach oben bis 0xffffffff. Und nun entsteht vielleicht beim einen oder anderen ein Bisschen Verwirrung: Wenn man Speicher grafisch darstellt, so werden die Speicherstellen von oben nach unten dargestellt, das heisst, dass die tiefen Adressen oben stehen und die hohen Adressen unten:

0x00000000:  |xxxx|xxxx|xxxx|xxxx|
0x00000004:  |xxxx|xxxx|xxxx|xxxx|
0x00000008:  |xxxx|xxxx|xxxx|xxxx|
...
0x000ffff8:  |xxxx|xxxx|xxxx|xxxx|
0x000ffffc:  |xxxx|xxxx|xxxx|xxxx|

Wenn nun irgendwo in einem Text steht, dass der Speicher von oben nach unten verläuft, so ist damit gemeint, dass die Adressen von hohen zu tiefen verlaufen, und nicht anders herum!

Beim Sparc-Prozessor müssen, im Gegensatz zu Intel- und Motorola-Prozessoren, alle Daten aligniert werden, das heisst, dass die Adresse einer Variablen stets ohne Rest durch ihre Grösse teilbar sein muss. Ein Integer-Wert kann also beispielsweise nur an 4er-Adressen stehen. An der Adresse 0x00000003 beispielsweise könnte kein Integer stehen, sondern höchstens ein char. Es gibt momentan nur die folgenden Alignierungen: 1-Byte (für chars), 2-Byte (für short), 4-Byte (für int) und 8-Byte (für long). Wird beim Sparc-Prozessor probiert, aus einer nicht-alignierten Adresse zu lesen oder dorthin zu schreiben, so eintsteht ein BUS-Fehler. Kleine Nebenbemerkung: Auch bei den Intel- und Motorola-Prozessoren ist es eher von Vorteil, die Daten zu alignieren, denn nicht-alignierte Daten verursachen immer zwei Speicherzugriffe anstelle von einem, und das kostet sehr viel Zeit!

Nun zu den Datenstrukturen:

Structs:

Structs oder Records werden im Speicher aufsteigend angelegt. Sie werden dabei nach folgendem Prinzip aligniert:

Benötigt die Variable weniger Platz als die nachfolgende, so steht sie an der höchstwertigsten Stelle von genau so vielen Bytes, wie für die Alignierung für die nachfolgende Variable benötigt werden.

Benötigt die Variable gleich viel oder mehr Platz als die nachfolgende, so wird genau dieser Platz auch benutzt.

struct
int   a: 4 Bytes \                                                                                 a:0x00000000
short b: 2 Bytes  \                     \         0x00000000: |aaaaaaaaaaaaaaaaaaa| \              b:0x00000004
short c: 2 Bytes   ----------------------\        0x00000004: |bbbbbbbbb|ccccccccc|  \---------\   c:0x00000006
int   d: 4 Bytes   ----------------------/        0x00000008: |ddddddddddddddddddd|  /---------/   d:0x00000008
char  e: 1 Byte   /                     /         0x0000000c: |eeee|----|fffffffff| /              e:0x0000000c
short f: 2 Bytes /                                                                                 f:0x0000000e


                                                  0x00000000: |aaaaaaaaa|---------| \
short a: 2 Bytes\                                 0x00000004: |-------------------|  \             a:0x00000000
long  b: 8 Bytes \                                0x00000008: |bbbbbbbbbbbbbbbbbbb|   \            b:0x00000008
char  c: 1 Byte   \                   \           0x0000000c: |bbbbbbbbbbbbbbbbbbb|    \           c:0x00000010
long  d: 8 Bytes   --------------------|          0x00000010: |cccc|--------------|     \-----\    d:0x00000018
int   e: 4 Bytes  /                   /           0x00000014: |-------------------|     /-----/    e:0x00000020
char  f: 1 Byte  /                                0x00000018: |ddddddddddddddddddd|    /           f:0x00000024
short g: 2 Byte /                                 0x0000001c: |ddddddddddddddddddd|   /            g:0x00000026
                                                  0x00000020: |eeeeeeeeeeeeeeeeeee|  /
                                                  0x00000024: |ffff|----|ggggggggg| /

Ein Array wird ebenfalls aufsteigend abgespeichert. Definiert wird es durch die Adresse des ersten Elements (an der tiefsten Adresse) und dem index, welcher mit der Grösse eines Elements multipliziert wird:

int a[5]:               0x00001000 + 0 * 0x04 = 0x00001000:  a[0]
                        0x00001000 + 1 * 0x04 = 0x00001004:  a[1]
                        0x00001000 + 2 * 0x04 = 0x00001008:  a[2]
                        0x00001000 + 3 * 0x04 = 0x0000100c:  a[3]
                        0x00001000 + 4 * 0x04 = 0x00001010:  a[4]

Bei Mehrdimensionalen Arrays wird je nach Programmiersprache entweder Zeilen- oder Kolonnen-Vorrangig adressiert:

int Beispiel[Anzx][Anzy];  Das Element Beispiel[x][y] wird folgendermassen adressiert:
Zeilen-Vorrangig:       start + (y * Anzx + x) * 4
Kolonnen-Vorrangig:     start + x * Anzy * 4 + y * 4

In einem Stack werden die Variablen verkehrt herum angelegt, also von oben nach unten (von hohen zu tiefen Adressen). Beinhaltet der Stack Arrays oder Structs, so werden diese jedoch innerhalb des definierten Speicherplatzes vollständig, wie gehabt, austeigend angelegt. Jeder einzelne Eintrag des restlichen Stackframes muss nach folgendem Prinzip aligniert sein:

Benötigt die Variable weniger Platz als die nachfolgende (tiefere Adresse), so steht sie an der niederwertigsten Stelle von genau so vielen Bytes, wie für die Alignierung für die nachfolgende Variable benötigt werden.

Benötigt die Variable gleich viel oder mehr Platz als die nachfolgende, so wird genau dieser Platz auch benutzt.

Wiederum die zwei Beispiele:


int   a: 4 Bytes \                                0x00100000:                        <-- Ab hier stehen die gesicherten Register           a: %fp - 0x04
short b: 2 Bytes  \                     \         0x00100004: |fffffffff|----|eeee|                                                        b: %fp - 0x06
short c: 2 Bytes   ----------------------\        0x00100008: |ddddddddddddddddddd|                                             ------\    c: %fp - 0x08
int   d: 4 Bytes   ----------------------/        0x0010000c: |ccccccccc|bbbbbbbbb|                                             ------/    d: %fp - 0x0c
char  e: 1 Byte   /                     /         0x00100010: |aaaaaaaaaaaaaaaaaaa|                                                        e: %fp - 0x0d
short f: 2 Bytes /                                0x00100014:                        <-- Hier zeigt der %fp hin                            f: %fp - 0x10


                                                  0x00100000:                        <-- Ab hier stehen die gesicherten Register
                                                  0x00100004: |-------------------|  <-- Diese 4 Bytes müssen ebenfalls berücksichtigt
                                                  0x00100008: |ggggggggg|----|ffff|      werden, denn %sp muss durch 8 teilbar sein!
short a: 2 Bytes\                                 0x0010000c: |eeeeeeeeeeeeeeeeeee|                                                        a: %fp - 0x02
long  b: 8 Bytes \                                0x00100010: |ddddddddddddddddddd|                                                        b: %fp - 0x10
char  c: 1 Byte   \                   \           0x00100014: |ddddddddddddddddddd|                                             ------\    c: %fp - 0x11
long  d: 8 Bytes   --------------------|          0x00100018: |-------------------|                                             ------/    d: %fp - 0x20
int   e: 4 Bytes  /                   /           0x0010001c: |--------------|cccc|                                                        e: %fp - 0x24
char  f: 1 Byte  /                                0x00100020: |bbbbbbbbbbbbbbbbbbb|                                                        f: %fp - 0x25
short g: 2 Byte /                                 0x00100024: |bbbbbbbbbbbbbbbbbbb|                                                        g: %fp - 0x28
                                                  0x00100028: |-------------------|
                                                  0x0010002c: |---------|aaaaaaaaa|
                                                  0x00100030:                        <-- Hier zeigt der %fp hin