Assembler ist eine etwas besser leserliche Form der Maschinensprache (Sprache, die die CPU 'versteht'). Mit Assembler hast du bereits im Little Man Computer Bekanntschaft gemacht.
Befehle in Maschinensprache werden im Binärsystem ausgedrückt. Selbst für relativ kleine Zahlen resultiert dies daher in ziemlich langen Ausdrücken. Deshalb schreibt man diese oft ins Hexadezimalsystem um, was folgende Vorteile hat:
Das Hexadezimalsystem ist das Zahlensystem mit:
Beispiele:
Stelle folgende Zahlen im Binärsystem dar und wandle die ins Dezimalsystem um:
Stelle folgende Zahlen im Hexadezimalsystem dar und wandle sie ins Dezimalsystem um:
Den ARM-Risc-Simulator von Peter Higginson findet man hier: https://peterhigginson.co.uk/RISC/ Eine kleine Hilfe und die ganze Versionsgeschichte findet man direkt mit Help
Das ist die Übersicht aller möglichen Befehle: https://www.peterhigginson.co.uk/RISC/instruction_set.pdf
Der Simulator läuft direkt im Browser und besteht aus drei Teilen:
Unsere Simulator-CPU kann nur 16-Bit-Zahlen. Was war nochmals die grösste darstellbare unsigned Zahl? Wir können wir zwei 32-Bit-Zahlen addieren oder sogar multiplizieren?
Tipps:
Zerlege die beiden Zahlen in je zwei Teile, nämlich ein oberes und ein unteres Word (1 Word = 2 Bytes):
$$2'596'896'414 = \text{0x}\,\text{9AC9}\,\text{7E9E} = \text{0x}\,\text{9AC9} \cdot 2^{16} + \text{0x}\,\text{7E9E} \cdot 2^0$$
$$3'123'455'219 = \text{0x}\,\text{BA2C}\,\text{24F3} = \text{0x}\,\text{BA2C} \cdot 2^{16} + \text{0x}\,\text{24F3} \cdot 2^0$$
Nun kommt Binom
$$\text{0x}\,\text{9AC9}\,\text{7E9E} \cdot \text{0x}\,\text{BA2C}\,\text{24F3} = \text{0x}\,\text{9AC9} \cdot \text{0x}\,\text{BA2C} \cdot 2^{32} + \text{0x}\,\text{9AC9} \cdot \text{0x}\,\text{24F3} \cdot 2^{16} + \text{0x}\,\text{BA2C} \cdot \text{0x}\,\text{7E9E} \cdot 2^{16} + \text{0x}\,\text{7E9E} \cdot \text{0x}\,\text{24F3} \cdot 2^0$$
Jetzt kann man die einzelnen Summanden einfach richtig im Speicher verteilen:
$$2^0$$ | 204 |
$$2^{16}$$ | 205 |
$$2^{32}$$ | 206 |
$$2^{48}$$ | 207 |
Aber Achtung! Es gibt immer wieder Überträge, die in die Additionen für die einzelnen Speicheradressen mit einfliessen müssen.
Aus fast allen Programmiersprachen gibt es Bedingungen, die entweder Verzweigungen (if…else…) oder Schleifen (for… oder while…) steuern. Damit ein Compiler oder Interpreter diese Anweisungen in Maschinensprache übersetzen kann, muss es das also auch dort geben.
Sobald ein Programm gestartet wird, erhält es vom Betriebssystem RAM zugewiesen. Dieses RAM ist in zwei Blöcke unterteilt, die für unterschiedliche Operationen eingesetzt werden: Die Heap und der Stack (Wikipedia). In unserem Simulator beginnt das Programm bei Adresse 0000 und der Stack bei Adresse 0400 (s. Register SP). Der Stack wird absteigend aufgefüllt und belegt so den unteren Teil im Main Memory. Alles dazwischen kann als Heap verwendet werden…
Bisher haben wir Daten jeweils an einer bestimmten Adresse im Speicher abgelegt. Bei dieser Methode muss man immer auch die Speicheradresse kennen, sonst sind die Daten nicht mehr auffindbar. Den Adressbereich im Speicher, der dafür zur Verfügung steht, nennt man die Heap. Auf der Heap werden einerseits Daten gespeichert, die über einen grossen Teil des Programm benötigt werden, und andrerseits alle Objekte, Arrays sowie Elemente, deren Grösse sich ändern kann. Da unterschiedliche Datentypen unterschiedliche Speichergrössen haben, wird die Heap sehr unübersichtlich, und die Verwaltung der Speicheradressen ist extrem wichtig und ziemlich aufwändig. Werden einzelne Speicherbereiche nicht mehr gebraucht, sollten sie freigegeben werden, und später sollten die so entstandenen Lücken durch sog. Defragmentierung wieder geschlossen werden.
Der Stack ist ein Speicher, der als Stapel angeordnet ist: Es kann immer nur auf das oberste Element zugegriffen werden, und beim Zugriff wird das oberste Element gewöhnlich auch gerade entfernt, sodass nun das zweioberste Element wieder gelesen werden kann. Somit müssen die Elemente, die auf dem Stack abgelegt sind, in exakt umgekehrter Reihenfolge wieder gelesen werden. Der Prozessor hat für den Stack spezielle Befehle, die schnell arbeiten und wenig Verwaltung erfordern: PSH (Push) und POP. Der Stack wird eingesetzt für
Funktionen sind Programmteile, die in genau gleicher Art immer wieder benötigt werden. Anstatt überall die Codezeilen erneut einzufügen, wird sie einmal geschrieben und von überall her aufgerufen.
Beachte dazu den Artikel in Wikipedia, vor allem die Begriffserklärung und den Abschnitt 3. Die Fakultät kann man nicht nur wie in Aufgabe 12 iterativ, sondern auch rekursiv berechnen: Die Funktion Fakultät einer natürlichen Zahl $n \geq 1$ ist definiert als das Produkt der Zahlen 1 bis $n$: $$n! = 1\cdot 2 \cdot 3 \dotsm n$$ Das lässt sich auch als Zahlenfolge und somit rekursiv darstellen: $$(n+1)! = (n+1)\cdot n!$$ Das lässt sich auch genauso programmieren.
In C# könnte eine solche Methode Fakultät() etwa so aussehen:
static long Fakultät(long Zahl) { long lRet = 1; if (Zahl > 1) { lRet = Zahl * Fakultät(Zahl - 1); } return lRet; }