Wenn man beispielsweise ein C-Programm compiliert
dann wird dass doch in Assembler Code umgewandelt,
aber manb kann keinen klaren Code lsen. Wenn man
Assembler schreibt muss man das ganze ja eigentlich
auch wieder durch ein spezielles Assembler Programm
laufen lassen, soweit ich weiß. (in was wird sowas
eigentlich geschrieben!?)
Nachdem Calocybe und Frank Schönmann die wesentlichen
Antworten bereits gegeben haben, möchte ich noch etwas
nachtragen zum Thema Assembler und Maschinensprache.
Beide sind für mich nämlich wesentlich weiter voneinan-
der entfernt, als die bisherigen Beiträge dies vermuten
lassen. (Das hängt nicht zuletzt mit der Qualität der
verschiedenen Assembler zusammen.)
Maschinensprache besteht also aus Bytefolgen.
Üblicherweise beginnt eine solche Bytefolge mit einer
Kennzeichnung des Befehls (Beispiele siehe bei Frank);
aus dieser Kennzeichnung ist dann auch ableitbar, wie
viele Bytes der Befehl enthält (das ist nämlich sehr
unterschiedlich, weil es vom Befehl abhängt, welche und
wie viele Operanden er verarbeiten soll und in welchem
Format die anzugeben sind: Will man den Inhalt zweier
Register addieren, dann reichen eine ADD-Markierung und
die Registernummern; will man zwei Speicherwerte addieren
- vorausgesetzt, die CPU kann so etwas - dann müssen zwei
Speicheradressen als Operanden her, und die sind dann
etwas länger usw.).
Man "denkt" also wirklich in Speicherabbildern und
(numerischen) Adressen, wenn man Maschinensprache
schreibt. Das tat allerdings auch in den 70er-Jahren
schon niemand mehr.
Assembler ist eine Sprache, die versucht, diese Maschi-
nensprache durch abstrakte Features handhabbarer zu
machen. Beispielsweise will man eben möglichst *nicht*
wissen müssen, wie lang ein Befehl ist - denn versuche
mal, eine Schleife zu programmieren, wenn Du die Sprung-
adressen in realen Speicherpositionen berechnen mußt!
Füge nur einen einzigen Befehl in Dein Programm ein,
und alle Adressen sind falsch, nämlich um die Länge
dieses Befehls verschoben.
Also versteht Assembler als allererstes schon mal Varia-
blen. So, wie es Befehle in Assembler gibt, mit denen
man Maschinenoperationen notiert, gibt es auch Befehle,
mit denen man Variablen deklariert - ganz ähnlich wie
in JavaScript, bloß beschreibt die Anweisungsfolge (im
Wesentlichen) immer noch sequentiell eine Speicherbele-
gung. Wenn man also mitten zwischen seinen Befehlen
eine Variable deklariert, ist das wahrscheinlich keine
so brilliante Idee - aber ausschließen kann man das
auch nicht.
Und wenn man Speicherstellen einen Variablennamen geben
kann, dann natürlich auch Programmstellen einen Sprung-
markennamen. Der Assembler macht für den Entwickler die
gesamte Adreßrechnerei und generiert am Ende (fast schon
Maschinenbefehle mit den entsprechenden Operandenwerten
(Ich überspringe mal den Aspekt der Relozierbarkeit,
Linker usw. - das fällt dann ins Kapitel Betriebssystem-
architektur.)
Das nächste, was man auch dringend braucht, ist die
Möglichkeit, Code innerhalb eines Programms wiederzu-
verwenden. Also das, wofür man heute Funktionen schreibt.
Dafür gibt es in Assembler zwei Möglichkeiten:
- Entweder springt man an den Anfang eines Code-Stücks,
nachdem man seine aktuelle Adresse vorher irgendwo
hinterlegt hat, und das Code-Stück springt am Ende
wieder dorthin (plus eine Befehlslänge) zurück, - oder man läßt den Code einfach kopieren.
Dafür gibt es in Assembler Makros - ja, das Zeug, was
es auch in Word oder Excel gibt, die Idee ist *ur*alt.
Ein wirklich guter Assembler kann bei solchen Makros
übrigens auch Parameter übergeben und in einer Meta-
sprache Programme formulieren, die zur Assemblierungs-
zeit bestimmen, wie der Inhalt der generierten Befehle
aussehen soll - man kann also durchaus variablen Code
damit erzeugen.
Zudem kann man natürlich beliebige Assembler-Module
separat schreiben und übersetzen, und erst später zu
einem Programm zusammenbinden lassen. Modularität ist
keine Erfindung der neueren Informatikgeschichte.
Die Vielzahl weiterer features eines guten Assemblers
(Dummy-Sections und Basisregister, für etwas Ähnliches
wie Objektadressierung) aufzuzählen würde zu weit führen.
Was ich damit letztlich nur sagen wollte, ist: Auch
wenn heute niemand mehr maschinenabhängig programmiert,
so finden sich doch fast *alle* wesentlichen features
von 3GL-Programmiersprachen bereits in Assemblersprachen
der 70er Jahre wieder.
Die uralte IBM370-Architektur, also der gute alte Groß-
rechner, hat einen (!) Maschinenbefehl, der
1. den Inhalt einer Menge (!) von Registern an eine
Speicherstelle schreibt, deren Adresse in einem als
Operand anzugebenden Register steht,
2. die aktuelle Adresse plus seine Befehlslänge in ein
Register schreibt und
3. an die in einem anderen Register oder als Operand im
Speicher stehende Adresse springt.
Allein mit diesen einzigen Befehl hat man schon eine
wunderbare Prozeduraufrufschnittstelle, mehr braucht
man nicht. (Rückladen einer Registermenge gibt es
natürlich auch, das macht dann die aufgerufene Funktion
direkt vor dem Rücksprung.)
Und die Zieladresse kann man dann auch noch mit symbo-
lischen Adressen (quasi dem "Funktionsnamen") angeben.
Wenn man solche Speicherbereiche im Speicher "stapelt",
dann hat man auch (fast) alle Voraussetzungen für ge-
schachtelte, insbesondere rekursive Funktionsaufrufe.
Ein gutes, strukturiert geschriebenes Assemblerprogramm
ist genauso lesbar wie ein Programm in einer beliebigen
"Hochsprache". Es ist nur länger, weil man Sprachelemente
wie Schleifen oder Case-Abfragen eben selbst programmieren
muß. Das aber wiederum ist eine Eigenschaft der zugrunde-
liegenden Maschinensprache - nicht des Assemblers ...
Und die Maschinensprachen der Großrechner konnten halt
schon vor 30 Jahren Dinge, die ein Pentium heute noch
nicht beherrscht (aber heute auch nicht mehr beherrschen
muß, weil es eben Hochsprachen gibt). Beispielsweise
mit *einem* Maschinenbefehl zwei 16 Millionen Zeichen
lange Strings miteinander zu vergleichen und die Position
der ersten Abweichung in einem Register hinterlegen ...