Programmierseite - Interessante Themen
Hauptseite |  Bücherseite |  Linkseite |  Linuxseite |  Programmierseite |  Ereignisse


<< Zurück Alle Angaben ohne Gewähr. Benutzung auf eigenes Risiko.
(C) Copyright Bernd Noetscher 1995 - 2000
eMail: webmaster@berndnoetscher.de

Assembler

This software may be used and distributed according to the terms of the GNU Public License,
incorporated herein by reference.

.

1. Grundlagen: Zusammenspiel Prozessor und Programm

Bevor man so richtig loslegen kann, muß natürlich erst einmal ein gewisses Basiswissen vorhanden sein. Deshalb muß vor der eigentlichen Grafikprogrammierung Grundlegendes erläutert werden. So wird in den späteren Kapiteln alles jetzt Erwähnte vorausgesetzt. 

Für die Hardware- bzw. Assemblerprogrammierung ist die Kenntnis der Datentypen mit ihren Wertebereiche und deren Platzbedarf sehr wichtig. Die einzelnen Datentypen unterscheiden sich letztlich lediglich durch ihren Speicherplatzbedarf im RAM und durch das verschiedene Ansprechen durch die CPU. Das heißt, alle Datentypen bestehen aus Bytekettten unterschiedlicher Größe und werden erst durch eine unterschiedliche Sichtweise (denn Bytes sind Bytes) unterscheidbar. Z. B. kann die Bytefolge "01 65" als 16bit Zahl angesehen werden oder als Zeichenkettenvariable mit einer Länge von 1 und dem Großbuchstaben A (entspricht der Pascal-Konvention; führendes Byte enthält die Stringlänge).


Die wichtigsten Datentypen:

Bezeichnung

Anzahl der Byte

Anzahl der Bits

Wertebereich signed

Wertebereich unsigned

verschied. Zustände

ShortInt/Byte 1 o

8

-128 bis 127

0 bis 255

256

Integer/Word 2 oo

16

-32768 bis 32767

0 bis 65535

65536

Long Integer 4 oooo

32

231 bis 231-1

0 bis 232-1

232

Pointer 4 oooo

32

-

-

-

Es gibt natürlich noch viel mehr Datentypen (Gleitkommavariablen, Zeichenketten usw.), wichtig sind aber zur Grafikprogrammierung (gemeint ist die Darstellung, keine 3D-Berechnungen) im eigentlichen Sinne nur die aufgeführten. Die Datentypen müssen entsprechend der Registerbreite bzw. der Ergebnisgröße der math. Funktionen gewählt werden. Dabei sollte man so wenig wie möglich Speicherplatz benötigen, damit von vornherein Platzverschwendung vermieden wird, denn es rächt sich später, wenn eine zeitraubende Umdefinierung, aufgrund von Speichermangel, notwendig wird. 

Z. B. braucht das Ergebnis einer Multiplikation von zwei Variablen vom Typ Byte/ShortInt (1 Byte * 1 Byte) mind. einen Speicherplatz von Typ Integer/Word (2 Byte), denn das Ergebnis könnte größer als der Wertebereich einer Byte-/ShortIntvariable sein. Das Ergebnis muß in der Regel so groß wie die Summe aus den Speichergrößen der Operanden sein. Bei Datentypen mit Vorzeichen muß ein Bit das Vorzeichen speichern. Dadurch vermindert sich natürlich die Bitanzahl zur Darstellung der Zahl um eins. Es wird der Wertebereich um die Hälfte "reduziert". Der Prozessor selbst kann signed und unsigned nicht unterscheiden. Die Unterscheidung wird es durch entsprechende Befehle möglich. Der Programmierer muß durch die entsprechenden Befehle dem Prozessor mitteilen, ob es sich um signed oder unsigned Variablen handelt. 

Häufig müssen die einzelnen Bits von Variablentypen mit mehreren Bytes getrennt angesprochen werden. Z. B. besteht eine Wordvariable aus zwei Bytes von dem das höherwertige Hibyte (rechtes Bytestück) und das niederwertige Lobyte (linkes Bytestück) genannt wird. 

Anordnung einzelner Bits der Variablen Intgeger/Word und Long Integer: 

Bits

8

8

8

8

Datentypen
Lo-Byte Hi-Byte Word insg. 16bit
Lo-Word Hi-Word Long Integer insg. 32 bit

Übriges besteht ein Unterschied in der Speicherreihenfolge der Byteteile einer Variablen zwischen den Intelsystemen und den Macsystemen. Bei Intel wird zuerst Hi, und dann Lobyte abgelegt, was bei Macintosch genau umgekehrt geschieht. Dies ist insofern interessant, wenn ein Datenaustausch zwischen beiden Systemen vorgenommen werden muß, dann muß nämlich die Reihenfolge der Lo- und Hibytes beachtet werden.


Die Prozessorregister

Wer eine Hochsprache beherrscht und dort jahrelang Programme entwickelt hat, wird auf den ersten Blick überrascht sein, denn es sind nur einfache Befehle mit nur maximal zwei Operanden möglich.

Allgemeine

Segment-

Offset-

Register

register

register

AX

CS

IP

BX

DS

SI

CX

ES

DI

DX

SS

SP

BP

FLAGS

Nun stellt sich die Frage, was ist ein Register. Technisch gesehen ist ein Register ein sehr schneller Speicher (sehr viel schneller als der Arbeitsspeicher), der nur eine kleine Kapazität besitzt. Genauer gesagt, kann das größte Register 4 Bytes speichern. Doch keine Angst, die wenigen Bytes reichen vollkommen aus, um auch die komplexesten Probleme in der EDV zu lösen. 

Die Register sind historisch in ihrer Größe gewachsen, so waren die ersten Register lediglich 1 Byte groß. Später gebrauchte man 2 byte große Register, die MS DOS und die älteren Programme benutzen. Heute sind die sogenannten 32 bit großen Register (z. B. 32-bit-Programmierung von OS/2 oder Windows 95), also 4 byte große Register bekannt. Die großen 4-byte-Register bieten den Vorteil, daß sie ohne großen Aufwand auch die sehr große Long Integer-Zahl in nur einem Register verwalten können (was bei 16 bit zwei Register erfordert). Zusätzlich wurde das leidige Thema der Segmentierung des Arbeitsspeichers "abgeschafft". Darin liegt der Hauptvorteil, denn nun kann man auch in einfacher Weise größere Arbeitsspeicherabschnitte (>64 KB) verwalten (betrifft Kopieren, Verschieben, Beschreiben und Lesen, ohne auch nur auf irgendwelche Segmentbegrenzungen achten zu müssen). 

Aus Sicht des Programmierers entspricht ein Register im Grunde einer Variablen vom Typ Word oder Integer. Denn manche dieser Register ermöglichen es, mathematische Berechnungen durchzuführen, andere jedoch dienen wiederum einfach nur zum reibungslosen Ablauf des Maschinenprogrammes. Die Register werden natürlich durch ihre Funktionsweise unterschieden.


Die allgemeinen Register:

Die allgemeinen Register werden für die eigentliche Verarbeitung der Daten genutzt. Mit ihnen werden die mathematischen Berechnungen (aber keine Kommazahlen), (Sprung-)Vergleiche, Schleifensteuerungen und Schreiben/Lesen des Arbeitsspeichers (vor allem in Einzelschritten) möglich. Die Namen dieser Register lauten AX, BX, CX und DX (bei der 32bit-Version wird einfach ein E für Extended vorangestellt, z. B. EAX). Wobei die Register AX und DX am häufigsten gebraucht werden. All diese vier Register sind gleich groß und gleichartig aufgebaut. 

Das Register EAX ist ein 32 bit Register, dessen Loword AX als 16bit-Teil angesprochen werden kann. Dieser 16bit-Teil wird nochmals in zwei 8bit-Teile in Lo- und Hibyte (AL, AH) unterteilt. 

.

EAX 32bit

.

AX

16bit

AH 8bit

AL 8bit

       
.

EBX 32bit

.

BX 16bit

BH 8bit

BL 8bit

.

ECX 32bit

.

CX 16bit

CH 8bit

CL 8bit

.

EDX 32bit

.

DX 16bit

DH 8bit

DL 8bit

       
Das heißt das EAX, AX, AH und AL keine verschiedenen Register sind, sondern daß sie jeweils nur einen bestimmten Teil von dem gleichen Register ansprechen. EAX betrifft das komplette 4 byte große Register, AX betrifft den rechten 2 byte großen Teil und die beiden byte großen AH und AL sind die kleinst ansprechbare Registerteile. Auch die anderen Register EBX, ECX und EDX besitzen diese Unterteilung. 

Mit diesen Registern kann im Grunde alles gemacht werden (Vergleiche, Berechnungen usw.), ohne auf irgendwelche Vorsichtsmaßnahmen oder mögliche Computerabstürze zu achten. Jedoch wird das Register ECX als Zähler bei vielen Befehlen verwendet und sollte deshalb auch nur für diesen Zweck verwendet werden. 

Die folgende Tabelle zeigt uns, daß nur die Datentypen und die Register zusammen passen, die auch die gleiche Bytegröße besitzen. 

Datentyp

Typgröße in Bytes

dazugehöriges Register

Registergröße in Bytes

ShortInt/Byte 1 oo AL, AH, BL, BH, CL, CH, DL, DH

1

Integer/Word 2 oo AX, BX, CX, DX, DS, SI, ES, DI

2

Long Integer 4 oooo EAX, EBX, ECX, EDX, ESI, EDI

4

Pointer 4 oooo Registerpaare DS:SI oder ES:DI

Registerpaar jeweils 4 Bytes

Nach der harten Theorie nun die Praxis. Anders als in den Hochsprachen muß in Assembler jede Aufgabe Schritt für Schritt zerlegt werden. So muß auch der Compiler von Pascal bei der Umwandlung von Hochsprache in Maschinensprache alle Befehle, mathematischen Berechnungen usw. analysieren und die verschiedenen Operatoren in die entsprechende Maschinenbefehle übersetzen.

Leider ist in Borland Pascal 7.0 das Ansprechen der 32-bit-Register (EAX, EBX, ECX, EDX, ESI usw.) nicht möglich. Der dazugehörige Protected Mode Handler (RTM.EXE, DPMI16BI.OVL) arbeitet im 16bit-Modus. Kurz und klar bedeutet dies, bei der späteren Grafikprogrammierung Schwierigkeiten mit Speicherbereichen, größer 64 KB. Bleibt zu hoffen, daß Sie ein glücklicher Besitzer eines 32-bit-Compilers sind, bei dem der 32-bit-Protected-Mode genutzt wird (z. B. Watcom V10.6 C/C++), dann können Sie einfach und ohne Probleme auch die größten Speicheranforderungen bewältigen. 

Die mathematischen Ausdrücke müssen in ihre einzelnen Bestandteile zerlegt werden und so Operation für Operation nacheinander abgearbeitet werden. So müssen bei dem Ausdruck

" I:=X*Y+100; " die Befehle (die Wertzuweisung, die Multiplikation und die Addition) nacheinander entsprechend ihrer mathematischen Rangfolge (z. B. Punkt vor Strich) programmiert werden.

Ein Struktogramm zeigt nun deutlich die Vorgehensweise in diesem Beispiel:

Allgemeines Register = X
Allgemeines Register = Allgemeines Register * Y
Allgemeines Register = Allgemeines Register + 100
I = Allgemeines Register

Einige andere Beispiele zeigen die Programmierung bei einfachen mathematischen Berechnungen.
Pascal:

I: definiert als Word
I:=I+1;

Die Variable I wird um 1 erhöht. In Assembler zieht es ein bißchen anders aus.
Zuerst muß das Register AX den Inhalt der Speicheradresse I erhalten.
MOV AX, I

Jetzt muß nur noch die eigentliche Addition durchgeführt werden:
ADD AX, 1

und der Registerinhalt an die Adresse von I zurückgeschrieben werden:
MOV I, AX

Natürlich könnten Sie statt AX auch BX, CX oder DX verwenden. Weitere Beispiele:
I:=I-1;

Code Erklärung
MOV AX, I ; AX = I
SUB AX, 1 ; AX = AX -1
MOV I, AX ; I = AX
I:=I*1;

Code Erklärung
MOV AX, I ; AX = I
MOV BX, 1 ; BX = 1
MUL BX ; AX = AX * BX
MOV I,AX ; I = AX
I:=I DIV 1;

Code Erklärung
MOV AX, I ; I = AX
MOV BX, 1 ; BX = 1
DIV BX ; AX = AX / BX
MOV I, AX ; I = AX

Zu beachten sind bei Multiplikation/Division die Unterscheidung zwischen vorzeichenlosen/-behafteten Variablen/Speicherplätzen. Bei signed Variablen lauten die entsprechenden Befehle IMUL bzw. IDIV. Dadurch kann der Prozessor erst Integer (also signed) und Word (unsigned) unterscheiden. Außerdem kann es bei der Multiplikation zu einem Overflow (zu deutsch: Überlauf) kommen. Das ist eine Situation, die eintritt, wenn das Ergebnis größer als die Registerbreite (in diesem Fall 65535) ist. Dann wird zu dem Register AX noch das Register DX zur Speicherung des Ergebnisses verwendet (der Prozessor macht dies automatisch). Sie müßten also das Loword einer Longint-Variable (untere 2 Bytes) mit dem Inhalt des Register AX füllen und den Rest, das Hiword (die anderen 2 Bytes) mit dem Inhalt des Register DX überschreiben um das komplette Ergebnis einer Multiplikation von Integer-Variablen in einer Long Integer Variablen zu sichern.


Die Segmentregister:

Wo befindet sich der aktuelle Programmcode (CS), die aktuellen Programmdaten (DS), der Zwischenspeicher (SS) und das wahlfreie Segment (ES) im Arbeitsspeicher? All diese Fragen werden mit Hilfe der Segmentregister festgelegt. Doch vorsichtigt falsche Werte in den Registern führen unweigerlich zum Absturz. Zur Programmierung von Assemblerteilen innerhalb von Pascal sind nur DS, ES interessant. Die anderen werden automatisch vom Compiler eingestellt und brauchen uns deshalb nicht mehr zu interessieren. Hier sehen sie nun alle Segmentregister mit den dazugehörenden Offsetregistern.

Registerpaare:

DS 16bit

SI 16bit

Datensegment

Sourceindex

ES 16bit

DI 16bit

Extrasegment

Destinationindex

CS 16bit

IP 16bit

Codesegment

Instructionpointer

SS 16bit

SP 16bit

Stacksegment

Stackpointer

BP 16bit

Basepointer

Das Register CS bildet zusammen mit dem Register IP einen Zeiger auf den aktuellen Befehl. Nach jedem Befehl erhöht die Prozessor automatisch IP um die entsprechenden Bytes, um den nächsten Befehl abzuarbeiten. Dabei enthält CS den Wert, der das Segment bestimmt, und IP den Offsetwert. Auch DS (Segmentanteil) und SI (Offset) bilden zusammen einen Pointer, der auf die aktuelle "Variable" zeigt. Das BP Register dient zur vereinfachten Adressierung von Variablen wie z. B. Arrays. 

Registerpaar

Segment

Offset

Zweck

CS:IP

CS

IP

  Zeiger auf nächsten auszuführenden Befehl

DS:SI

DS

SI

  Zeiger auf aktuelle Variable

ES:DI

ES

DI

  Zeiger auf optionalen Datenbereich

SS:SP

SS

SP

  Zeiger auf Stackspitze

Besondere Beachtung sollte dem Register DS geschenkt werden, da vor jeder Veränderung der alte Inhalt gerettet und nach beenden des entsprechenden Programmteils wieder hergestellt werden muß (betrifft auch CS, ES braucht nicht gesichert werden), denn sonst entsteht sehr schnell eine Situation, wo man sagt der Computer ist abgestürzt. Mit anderen Worten, die CPU arbeitet z. B. ohne es zu wissen, ein anderes Programm mit den falschen Variablen ab. Wobei die falschen Variablenwerte schnell zu Schutzverletzungen, falscher Befehlsausführungen usw. führen. Dann ist auch der Weg zu speziellen Fehlerabfangroutinen (Aufruf aufgrund fehlerhafter Befehlsinterpretation) des Prozessors nicht mehr weit, was schließlich zu guter Letzt den Stillstand des Systems bedeutet. 

Stacksegment

 

Daten-

segment

Programm-

segment

 Der Stack: 

Die CPU-Register sind beim Zwischenspeichern von Ergebnissen/Werten schnell erschöpft. Deshalb benötigt man einen Zwischenspeicher, den Stack. Dort werden Parameter von Prozeduren/Funktionen, Funktionsrückgaben oder Registerinhalte gespeichert. Der Befehl PUSH AX (Stack schreiben) rettet den Inhalt (2 Bytes) von AX auf den Stack (SP wird um 2 erhöht). POP AX (Stack lesen) holt 2 Bytes vom Stack in das Register AX (SP wird um 2 vermindert). Das Auswahl des zu lesenden bzw. zu schreibenden Element erfolgt nach dem sogenannten LIFO-Prinzip (Last in/First out). Es wird also immer am Ende gelesen bzw. geschrieben. Außerdem sollte man folgendes beachten: Auch hier gibt es einen Overflow, der aber bei überlegter Programmierung nicht eintreffen wird. Der Stacküberlauf wird durch entsprechend große Reservierung (wird in der Regel vom Compiler eingestellt) nur selten erreicht. 

Stacksegment (SS)

wächst Richtung

absteigender Adressen

Stackoffset (SP)
 

Das Flagregister: 

Dieses Register speichert keine Zeiger oder Variablen, sondern einfach nur CPU-Zustände. Das Register ist 1 Byte groß, wobei jedes dieser 8 Bit für eine andere Aufgabe benutzt wird. Diese Register sind entscheidend bei wichtigen Operationen und können wenn nötig auch auf den Stack zwischengespeichert werden. 

O

D

I

S

Z

A

P

C

O = Overflow-Flag
D = Direction-Flag
I = Interrupt-Flag
S = Sign-Flag
Z = Zero-Flag
A = Auxiliary-Flag
P = Parity-Flag
C = Carry-Flag

Das Carry-und das Overflow-Flag werden beim Überlauf und beim Übertragen in einer anderes Register gesetzt. Z. B. bei der Multiplikation zweier Wordvariablen kann ein 16bit-Register zu klein sein. Z. B. bei 1000*2000 ist das Ergebnis schon größer als 65535, es kommt zum Overflow (zu deutsch: Überlauf). Die restlichen zu vielen Bits des Ergebnisses werden in ein anderes Register übertragen und das Carry-Flag (zu deutsch: Übertrag) wird gesetzt. Nur mit ihrer Hilfe kann das Programm auf solche Situationen reagieren und eventuelle Fehlerabfangroutinen ausführen lassen. 

Das Direction-Flag legt die Kopierrichtung von (auch großen) Speicherbereichen fest. Entweder wird nach jeder Kopieraktion das entsprechende Register inkrementiert oder dekrementiert. Standardmäßig ist das Bit gesetzt. Wenn man z. B. ein Bild auf dem Kopf darstellen will, muß lediglich die Kopierrichtung geändert werden (und dann kopiert werden) und schon steht das Bild verkehrt herum. 

Wenn das Interrupt-Flag gelöscht ist werden keine Interrupts von der CPU bearbeitet. Durch das Löschen kann man dann bei kritischen Operationen Unterbrechungen durch fremde Programme Unterbinden und in aller Ruhe seinen Programmteil ausführen lassen. Danach muß aber sofort das Flag wieder gesetzt werden. Standardmäßig ist selbstverständlich das Interrupt-Flag gesetzt. 

Das Carry-Flag hat im Zusammenhang mit dem Signed-, Zero- und Parity-Flag eine sehr wichtige Aufgabe. Nur durch den Vergleich mit diesen Flags kann die CPU bedingte Sprunganweisungen durchführen.


Speicherzugriff: 

Der Datentyp Pointer (zu deutsch: Zeiger) dient zur Markierung bzw. Reservierung großer Speicherbereiche im RAM. Er besteht aus zwei Bestandteilen, dem Segmentanteil und dem Offsetanteil. Der Offsetanteil gibt die relative Position zum Segmentanteil an, ist sozusagen die Unteradresse. Man kann also mit Hilfe der Formel Segmentanteil * 16 + Offsetanteil die physikalische Adresse berrechnen. Das Ergebnis interpretiert der Prozessor als 20bit Zahl, mit der die Speicherstelle im RAM identifiziert wird (entspricht dem Realmode). Z. B. ergibt der Segmentanteil A000h und der Offset 0 die Speicheradresse 655360. Die Adresse erhält man auch mit einem Segmentanteil von 963Ch und Offsetanteil von 9C40h (963Ch * 16 + 9C40h = 655360). Die Speicheradressen lassen sich also mit vielen Kombinationen aus Segment und Offset darstellen. Leider können im Realmode keine größeren zusammenhängende Speicherbereiche als 64 KB verwalten werden, was dagegen schon im 16bit-Protected Mode möglich ist. Doch gibt es auch dort das schwerwiegende Problem der Segmentierung. Sobald ein Speicherbereich größer 64 KB ist, müssen nämlich spezielle Routinen zur Auswahl des richtigen Segments implementiert werden. Mit anderen Worten bei einer Operation mit dem RAM müssen dann ständig die Segment - und Offsetregister aktualisiert werden, was einen erheblichen Programmier- und Zeitaufwand erfordert. 

bis
^ 1 MB

Segmentgrenzen


4. Segment
64 KB groß


3. Segment
64 KB groß


2. Segment
64 KB groß

Offset
min. = 0
max. = 65535


1. Segment
64 KB groß

 
Wenn man einen Pointer hat, der auf den Anfang eines reservierten Bereich von 128 KB zeigt, kann man nur durch das Inkrementieren bis zur Hälfte des Bereiches (65535. Stelle), die Bytes ansprechen. Denn das Offsetregister ist 16bit breit, und kann deshalb maximal eine Zahl von 65535 darstellen. Es käme bei einer weiteren Erhöhung zum Overflow. Die anderen Speicherstellen können aber erst durch eine Erhöhung des betreffenden Segmentregister um 4096 und durch die Anpassung des Offsetregisters erreicht werden. Im Grunde entspricht ein Pointer einer dynamischen Variable. Beide werden erst während der Laufzeit des Programms angelegt und je nach Bedarf erzeugt oder gelöscht.


Der Protected Mode: 

Ab dem 80286 PC beherrscht der Prozessor neben den gewöhnlichen Betriebsmodus (Realmode) einen neuen Modus, den Protected Mode. Das Betriebssystem und die Programme sind nun besser untereinander vor Überschreibungen geschützt und die Speichergrenze (max. adressierbare Speicherstelle) von nur 1 MB wird überwunden. Man kann jetzt bis zu 16 MB adressieren, da der Segmentanteil (im Protected Mode = Selektor) einer Adresse nun zusammen mit dem Offset eine 24bit großen Adressraum erlaubt. Der Protected Mode (zu deutsch: geschützter Modus) ermöglicht auch ein komplexes Verwaltungssystem. So zeigen die Werte in den Segmentregistern (enthält den Selektor) nicht mehr direkt auf die physikalische Adresse, sondern bestimmen den zuständigen Eintrag in einer Beschreibertabelle (descriptor table). Dort sind alle wichtigen Angaben über jedes Segment festgelegt. Die Zugriffsrechte (darf gelesen/geschrieben werden), die Größe des reservierten Speicherblockes und andere Eigenschaften (ist das Segment verschiebbar oder fixiert) und vor allem die eigentliche Basisadresse werden dort gespeichert. Das bedeutet bei irgendwelchen Schreib- oder Leseaktionen eines Programms, überprüft das Betriebssystem mit Hilfe der CPU, ob die betroffenen Arbeitsspeicherbereiche dem Programm gehören oder nicht. Bei Unzulässigkeit wird dann einfach der Übeltäter (z. B. ein fehlerhaftes Programm) terminiert. Natürlich entstehen durch solche Programme dann nur noch selten Systemabstürze (siehe Linux, OS/2 oder Windows NT). 

Jedoch bietet diese Art der Speicherverwaltung nicht nur Vorteile. Der Nachteil liegt in der zeitraubenden Speicherverwaltung, z. B. dauert das Laden eines Pointers erheblich länger als im Realmode. Doch hat sich der Protected Mode in allen Softwarebereichen durchgesetzt. Besonders die neuere 32bit-Protected-Mode-Version wird fast überall eingesetzt (auch bei den Computerspielen). Denn hier können sage und schreibe bis zu 4 GB physikalischer Speicher adressiert werden, was den Speicherhunger aller komplexer Anwendungen endlich zufriedenstellen kann. Natürlich braucht man dafür genug physikalischen RAM. Außerdem gibt es durch die 32bit-Adressierung keine Notwendigkeit für besondere Routinen zur Umgehung der Segmentgrenzen, denn es gibt sie, die 64 KB-Blockunterteilung, einfach nicht mehr. 

Linux, OS/2, Windows 95 und Windows NT sind mehrere moderne Betriebssysteme, die alle diese Vorteile ausnutzen (auch das preemtive Multitasking). Deshalb wird irgendwann MS DOS, geschrieben für den Realmode, und dessen Komplementär Windows 3.1 (benutzt 16bit-Protected-Mode, kooperatives Multitasking) vollständig von moderneren Betriebssystemen ersetzt werden. 

Hier eine kurze laienhafte Unterscheidung zwischen preemtives und kooperatives Multitasking. Beide erlauben Multitasking, wobei das preemtive kurz gesagt das "echte" bezeichnet. Dort bestimmt das Betriebssystem durch verschiedene Verfahren den Anteil eines jeden Programmes an der Prozessorzeit. Das Multitasking wird also aktiv durchgeführt und wird bei hoher CPU-Auslastung einfach erzwungen. Hingegen herrscht beim kooperativen, unechtem Multitasking, eine Desperado-Mentalität. Wer zuerst kommt malt zu erst. So ungefähr kann man das Verfahren der freiwilligen Zeitvergabe durch die Programme beschreiben. Das Betriebssystem fragt die Programme einzeln an und vergibt die von ihnen zugewiesene Zeiten an andere Programme.


Speicherverwaltung in Pascal: 

Wenn Sie einen größeren Speicherbereich z. B. für eine Bilddatei benötigen, brauchen Sie nur eine Variable vom Typ Pointer. Mit der Funktion GlobalAllocPtr ( Eigenschaften , Speicherbereichgröße ) reservieren Sie einen Speicherbereich beliebiger Größe. Die Rückgabewert ist ein Pointer, der auf Anfang des neu reservierten Speicherbereiches zeigt. Die Eigenschaften legen die genauere Behandlung des Speicherbereiches fest, man kann also mit GMEM_MOVEABLE verschiebbare und mit GMEM_FIXED feste Speicherblöcke reservieren. Bei verschiebbar wird dem Betriebssystem bei Bedarf erlaubt, die Basisadresse des Selektors zu ändern, was bei Speicherknappheit sehr vorteilhaft sein ist. GMEM_FIXED dagegen, reserviert einen festen Speicherblock, den das Betriebssystem nicht innerhalb des Speichers verschieben wird. 

Anders als im Real Mode kann hier sogar eine Speicherbereichsgröße über 64 KB reserviert werden. Der Protected Mode Handler reserviert dann einfach mehrere miteinander verbundene Segmente. Trotzdem kann wie bereits erwähnt nicht komplett auf einmal auf den Speicher zugegriffen werden. Aufgrund der Segmentgrenzen kann der großen Speicher nur mit ein paar Tricks genutzt werden. Jedoch erst später dazu mehr.

Hier wird z. B. ein verschiebbarer 1 Mio. Bytes großer Speicherbereich reserviert: 

VAR Zeiger_auf_Speicherbereich : POINTER; 

BEGIN
Zeiger_auf_Speicherbereich := GlobalAllocPtr (GMEM_MOVEABLE, 1000000);
END.


Wichtig sind noch die drei Funktionen Ptr ( Segment , Offset ) und Seg ( Pointer ) und Ofs ( Pointer ). Die Funktion Ptr ermittelt als Funktionsrückgabe einen Pointer, der durch das Segment und Offset bestimmt wird. Die Funktion wird z. B. beim Durchwandern von Speicherbereichen benötigt. Die beiden Funktionen Seg und Ofs ermitteln jeweils Segment und Offset eines Pointers und geben diese als Word zurück.

Die von Pascal zur Verfügung gestellten Befehle zum Kopieren Move ( Quellpointer , Zielpointer ) und zum konstantem Füllen Fillchar ( Pointer , Speicherbereichsgröße , Füllbyte ) sind für die Grafikprogrammierung wenig geeignet, da sie einfach zu langsam ausgeführt werden. Zum Vergleich: Bei der direkten Assemblerprogrammierung wird beides um einige mal schneller ausgeführt (Kopieren 4x und Füllen 2x so schnell). Denn hier nutzt man einfach die 4 Byte bzw. 2 Byte Schreibgröße und nicht wie Pascal lediglich die 1 Byte Größe.

Nach der Nutzung eines Speicherbereiches sollte dieser so schnell wie möglich dem System zurückgegeben werden, das erledigt die Funktion GlobalFreePtr ( Pointer ). Sie gibt einen reservierten Speicherbereich dem Betriebssystem zurück. Der Pointer ist nun ungültig und darf nicht ohne vorherige

Überschreibung des Pointerinhaltes (z. B. durch Ptr) benutzt werden. Achtung, ungültige Pointer führen zum Abbruch des Programms und oft auch zum Systemstillstand.



2. VGA/SVGA-Programmierung

Bei der Grafikprogrammierung ist vor allem ein Gesichtspunkt sehr wichtig: die Geschwindigkeit. Alle Grafikoperationen sind sehr aufwendig und verlangen sehr viel Prozessorzeit, um die Schnelligkeit von vornherein durch so wenig Maschinenbefehle wie möglich zu erreichen, müssen die entsprechenden Programmteile deshalb in Assemblersprache implementiert werden. 

Die Grafikkarte Ihres PCs kennt verschiedene Bildschirmmodi, in denen die Anzahl der Pixel und Farben unterschiedlich sind. Der Standardbildschirmmodus mit einer Bildbreite von 320 Pixeln und einer Bildhöhe von 200 Pixeln (gekennzeichnet mit der Nr. 13H) war der erste mit 256 Farben. Hier entspricht jeder Pixel einem Byte im Arbeitsspeicher Ihres PCs. Dadurch ergeben sich auch die 256 Farben, denn 28 entspricht einem Byte, welches 256 verschiedene Zustände darstellen kann. Dieser Modus ist so einfach aufgebaut (jeder Pixel belegt ein Byte), daß die Programmierung enorm erleichtert wird. 

Zur Verständnis der späteren Programmteile ist es sehr wichtig, daß Sie den Aufbau der Bildspeicherdaten im RAM verstehen. Der Arbeitsspeicher ist in unterschiedliche Abschnitte, Segmente genannt, unterteilt. Das Segment A000H enthält die Bildspeicherdaten (diese entsprechen den Pixeln auf den Bildschirm). Man muß also nur noch die Bytes dort verändern und schon sieht man das Ergebnis auf dem Bildschirm. Das Koordinatensystem fängt links oben in der Ecke an. Dort ist der Punkt 0,0 (X,Y) angelegt. Eine bestimmte Adresse, Segmentanteil A000h und Offsetanteil 0, betrifft diesen Punkt. Wenn nun der Bytewert an dieser Position verändert wird, erscheint automatisch eine andere Farbe auf dem Bildschirm. Um nun jeden einzelnen Punkt zu erreichen, kann man die Adresse auch so formulieren: Segmentanteil ist A000h, Offsetanteil wird durch 320*Y+X berechnet. Möchten Sie z. B. dem Punkt ganz rechts unten in der Ecke eine andere Farbe geben, muß das Segmentregister mit A000h geladen und in das Offsetregister die Zahl 64000 (320*199+320) geschrieben werden. Anschließend muß der eigentliche Schreibbefehl programmiert werden und fertig ist die Farbänderung eines Punktes.



Adreßbeispiele: 

Adresse A000H:0
O O

0, 0 320,0
Adresse A000H:320
.
.
.

320, 200O

Adresse A000H:64000


Das ist doch gar nicht so schwer werden Sie sagen, aber wie werden die Farben eigentlich genau festgelegt? Die Farbvergabe erfolgt durch eine Tabelle, dem Palettenregister. Dort werden 256 verschiedene Tabelleneinträge mit jeweils einem roten, grünen und blauen Farbanteil (RGB-Farben) gespeichert. Jeder Farbanteil ist ein Byte groß, was einen Wertebereich jeweils für Rot, Grün und Blau von 0 bis 255 ermöglicht. Die Bytewerte beziehen sich also auf die einzelnen Farbeinträge dieser Palette und ergeben erst zusammen die vielen bunten Farben auf dem Bildschirm. 

Enthält der 0. Farbeintrag (gezählt wird von 0 bis 255) einen Rotanteil von 255, einen Grünanteil von 0 und einen Blauanteil von 0, definiert dieser eine sehr helle rote Farbe. Schreiben Sie nun an der Position 0,0 den Bytewert 0, erscheint automatisch ein Pixel mit einer sehr hellen roten Farbe. Die einzelnen Bytes der Bildspeicherdaten verweisen also auf die Paletteneinträge, in denen die RGB-Farben definiert werden.

Man kann natürlich die Farbregister mit beliebigen Rot-,Grün- und Blauwerten laden, so daß aus einem Farbtopf von 262.144 Farben (ergibt sich aus den RGB-Bytes=218 jeweils 26 für jeden Farbanteil), 256 Farben gleichzeitig dargestellt werden können. Gegenüber dem Vorgänger, der EGA-Karte war dies ein erheblicher Leistungssprung hinsichtlich der Programmierung und Darstellung von Grafiken. Auch deswegen verhalf die Einführung der VGA-Karte den PC-Spielen zu einem starken Höhenflug. 


Grafikmodus einschalten

Nach Theorie kommen wir nun zur Praxis. Man muß natürlich zuerst den richtigen Grafikmodus einschalten. Erreichen kann man dies durch einen bestimmten BIOS-Aufruf. Das BIOS stellt eine unabhängige Hardware-Schnittstelle dar und wird deshalb der zweiten Möglichkeit, der direkten Hardware-Programmierung, fast immer vorgezogen. Folgender Programmteil schaltet den Grafikmodus 13H ein: 

MOV AH, 00H Legt die BIOS-Funktion "Bildschirmmodus einstellen" fest
MOV AL, 13H Nr. des einzustellenden Grafikmodus
INT 10H Einsatz des BIOS durch Ausführung des Interrupts 10H


Pixel lesen/schreiben

Nach der Ausführung der drei Programmzeilen sieht man erstmal gar nichts außer eine schwarzen Bildschirm, deshalb lassen wir einen Punkt an einer beliebiger Stelle auf dem Bildschirm erscheinen. Das erledigt eine kleine Prozedur:

Procedure Putpixel ( x , y : Integer ; Pixelfarbe : Byte ); Assembler;
asm
mov
ax,320 (* Bildbreite ist 320 Bytes *)
imul y (* Register AX mit Y multiplizieren *)
add ax,x (* X zu Register AX addieren *)
mov es,segA000 (* Bildspeichersegment auswählen *)
mov di,ax (* Ergebnis von AX als Offset angeben *)
mov al,Pixelfarbe (* Pixelfarbe im Teilregister AL speichern *)
stosb (* Pixel in den Arbeitsspeicher schreiben *)
End;


Beim Lesen eines Pixels sind nur ein paar Schritte mehr notwendig:

Function GetPixel ( X , Y : Integer ) : Byte; assembler;
asm
push
ds (* DS auf den Stack retten *)
mov
ax,320 (* Bildbreite ist 320 Bytes *)
imul y (* Register AX mit Y multiplizieren *)
add ax,x (* X zu Register AX addieren *)
mov ds,segA000 (* Bildspeichersegment auswählen *)
mov si,ax (* Ergebnis von AX als Offset angeben *)
lodsb (* Pixel aus dem RAM laden *)
pop ds (* geretteten Inhalt vom Stack in DS schreiben *)
push ax (* Pixelfarbe als Ergebnisrückgabe retten *)
end;


Linie zeichnen 

Um eine Linie zu zeichnen muß man die logische Linie auf das Pixelraster legen. Dabei muß man sich ein rechtwinkeliges Dreieck vorstellen, welches zwei Seiten als Höhe und Breite besitzt, wobei die gegenüberliegende Seite die eigentlich zu zeichnende Linie ist. Mit Hilfe der Division in Bezug zu der Höhe und der Breite kann man nun Pixel für Pixel der Linie zeichnen. 

Procedure Line ( x1 , y1 , x2 , y2 : integer ; PixelFARBE : Byte );
var
dx1 , dy1 , xa , ya : integer;
n , maxn : word;

begin
asm
mov
ax,x2 (*dx1:=x2-x1; Breite berechnen *)
sub ax,x1
mov dx1,ax
mov
ax,y2 (*dy1:=y2-y1; Höhe berechnen *)
sub ax,y1
mov dy1,ax
end
;

(* Breite oder Höhe größer? *)
maxn:=abs(dx1);
if maxn<abs(dy1) then maxn:=abs(dy1);
if maxn=0 then inc(maxn);
for n:=0 to maxn-1 do
begin
asm
mov
ax,dx1 (*xa:=x1+(dx1*n)div maxn;*)
imul n
idiv maxn
add ax,x1
mov xa,ax
mov
ax,dy1 (*ya:=y1+(dy1*n)div maxn;*)
imul n
idiv maxn
add ax,y1
mov ya,ax

(* Folgendes verhindert zeichnen über die Ränder *)
cmp xa,319
ja @verdeckt
cmp xa,0
jb @verdeckt
cmp ya,199
ja @verdeckt
cmp ya,0
jb @verdeckt
(* Offset berechnen *)
mov ax,320
imul ya
add ax,xa (*scroff:=ya*320+xa;*)
mov es,sega000 (* Bilddatensegment auswählen *)
mov di,ax (* richtigen Offset angeben *)
mov al,pixelFARBE (* zu zeichnende Farbe festlegen *)
stosb (* Pixel schreiben *)
@verdeckt:
end;
end;
end;

Rechteck zeichnen

Ähnlich funktioniert das Zeichnen eines gefüllten Rechteckes. Es wird aber nicht jeder Pixel vom Anfang bis zum Ende einer Zeile einzeln geschrieben, sondern jede Zeile komplett mit nur einem Befehl. Logischerweise erhöht sich dadurch die Ausführungsgeschwindigkeit beträchtlich.

PROCEDURE Bar ( X , Y , Breite , Hoehe : Integer ; Pixelfarbe : Byte );
VAR
Zeile : Integer;
Laenge : Integer;

BEGIN
Laenge : = Breite DIV 2; (* Schreiblänge ist die Hälfte, da mit 16-bit gefüllt wird *)
FOR Zeile : = Y TO Y + Hoehe DO (* von der ersten bis zur letzten Zeile wiederholen *)
BEGIN
ASM
mov
ax,320
imul Zeile
add ax,x (* Offsetberechnung *)
mov
di,ax (* Offset laden*)
mov al,Pixelfarbe (* Farbe in Unterregister AL laden *)
mov ah,al (* Farbe auch ins andere 8-bit-Unterregister laden *)
mov cx,laenge (* Länge der Schreibaktion festlegen *)
mov es,SegA000 (* Bildspeichersegment auswählen *)
rep stosw (* Zeile mit 16-bit-Schreibaktion füllen *)
END;
END;
END;


Bild/Grafik anzeigen

Das Laden eines Bildes/Grafik ist Gegensatz zu dem Vorhergehendem viel komplexer. Zuerst muß das gewünschte Bild, welches sich in einer Bilddatei befindet in den Arbeitsspeicher geladen werden. Dann müssen die Informationen der Bilddatei (Größe, Breite, Höhe etc.) ausgewertet und die Farbpalette neu eingestellt werden. Danach werden nur noch die eigentlichen Bilddaten in das Segment A000H an die richtige Stelle kopiert und es erscheint das Bild.

Hier wird zuerst einmal der Dateiheader einer Grafik-BMP-Datei definiert:

type
bmp_header_ = record
signatur : word;
flen : longint;
dummy1 : word;
dummy2 : word;
offset : longint;
info_size : longint;
xmax : longint;
ymax : longint;
polanes : word;
bits_per_pixel : word;
compress : word;
xsize : longint;
hdpi : longint;
vdpi : longint;
cols : longint;
coli : longint;
end;


var
bmp_header : bmp_header_; (* Dateiheader *)
pal_array : array[0..255,0..2]of byte; (* Palettenfarben mit Rot-, Grün- und Blauanteilen *)
pal_res : array[0..255]of byte; (* muß mitgeführt werden *)
helligkeit : integer; (* speichert die Helligkeit der Farben *)


Nach dem Laden der Headerinformationen kann nun die neue Farbpalette eingestellt werden:

procedure setpalette ( anzahl : integer );
var
merker,i : integer;
pl_array : array[0..255,0..2]of byte;

begin
merker : = helligkeit;
helligkeit : = helligkeit*2;
for i : = 0 to anzahl do
begin
pl_array[i,red] : = (pal_array[i,blue]*helligkeit div 255) shr 2;
pl_array[i,green] : = (pal_array[i,green]*helligkeit div 255) shr 2;
pl_array[i,blue] : = (pal_array[i,red]*helligkeit div 255) shr 2;
end;
port[$3C8] : = 0;
for i : = 0 to anzahl do
begin
port[$3C9] : = pl_array[i,red];
port[$3C9] : = pl_array[i,green];
port[$3C9] : = pl_array[i,blue];
end;
helligkeit : = merker;
end;

Folgender letzter Programmteil zeigt wie die Bilddatei geladen, die gezeigten Prozeduren genutzt und die Bilddaten kopiert werden.

Function LoadBMPFile(x , y: Integer; Filename: String):Boolean;
var
Handle:file;
Bildgroesse:longint;
i,it:integer;
ts2,to2,tsegmentBMP,toffsetBMP,tsegment,toffset:word;
groesse2:word;
breite,hoehe:integer;
 
(* ermittelt den richtigen Pointer abhängig vom Offset *)
function getptr(p:pointer;offset:longint):pointer;
type
long=record
lo,hi:word;
end;

begin
getptr:=ptr(long(p).hi+long(offset).hi*selectorinc,long(p).lo+long(offset).lo);
end

(* lädt die eigentlichen Bilddaten in den Arbeitsspeicher *)
Function loadfile:pointer;
var
buffer:pointer;
size,offset,count:longint;

begin
buffer:=nil;
size:=filesize(handle)-filepos(handle);
buffer:=globalallocptr(gmem_moveable,size);
if buffer<>nil then
begin
offset:=0;
while offset<size do
begin
count:=size-offset;
if count>32768 then count:=32768;
blockread(handle,getptr(buffer,offset)^,count);
inc(offset,count);
end;
end;
close(handle);
loadfile:=buffer;
end;

(* wird ganz am Schluß aufgerufen und zeigt die Grafik an *)
procedure putVGAPixel(x1,y1,breite,hoehe:integer;
tsegment,tsegmentBMP,toffsetBMP:word);
const bildbreite=320;

var c2,toffset:word;
zaehler,y,x2:integer;

begin
(* Korrektur der Breite auf ein vielfaches von 4 *)
if breite mod 4<>0 then breite:=breite+(4-(breite mod 4));
x2:=breite;
(* nach dem rechten Rand abschneiden *)
if 320-x1<=breite then x2:=320-x1;
zaehler:=x2 div 4;
for y:=y1 to y1+hoehe-1 do
begin
c2:=toffsetbmp+breite*(y-y1);
toffset:=bildbreite*y+x1;
asm
push
ds
mov
ds,tsegmentbmp (* Quelladresse-Segment *)
mov si,c2 (* Quelladresse-Offset *)
mov cx,zaehler
mov es,tsegment (* Zieladresse-Segment *)
mov di,toffset (* Zieladresse-Offset *)
db 66h;rep movsw (* 32-bit-Kopieraktion *)
pop ds
end
;
end;
end;

(* Programm der Hauptfunktion, hier werden der Dateiheader ausgewertet und die Unterfunktionen aufgerufen *)
begin
assign(handle,filename);
reset(handle,1);
blockread(handle,bmp_header,54);

if (lobyte(bmp_header.signatur)<>$42)Or(hibyte(bmp_header.signatur)<>$4D)then
begin
close(handle);
exit;
end;

for it:=1 to 256 do
begin
blockread(handle,pal_array[it-1,0],3);
blockread(handle,pal_res[it-1],1);
end;
filenamemerker:=filename;
bildgroesse:=bmp_header.xmax*bmp_header.ymax;
po:=loadfile;
tsegment:=fenstersegment;
tsegmentBMP:=seg(po^);
toffsetBMP:=ofs(po^);
setpalette(255);
breite:=bmp_header.xmax;
hoehe:=bmp_header.ymax;
if bildgroesse<=65535 then
begin
putVGAPixel(x,y,breite,hoehe,segA000,tsegmentBMP,toffsetBMP);
End;
globalfreeptr(po);
end;


 

3. Wichtige Assembler-Befehle

wichtig

Befehl

Operanden, -größe

max. Taktzyklen 486

Beschreibung

x

ADD

AL, AX

1

Addition
 

AND

AL, AX

3

Register, Variable mit Register logisch verknüpfen
 

CLC

-

2

Carry-Flag löschen
 

CLD

-

2

Direction-Flag löschen
 

CLI

-

5

Interrupt-Flag löschen

x

CMP

AL, AX

1

Register, Variable mit Register vergleichen

x

DEC

AL, AX

3

Register, Variable um eins vermindern

x

DIV

AL, AX

16

Register durch Register, Variable dividieren (unsigned)

x

IDIV

AL, AX

20

Register durch Register, Variable dividieren (signed)

x

IMUL

AL, AX

26

Register mit Register, Variable multiplizieren (signed)
 

IN

AL, AX

14

Port (def. d. Variable oder Register) in Register einlesen

x

INC

AL, AX

3

Register, Variable um eins erhöhen

x

INT

Byte

unterschiedlich

Aufruf eines Interrupt-Handlers

Jcc

siehe Sprungtabelle

3

Programmverzweigung (abhängig von einer Bedingung) an angegebene Adresse

JMP

-

3

Programmverzweigung (entspricht GOTO) an

angegebene Adresse

x

LDS

SI

12

Laden eines Pointers (4 Byte) ins Registerpaar DS:SI

x

LES

DI

12

Laden eines Pointers (4 Byte) ins Registerpaar ES:DI

x

LODSB

AL, AX

5

Variable von DS:SI ins Register laden

x

LOOP

CX

9

Schleifensteuerung über CX als Zähler

x

MOV

AL, AH, AX

1 bis 9

Kopieren von Register, Variable nach Register,Variable (MOV ZIEL, QUELLE)

x

MOVSB

Byte, 2 Byte

7

Kopiert Speicherinhalt von DS:SI nach ES:DI

x

MUL

AL, AX

26

Register mit Register, Variable multiplizieren (unsigned)
 

NOP

-

1

keine Operation/Auswirkungen, IP wird um eins erhöht
 

NOT

AL, AX

3

jedes Bit wird umgekehrt, 1er werden 0er, 0er werden 1er
 

OR

AL, AX

3

Register, Variable mit Register logisch verknüpfen
 

OUT

AL, AX

16

Registerinhalt an Port (def. d. Variable oder Register)

x

POP

2 Byte

6

Registerinhalt wird durch 2 Bytes vom Stack ersetzt,SP wird um 2 erhöht

x

PUSH

2 Byte

4

Registerinhalt wird auf den Stack gerettet,SP wird um 2 vermindert

x

REP

-

unterschiedlich

wiederholt Kopieraktion, CX bestimmt die Anzahl
 

STC

-

2

Carry-Flag setzen
 

STD

-

2

Direction-Flag setzen
 

STI

-

5

Interrupt-Flag setzen

x

STOSB

AL, AX

5

Registerinhalt bei ES:DI speichern

x

SUB

-

3

Register mit Register, Variable subtrahieren
 

XOR

-

3

Register, Variable mit Register logisch verknüpfen


4. Wichtige CPU-Register (x86-kompatibel)

. EAX 32bit . . DS 16bit . SI 16bit .
    AX 16bit . Datensegment   Sourceindex  
AH 8bit AL 8bit
ES 16bit . DI 16bit .
. EBX 32bit . . Extrasegment Destinationindex
BX 16bit .
BH 8bit BL 8bit CS 16bit . IP 16bit .
Codesegment Instructionpointer
. ECX 32bit . .
CX 16bit . SS 16bit . SP 16bit .
CH 8bit CL 8bit Stacksegment Stackpointer
. EDX 32bit . . BP 16bit .
    DX 16bit . Basepointer  
DH 8bit DL 8bit

5. Wichtige Übersichten

Datentyp

Typgröße in Bytes

Register

Registergröße in Bytes

ShortInt/Byte

1

Al, AH, BL, BH, CL, CH, DL, DH

1

Integer/Word

2

AX, BX, CX, DX, DS, SI, ES, DI

2

Long Integer

4

EAX, EBX, ECX, EDX, EDS, ESI, EES, EDI

4

Pointer

4

Registerpaare DS:SI oder ES:DI Registerpaar

jeweils 4 Bytes


Registerpaar

Segment

Offset

Zweck

CS:IP CS IP Zeiger auf nächsten auszuführenden Befehl
DS:SI DS SI Zeiger auf aktuelle Variable
ES:DI ES DI Zeiger auf optionalen Datenbereich
SS:SP SS SP Zeiger auf Stackspitze



Flags-Register - Bitbedeutung

O

D

I

S

Z

A

P

C

  O = Overflow-Flag
                  D = Direction-Flag
                  I = Interrupt-Flag
                  S = Sign-Flag
                  Z = Zero-Flag
                  A = Auxiliary-Flag
                  P = Parity-Flag
                  C = Carry-Flag


Datentypen - Wertebereiche

Bezeichnung Anzahl der Byte Anzahl der Bit Wertebereich signed Wertebereich unsigned verschied. Zustände
ShortInt/Byte

1

8

-128 bis 127

0 bis 255

256

Integer/Word

2

16

-32768 bis 32767

0 bis 65535

65536

Long Integer

4

32

231 bis 231-1

0 bis 232-1

232

Pointer

4

32



Datentypen - Speicherung der einzelnen Bytes/Bits

Bits

8

8

8

8

   
Datentypen
    Lo-Byte Hi-Byte   Word insg. 16bit
  Lo-Word   Hi-Word   Long Integer insg. 32 bit


(*
Referat: CPU/Assembler 1997
Autor: Bernd Noetscher
-----------------------------------------------------------
Geschwindigkeitsvergleich von Maschinensprachen (Assembler)
und Hochsprachen
*)

VAR
REPEATER, X, Y : WORD;

BEGIN
ASM

mov ax,13H
int 10h (* VGA einschalten: 320*200 Pixel *)

@REPEATER_SCHLEIFE:
@Y_SCHLEIFE:
@X_SCHLEIFE:

mov ax,0a000h (* AX=0a000H *)
mov es,ax (* ES=AX *)
mov ax,320 (* AX=320 *)
mul y (* AX=AX*Y *)
add ax,x (* AX=AX+X *)
mov di,ax (* DI=AX *)
mov ax,y (* AX=X *)
add ax,repeater (* AX=AX+REPEATER *)
stosb (* Inhalt von AL nach ES:DI *)

inc x (* X=X+1 *)
cmp x,319 (* X kleiner 319 ? *)
JBE @X_SCHLEIFE (* ja, dann gehe zu X_SCHLEIFE *)

mov x,0 (* X=0 *)
inc y (* Y=Y+1 *)
cmp y,199 (* Y kleiner 199 ? *)
JBE @Y_SCHLEIFE (* ja, dann gehe zu Y_SCHLEIFE *)

mov x,0 (* X=0 *)
mov y,0 (* Y=0 *)

inc repeater (* REPEATER=REPEATER+1 *)
cmp repeater,100 (* repeater kleiner 100 ? *)
JBE @REPEATER_SCHLEIFE (* ja, dann gehe zu REPEATER_SCHLEIFE *)

mov ax,03H
int 10h (* Textmodus einschalten: 80*25 Zeichen *)

END;



END.

<< Zurück Alle Angaben ohne Gewähr. Benutzung auf eigenes Risiko.
(C) Copyright Bernd Noetscher 1995 - 2000
eMail: webmaster@berndnoetscher.de