Ein kleines Beispiel: die bereits oben erwähnte (fiktive) Instruktion “0110110101”, ausgesprochen “addiere den Wert 5 auf den im Akkumulator gespeicherten Wert”, würde sich mit Hilfe einer Assemblersprache auf die folgende Art schreiben lassen:

add al, 5

Das Mnemonic al bezeichnet hier übrigens das Akkumulator-Register. Die Zusammenfassung mehrerer Assemblerbefehle, die zusammenhängend ausgeführt werden sollen, um eine Aufgabe zu lösen, nennt man Assemblerprogramm. Assemblerprogramme werden in der Regel auch nicht mehr über externe Geräte eingegeben und dann in Lochkarten gestanzt, sondern direkt im Computer eingegeben. Die Übersetzung in echten Maschinencode (denn nur solchen kann der Prozessor verstehen) übernimmt dann ein sogenannter Assembler. Das ist ein relativ einfaches Programm, welches die geschriebenen Assembler-Befehle aus einer Datei lesen und in Maschinencode übersetzen kann und am Ende zum Beispiel eine ausführbare Datei (unter Windows sind das die Dinger mit “.exe” hinten dran) erzeugt.

An dieser Stelle möchte ich noch auf eine kleine Eigenheit in Bezug auf Assemblersprachen hinweisen, um spätere Verwirrung zu vermeiden: in der Umgangssprache (unter Informatikern) werden nicht nur die Übersetzungsprogramme “Assembler” genannt, sondern mitunter auch der geschriebene Code selber. Man sagt dann, dass ein Programm “in Assembler geschrieben” ist – gemeint ist natürlich, dass es in einer Assemblersprache geschrieben wurde, aber Informatiker sind zuweilen recht faul, was diese Unterscheidung angeht. Ich werde nach Möglichkeit versuchen, die beiden Begriffe immer zu unterscheiden; wenn es mir einmal nicht gelingt, sollte es aber deswegen jetzt auch keine Verwirrung mehr geben.

Nun ist allein schon die Möglichkeit, Mnemonics anstelle von komplizierten Instruktions-Codes benutzen zu können, eine große Erleichterung beim Programmieren. In einer Assemblersprache sind allerdings noch mehr Dinge möglich. Eine in den Assembler-Anfangszeiten besonders wichtige Eigenschaft war die Unterstützung bei der Behandlung von Variablen und Sprungbefehlen.

Genauso, wie ein Programm im Speicher liegt, da es nicht nur ausschließlich in den Registern des Prozessors gehalten werden kann (dafür gibt es nicht genügend), können die Daten eines Programms meist auch nicht nur in den Registern vorhanden sein. Bisher habe ich es noch nicht erwähnt, aber eigentlich ist klar, dass auch Daten im Speicher gehalten werden. Sie sind genauso wie Instruktionen über eine Adresse ansprechbar; so könnte etwa das Register al in der obigen add-Anweisung auch durch eine Adresse ersetzt werden, so dass die 5 nicht mehr in den Akkumulator, sondern in den Speicher geschrieben wird. Nun ist das Hantieren mit Speicheradressen natürlich höchst unkomfortabel; aus diesem Grund können in einem Assemblerprogramm sogenannte Variablen deklariert werden. Das sind im Grunde nichts anderes als eindeutige Bezeichner für bestimmte Adressen im Speicher; die zugrundeliegende Adresse dabei entweder von Hand angegeben oder – und hier liegt der große Vorteil – automatisch vom Assembler bestimmt werden. Die Deklaration und Benutzung von Variablen im Code ist relativ simpel; nachfolgend ein kleines Beispiel, in welchem eine Variable var deklariert und anschließend an die von ihr bezeichnete Adresse im Speicher der Wert 5 geschrieben wird (das db bedeutet nur, dass lediglich ein einziges Byte an Speicher reserviert werden soll):

var db
mov var, 5

Für die Bearbeitung komplexerer Aufgabenstellungen, die über das reine Verrechnen von Zahlen hinausgehen, ist die Kontrolle des Programmflusses unerlässlich. In unseren bisherigen Betrachtungen sah es immer so aus, als würde streng eine Instruktion nach der anderen geladen; bei näherer Betrachtung erweist sich das allerdings als sehr nachteilig, da so nur wirklich lineare Probleme zu lösen sind. In der Regel sind allerdings auch Verzweigungen im Programmfluss und Wiederholungen von Programmcode nötig, außerdem auch der Aufruf sogenannter Unterprogramme (ein Unterprogramm ist im Grunde nichts anderes als ein Codeabschnitt, der eine meist kleinere Aufgabe löst). Und hier kommen die Sprungbefehle ins Spiel; sie ermöglichen, dass der sonst streng lineare Programmablauf unterbrochen werden kann, indem die nächste zu ladende Instruktion angegeben wird. Springt man dabei zurück zu einer bereits ausgeführten Instruktion, hat man eine Wiederholung; überspringt man einen Teil der Instruktionen, hat man eine Verzweigung im Programmcode; und springt man schließlich an eine ganz andere Stelle im Code, so entspricht dies dem Aufruf eines Unterprogramms. Wir erinnern uns: die Adresse der nächsten zu ladenden Instruktion steht im Instruktionsregister; modifiziert man nun also nun diesen Wert, kann ein Sprung in der Instruktionsreihenfolge durchgeführt werden. Der mnemonische Code für die einfachste derartige Anweisung, den unbedingten Sprung, ist jmp (als Abkürzung für “jump”, also Sprung), dass als Operanden das Sprungziel, also die Adresse der nächsten Instruktion erwartet. Zu Zeiten der Erstellung von Maschinencode per Hand war es dabei noch nötig, die exakte Adresse (die Adresse der konkreten Speicherzelle, welche die gewünschte Instruktion enthält) zu kennen, was insbesondere bei Vorwärtssprüngen kompliziert war. Außerdem hatte jede Änderung am Maschinencode zur Folge, dass unter Umständen die Sprungziele angepasst werden mussten (da sich durch das Einfügen von Befehlen ja die Adressen aller nachfolgenden Instruktionen ändern). Im Assemblercode ist es nun möglich, dieses Problem zu umgehen, und zwar dank der Verwendung von Sprungmarken; dies sind einfache Bezeichner, die an beliebigen Stellen im Code zwischen zwei Instruktionen eingefügt werden können und bei der Übersetzung in Maschinencode automatisch in konkrete Adressen umgewandelt werden. Hierzu ein kleines (nicht sinnvolles!) Beispiel (var ist unsere Variable von vorhin):

mov al, var
loop:
add al, 1
jmp loop

Der Code macht nichts anderes, als zu Anfang in das Register al den Wert zu schreiben, der im Speicher an der durch var beschriebenen Stelle steht und dann in einer (unendlich lang laufenden) Schleife immer wieder den Wert 1 darauf zu addieren.

1 / 2 / 3 / 4

Kommentare (20)

  1. #2 Marcus Frenkel
    August 31, 2011

    Interessant. Ich habe noch nie von der Maschine gehört – wieder etwas gelernt. Die “Initial Orders”-Sprache erscheint mir zwar wirklich wie ein reiner Aufsatz auf Maschinencode, aber gut, Assembler ist es trotzdem. Danke.

  2. #3 Nele
    August 31, 2011

    Sehr cooler Artikel. Das erinnert mich an die 80er, als ich noch einen 8-Bit-Computer in Z80-Code programmiert habe. Damals habe ich per Hand und Rodnay Zaks Z80-Bibel assembliert und das ganze dann mit einem Monitor byteweise in den Speicher geschrieben. Mit Monitor meine ich jetzt nicht das Datensichtgerät sondern die Software zur Kontrolle des Computers auf Prozessorebene.

    Das war die beste Möglichkeit überhaupt, ein einfaches Computersystem von Grund auf kennen zu lernen. Mit den heutigen Computerarchitekturen geht sowas wahrscheinlich überhaupt nicht mehr. 🙂

  3. #4 Marcus Frenkel
    August 31, 2011

    Ach, das geht schon auch mit heutigen Architekturen; sie haben ja immer noch die Befehle von damals (plus eine Menge mehr). Zum Kennenlernen kann da auf Assemblerebene mit einfachsten Sachen gearbeitet werden; wer richtig in die Tiefe will, kann sogar ein einfaches Betriebssystem programmieren, das ist auch nicht so sehr kompliziert (durch die vielen SSE-Befehle sogar eher einfacher).

    Und dank Virtualisierung muss man dabei nicht einmal seinen eigenen Rechner lahmlegen. 😉

  4. #5 cimddwc
    August 31, 2011

    Mit entsprechenden Hochsprachen-Makros angereichert, kann man auch heutzutage noch ganz gut größere Programme in Assembler schreiben, finde ich. 🙂 Nur Berechnungen werden gern etwas umständlich.

    Richtig lustig wird’s dann mit Befehlen wie cvttpd2pi, maskmovdqu, punpcklbw, rsqrtss und was die Hersteller sich sonst so alles im Rahmen von SSEx & Co. ausgedacht haben… zum Glück brauch ich die eher selten bis gar nicht. 🙂

  5. #6 Informatiker (grad) ohne Durchblick
    August 31, 2011

    Erst mal vielen Dank für die ganzen hoch interessanten Blogbeiträge. Hab ich zwar fast alles schon mal gehört, aber eine Auffrischung tut immer gut. Vor allem wenn sie so anschaulich und verständlich geschrieben ist.

    Allerdings tu ich mich grad mit der Tabelle etwas schwer, genauer mit den Einträgen für al. Denn in Zeile 6 (der Tabelle) wechselt der Wert von 1 auf 0 und in Zeile 12 von 2 auf -1. Wieso?
    Ist das ein Tippfehler, oder stehe ich grad völlig auf dem Schlauch.

  6. #7 Marcus Frenkel
    August 31, 2011

    @Informatiker (grad) ohne Durchblick
    Das waren Tippfehler. Oder besser: Copy&Paste-Fehler; jeder Informatiker weiß, dass man das nicht macht. Jeder Informatiker macht es trotzdem. Meine Güte.

    Danke für den Hinweis, die sind mir beim Probelesen durch die Lappen gegangen. 😉

  7. #8 rolak
    August 31, 2011

    Na endlich. Meine Lieblingssprachen 🙂

    aber Informatiker sind zuweilen recht faul, was diese Unterscheidung angeht

    Auch wenn (z.B. als von mir ausgehende Verallgemeinerung) die Einsortierung richtig sein könnte – es wären zwei (10) Dinge anzumerken:

    • Es gibt so etwas wie produktive Faulheit, wie z.B. die Verbeserung von Vorhandenem, weil nicht so lange gewartet werden möchte etc pp
    • Informatiker wissen berufsbedingt um die Verschiedenheit von Wort und Bedeutung, haben kein Problem damit, verschiedene ähnliche Bedeutungen mit einem einzigen Wort zu beschreiben, so lange es der Kontext erlaubt auf das Gemeinte zu schließen. Bei Teekesselchen im allgemeinen ‘beschwert’ sich ja auch keiner – obgleich die Bedeutungen dort oft wesentlich weiter auseinanderklaffen.

    die Bitketten konnten allerdings nicht einfach komfortabel am Rechner eingegeben werden, sondern mussten noch manuell auf ein Trägermedium transferiert werden

    Nun ja, ‘komfortabel’ kann es wirklich nicht genannt werden, aber ‘am Rechner’ war sehr wohl nicht nur möglich, sondern teilweise sogar nötig. Selbst zu meinen Lebzeiten liefen noch Rechner (wars ne PDP11 oder doch eine 9er?) mit einer Hex-Tastatur (und das war schon golden, eine Reihe Schalter war durchaus üblich) plus ein Taster, mittels der byte für byte, binary of cause, der loader eingepiddelt werden konnte, der dann den ersten Lochstreifen einlas.
    Oder bei meinem 8085 die paar byte handgelötetes DiodenROM, dessen code über die cpu-eingebaute serielle Schnittstelle und die gebastelte PLL vom Flohmarkt-Kassettenrekorder den Monitor las, letzterer, wie oben schon erwähnt, so eine Art OS für Arme. Dieses Rom war die Verbesserung (aus Faulheit!) der kleinen Zähler/dipswitch/latch-Kombination, mit der bis dahin die loader-bits ins RAM getrieben wurden.
    Also bitte, ein festes BIOS ist doch schon absoluter Luxus :p

  8. #9 Marcus Frenkel
    August 31, 2011

    Nun ja, ‘komfortabel’ kann es wirklich nicht genannt werden, aber ‘am Rechner’ war sehr wohl nicht nur möglich, sondern teilweise sogar nötig.

    Akzeptiert und korrigiert. 😉
    Das war vor meiner Zeit. Ich hatte zwar im Kopf, dass Maschinencode auch direkt eingegeben wurde (unter anderem auch über die erwähnten Schalter), aber hatte bei Maschinencode in erster Linie die Lochkarten vor Augen. 😉

    Und: niemand hat gesagt, dass Faulheit nicht schlecht wäre. Sie verwirrt nur manchmal die Außenstehenden – und manchmal sogar Informatiker selber. Man denke nur an die Verwirrung über den Zusammenhang zwischen “Parsern” und “Compilern”…

  9. #10 rolak
    August 31, 2011

    niemand hat gesagt, dass Faulheit nicht schlecht wäre

    Ich. Gerade eben oben 🙂

    Übrigens ist in der rss-mail hinter dem smilie hinter dem ‘korrigiert’ ein Zeilenvorschub, der hier im HTML nicht auftaucht. Wunder über Wunder…
    Das mit der Faulheit erkläre ich typischerweise etwas überhöht, um die merkwürdigen Gesichter des Gegenübers zu bewundern. Auf ‘Du bist faul’ rechnet wohl keiner mit einem ‘Na zum Glück!’. Doch aufgepasst, es gibt auch die dunkle Seite der Faulheit…

    Das mit P&C ist doch simpel: nur einer trägt seinen Namen zurecht; der eine gibt einem Zeichenstrom Struktur, der andere nimmt sie einer source; das Ergebnis des einen wird typischerweise von einem Programm ausgewertet, das des anderen von einer cpu 😉 Trotzdem, ich kann nicht verhehlen daß diese ein-Wort-für-alle-Nummer eine der vielen Varianten ist, mit denen nicht nur ich meine Gegenüber ‘teste’, schaue ob genügend frei assoziierend mitgedacht wird, Reaktionen in Wort und Mimik genieße. Spielfreude.

    Zu der Rechnerfamilie von eben fiel mir noch einer dieser uralten koans ein, den ich vorsichtshalber mal kopiere:

    In the days when Sussman was a novice, Minsky once came to him as he sat hacking at the PDP-6.
    “What are you doing?”, asked Minsky.
    “I am training a randomly wired neural net to play Tic-Tac-Toe” Sussman replied.
    “Why is the net wired randomly?”, asked Minsky.
    “I do not want it to have any preconceptions of how to play”, Sussman said.
    Minsky then shut his eyes.
    “Why do you close your eyes?”, Sussman asked his teacher.
    “So that the room will be empty.”
    At that moment, Sussman was enlightened.

  10. #11 JPeelen
    August 31, 2011

    Die Programmiersprachen der frühen programmierbaren Taschenrechner (in meinem Fall ein TI-59) waren den Assemblersprachen sehr ähnlich. Schleifen und logische Verzweigungen wurden in derselben Weise mit Labels und GOTO definiert. Man konnte sogar den vorhandenen Speicher variabel in einen Teil für Programmcode und einen zur Datenspeicherung aufteilen. Aufrufbare Unterprogramme ließen sich definieren.
    Der Knaller war die Möglichkeit, diese Programme auf kleinen (Format wie ein Kaugummistreifen) Magnetkarten zu speichern, die ein Motor durch den Rechner zog.

  11. #12 Marcus Frenkel
    August 31, 2011

    @JPeelen
    Die Sprachen der aktuellen Taschenrechner (also “aktuell” heißt hier, aus meiner Schulzeit; ist also auch schon einige Jahre her) sahen da immer noch nicht besser aus. 😉

    @rolak
    Ein schöner Zeitvertreib. 😉

  12. #13 rolak
    August 31, 2011

    Die TI59 waren schon heiße Teile, lehrten mich einiges zum Thema Preisverfall: Nach dem Ferienjob einen zu 700DM erstanden, nach Weihnachten gabs den für unter 400… Aber allein das Pfeifen, wenn eine der Karten durchrutschte 😉

    Und ja, das war vor über 30 Jahren alles schon so wie heute auch noch: Die Dinger wurden gehackt durch Modifikation des Taktgenerators (‘overclocking’ gabs damals noch nicht) und wilde Programmcodekombinationen, gecrackt so daß die internen und die wechselbaren ROMs ausgelesen und auf Karte(n) übertragen werden konnten zum Verteilen (am besten im Diktat, sonst schriftlich) und reverse engineering (immer schriftlich), (usergestütztes) Paging in den swapfile aka Kartenstapel gab es auch schon.

    Und dann brachten diese $@$&#?!cnpxneqf den HP41 raus…

  13. #14 Dr. Webbaer
    September 3, 2011

    Wo wird noch mit Assembler entwickelt?

    MFG
    Dr. Webbaer (der selbst immerhin noch ein Einsatzgebiet kennt)

  14. #15 Marcus Frenkel
    September 3, 2011

    Im Spielebereich, im Betriebssystembereich, überall dort, wo gewisse Mikrooptimierungen vorgenommen werden müssen, im Compilerbau, im Grafikbereich, bei der Treiberprogrammierung…

  15. #16 Dr. Webbaer
    September 3, 2011

    Aja, danke. Assembler lebt also.
    MFG Wb

  16. #17 rolak
    September 4, 2011

    “If a program can’t rewrite its own code”,
    he asked, “what good is it?”
    (src)

    Das ginge zwar in (extrem wenigen) Hochsprachen auch, doch wenn es {zusätzlich} wesentlich ist, das generierte Kompilat auch in größeren Sequenzen auf Bitebene zu kontrollieren, geht nichts am jeweiligen Assembler vorbei.
    Selbst wenn dies wg exzessiver Verwendung camouflierender HLL-Makros kaum mehr auffallen sollte.

  17. #18 Mj
    September 11, 2011

    wer richtig in die Tiefe will, kann sogar ein einfaches Betriebssystem programmieren, das ist auch nicht so sehr kompliziert (durch die vielen SSE-Befehle sogar eher einfacher).
    Und dank Virtualisierung muss man dabei nicht einmal seinen eigenen Rechner lahmlegen. 😉

    Wie würde man das machen? Also mit welchen Tools optimalerweise?

  18. #19 rolak
    September 11, 2011

    Wie würde man das machen?

    Was, das Lahmlegen? 😀

    Also mit welchen Tools optimalerweise?

    Möglichst mit solchen, die Deinem Stil entgegenkommen. Generell Assembler und Debugger – ich nutze A386/D386, freie Software gibt es zu Hauf, wie z.B. nasm/gdb. Die erste schöne Übung ist ein (im Laufe der Zeit immer komfortabler werdender) ohne Betriebssystem auskommender Schmal-Debugger, der die Aktivitäten des startenden Erzeugnisses protokolliert…
    Wie bei der BS-Entwicklung allerdings SSE hilfreich sein soll, erschließt sich mir noch nicht^^

    Oh ja, einen der vielen Virtualisierer bräuchtest Du natürlich auch noch, es sei denn, Dir wäre ein separater PC als Spielwiese lieber.
    Und nicht vergessen: Es gibt viel zum Abgucken und Lernen (bsp)!

  19. #20 Geldhai
    September 16, 2011

    Lahmlegen verstehe ich jetzt auch nicht