zatzen hat geschrieben:Jetzt würde ich gern noch die Mischroutinen mittels FS und GS optimieren.
Das Lesen mittels db 64h; mov ax, ds:[si+bx] funktioniert offenbar, allerdings
habe ich Probleme, den Segmentregistern FS und GS einen Wert zuzuordnen.
db 64h; lds si, ... funktioniert nicht. db 64h; mov ax, ds oder umgekehrt auch nicht...
Auch über den Stack geht's nicht.
Ja, Zuweisung an Segmentregister (egal welche) gehen über spezielle Befehle. Nicht alles, was FS/GS betrifft, kann
mit db $64 bzw db $65 gemacht werden - diese Präfixbytes ändern nur das Segment bei Addressierung.
Für Laden von SEGMENT-REGISTER in normales Register/Speicher oder von normalem Register/Speicher in Segmentregister gibt es z.B. spezielle Befehle:
MOV Speicher/Register,SEGMENTREG. = db $8C,ModR/M
MOV SEGMENTREG.,Speicher/Register = db $8E,ModR/M
Das Mod-Reg-R/M Byte muß man in dem Fall natürlich selbst zusammenbasteln, so wie gehabt, aus den 3 Teilen:
Die mittleren 3 Bits (3,4,5) bestimmen in dem Fall das Segmentregister (ES,CS,SS,DS,FS,GS,?,?) (die Kombi 6 und 7 sind illegal). Die oberen 2 Bits bestimmen, wie die unteren 3 Bits gelesen werden:
%11 = Register (also AX,CX,DX,BX,SP,BP,SI,DI)
und wenn Mod = %00, %01 oder %10, dann geben sie verschiedene indizierte Adressierungsarten an, und bei %00 ohne Displacement, bei %01 mit 8-Bit-Displacement und bei %10 bit 16-Bit-Displacement - naja und so weiter. Kennst Du sicher selbst.
Dabei muß man natürlich beachten, daß im Falle eines Displacements dieses auch angegeben werden muß!
D.h. wenn Mod = %01 oder %10 ist, folgen natürlich noch ein oder zwei Byte, die den Additionswert, also dem Offset angeben.) Achja. Besonderheit ist, daß BP intern nicht einzeln als Index stehen kann, den Fall hat man für einzelnen 16bit-Offset (ohne Indexregister) vorgesehen. Also ist bei Mod = %00 und R/M = %110 nicht [BP], sondern nur der 16bit-Offset gemeint. Wenn man also im Assembler mov AX,[BP] benutzt, wird daraus intern mov AX,[BP+0] gemacht.
Somit ist es eigentlich keine gute Idee, allein BP als Index zu benutzen, weil ja immer mit Offset (wenn auch Null) gearbeitet wird. Andererseits gehen einem irgendwann die Register aus...
Aber OK. Wenn man gerade damit anfängt, FS und GS Werte zuweisen zu wollen, wäre am einfachsten in dem Fall der Weg über ein Register, also sowas wie
mov FS,AX - das entspräche dann db $8E,$E0
Dieses $E0 setzt sich dann bitweise so zusammen 11 100 000
11 = untere 3 Bit geben ein "normales" Register
100 = Segmentregister ist FS (also Nummer 4 in der Reihe, von 0 an gezählt)
000 = Das "normale" Register ist AX (also Nummer 0 in der Reihe).
(Zu beachten ist, daß $8C bzw $8E in dem Fall festlegt, ob es mov AX,FS oder mov FS,AX ist - in beiden Fällen ist das Mod-Reg-R/M Byte das gleiche!)
Man weist also vorher z.B. an AX das zu, was in FS stehen soll und danach führt man db $8E,$E0 aus.
Und, falls diese Frage kommt: Nein, es gibt leider keinen x86-Befehl für direkte Zuweisung von einem an ein anderes Segmentregister - hier muß immer der Umweg über ein anderes Register (oder über Speicher oder Stack) gegangen werden.
Für PUSH Segmentregister und POP Segmentregister gibt es IMMER spezielle Befehle, die direkt 1 Byte groß sind. Für FS und GS sind es 2 Byte (mit dem $0F Präfix) - hier war bei den 1-Byte-Befehlen kein Platz mehr, daher wurden diese in die zweite Befehlstabelle (eben mit dem $0F-Befehlspräfix) aufgenommen. FS und GS sind ja bekanntlich später "nachgerüstet" worden:
push ES = $06
push CS = $0E
push SS = $16
push DS = $1E
push FS = $0F,$A0
push GS = $0F,$A8
Man merkt schon, daß das Ganze einer gewissen "Gesetzmäßigkeit" folgt (außer bei FS und GS).
POP kann man sich leicht merken - einfach jeweils das untere Bit des Befehls setzen:
pop ES = $07
[pop CS = $0F] GIBT ES NICHT! $0F wurde daher später als Befehlspräfix für die zweite Befehlstabelle benutzt.
pop SS = $17
pop DS = $1F
pop FS = $0F,$A1
pop GS = $0F,$A9
(Daß pop CS keinen Sinn macht, dürfte einleuchten, das brauche ich wohl nicht zu erklären...)
Ich kann Dir gerne mal die ASM64FAQ.TXT zusenden,
dann noch ein cooles zeilenweise Textfile, das die Befehle nacheinander enthält, danach die Bytes für die Mod-Reg-R/M, dann für S-I-B (Scale-Index-Base), falls Du mal mit dem db $67-Präfix arbeiten willst (32-Bit-Adressierung...)
Das Ganze gibts aber auch nochmal als Textfile, wo es besser erklärt ist.
Dann hab ich auch noch eine (mit Rähmchengrafik gebaute) Tabelle, wo die Befehle tabellarisch in einer "Matrix" gelistet sind - ideal zum Ausdrucken.
Das ganze Zeug habe ich nicht selbst erstellt - nur über die Jahre so gesammelt.
Achja: Die ASM86FAQ.TXT kann man ja auch von meiner Seite herunterladen.
Und wenn man will, auch mein FAQREAD.EXE, was das Suchen in dem (ca. 1 MB großen) Textfile erheblich erleichtert. (Man kann dann aus Pulldowns die Kategorien und Subkategorien suchen. Es speichert auch Einstellungen ab. Und die Such-Eingabe ist recht einfach gelöst.
Einfach Buchstaben/Zahlentasten drücken und das Such-Eingabefeld geht auf. Wenn man beim ersten Zeichen zusätzlich Shift drückt (z.B. Shift+A), wird das Suchfeld gleich geöffnet, mit | A, wobei | dann ASCII 179 ist, also senkrechte Rähmchenlinie. Damit kann man dann einfacher gleich zu Assemblerbefehlen springen, weil die in so Kästchen stehen.
Selbst heute noch benutze ich gern und öfters diese Nachschlagewerke - ich habe schließlich nicht alle Befehle und Bytes ständig im Kopf.
Achja, diese "komplexeren" Befehle, sowas wie
LFS DI,Adresse
Könnt ich auch noch kurz erklären:
LFS Register,Adresse = $0F,$B4,Mod-Reg-R/M
LGS Register,Adresse = $0F,$B5,Mod-Reg-R/M
Das "Reg"-Feld (Bits 3,4,5) enthält das Register (z.B. wie oben DI wäre %111), Adresse würde sich wieder aus dem Mod und den R/M Feldern zusammensetzen. Ich weiß gerade nicht, ob Mod = %11 hier überhaupt Sinn macht, bzw. was dann herauskommt - vielleicht auch nur direkter Offset - ist ja auch egal.
Achso, WICHTIG! Immer, wenn man natürlich das Mod-Reg-R/M Byte benutzt und NICHT Register, also (indizierter) Speicheroffset, sollte man natürlich auch hier beachten, daß dieser OFFSET entsprechend dem angegebenen SEGMENT gilt!
D.h. normalerweise ist das DS. (
Wieder beachten! Jede Indizierung, die irgendwo BP enthält, macht SS zum Standardsegment!) Will man also z.B. eine Adresse im Code-Bereich (dem Code-Segment) benutzen, muß hier natürlich das Segment-Präfix für CS: angegeben werden!
Segment Präfixe sind:
ES: = $26
CS: = $2E
SS: = $36
DS: = $3E
FS: = $64
GS: = $65
Auch hier sieht man wieder: Diese entsprechen (bis auf die "nachgerüsteten" FS und GS) entsprechenden "Regeln" - es ist wie die PUSH-Befehle + $20 (also mit gesetztem Bit 5).
Kurze Erklärung zwischendurch - falls kein Interesse, einfach überlesen bis zur nächsten Linie:
Daß der ganze x86 Assembler teilweise geordnet und teilweise wie Chaos aussieht, liegt daran, daß zu Anfang (4040, 8080, 8086) CPUs noch "festverdrahtet" waren und man die Befehle so angeordnet hat, wie es am einfachsten zu "verdrahten" war. So entstanden auch "Lücken", also Bytes, wo die Kombination keinen Sinn machte - (so erklären sich z.B. die "illegalen" Befehle beim C64-Chip (MOS 6502 bzw MOS 6510), die dann auch eine gewisse Funktionalität haben, die sich quasi "von allein ergeben" hat). Beim x86 gelten solche "Stellen" bis auf weiteres als undefinierte Befehle und erzeugen einen INT $06 (illegaler Opcode). "Bis auf weiteres" bedeutet hier: Bis es eine neuere CPU-Generation gibt.
Bei intel hatte man schnell erkannt, daß Abwärtskompatibilität der CPU eine gute Idee und verkaufsfördernd für Hardware war, da man nicht immer komplett neue Software kaufen mußte, wenn man seinen Computer aufrüstete. Also behielt man die existierenden Befehle bei und füllte in nachfolgenden CPU-Generationen nach und nach die "Lücken" mit brauchbaren neuen Befehlen. Inzwischen sind CPUs intern nicht mehr "festverdrahtet", sondern die Zuordnung der Befehle erfolgt über interne Tabellen - somit könnte jeder Befehl an jeder Stelle stehen. Aus Gründen der Abwärtskompatibilität hat man aber bei den alten Befehlen ihre Stelle weiter beibehalten.
Mit dem 32bit-Mode (Protected-Mode) und später mit dem 64bit-Mode gab es zwei "Brüche" zur Abwärtskompatibilität.
Der PM ist immerhin noch in der Lage, auch 16bit-Programme auszuführen, da quasi der RM (16bit)-Mode (der ursprüngliche) als Sub-Mode des PM konstruiert wurde. Er funktioniert ab 386er (eigentlich ab 286er) wie der PM mit abgeschalteten/auf Standard gesetzten Features.
Der 64bit-Mode macht das gleiche für den 32bit-Mode, grenzt dabei aber den 16bit-Mode aus. D.h. im 64bit-Mode können auch 32bit-Programme ausgeführt werden, aber keine 16bit-Programme mehr. Aus Gründen der Steigerung der Performance (oder meiner Meinung nach der Steigerung der Verkaufszahlen neuerer Betriebssysteme, die dann auch neuere Rechner brauchen) hat man im 64bit-Mode die Abwärtskompatibilität zu 16bit weggelassen. (Technisch wäre weiterer Support von 16bit meiner Meinung nach möglich gewesen - in CPUs, die selbst schon 3D Grafik berechnen, sollte die "Emulation" eines 386ers keine Schwierigkeit darstellen...)
Dieses "Auffüllen" (Nutzen vorher ungenutzter Opcode-Bytes) durch neue Befehle macht also das teilweise "Chaos" aus. Will sagen: In manchen bereichen, wie dem Mod-Reg-R/M Byte bei den "MOV Register,Register" Befehlen, konnte man einfach die freien Bits noch weiter nutzen. Statt 4 gab es nun 6 Segmentregister. Weil aber im Mod-Reg-R/M Byte an der Stelle ohnehin 3 Bits vorgesehen sind, konnte man nun einfach weiternumerieren, und FS und GS die Nummern 4 und 5 geben.
Bei den PUSH/POP/LxS Befehlen oder den Segmentpräfixen war aber alles "nachfolgende" schon belegt gewesen, bzw für gar nicht mehr als 4 verschiedene Kombinationen vorgesehen gewesen. Hier mußte dann "getrickst" werden.
------------------------------------------------------------------------------------------------------------------------------------
So ist es ja auch quasi leicht ersichtlich, daß hier ab 386er diese schöne Lücke von $64 bis $67 einfach vollständig "aufgefüllt" wurde, allesamt mit Präfix-Bytes:
$64 = FS:
$65 = GS:
$66 = Präfix für 32bit-Rechenbreite (Registerbreite)
$67 = Präfix für 32bit-Adreßbreite (4 GB Adressierung) - ermöglicht übrigens das S-I-B Byte zusätzlich zum Mod-Reg-R/M Byte. Außerdem kann dann hier jedes Register als Indexregister benutzt werden und bei Kombination kann dann auch automatisch eins der beteiligten Register *1, *2, *4 oder *8 gerechnet werden.
Und ja - das ist auch im 16bit (Real-Mode) möglich - ABER VORSICHT! Die Register werden hier alle 32bit gelesen und es wird NICHT wrapraoundet! Wenn man aber nicht den Flat-4G-Trick angewendet hat und der Offset ergibt zusammengenommen einen Wert größer als 65535 (also $FFFF), dann gibt's einen Absturz (selbst getestet und danach auch nachgelesen, daß es so ist). D.h. hier werden NICHT die oberen 16bit ausgeblendet, wie es normalerweise der Fall ist (wie erwähnt: der RM ist seit 286er ein Sub-Mode des PM). D.h. um das zu nutzen, müssen die oberen 16bit der an der Adressierung beteiligten Register immer auf $0000 gesetzt sein - oder genauer gesagt: Das Gesamtergebnis darf keinen Offset über 65535 ergeben.
An sich fände ich das ja praktisch, vor allem wegen der automatischen Skalierung. Aber dafür dann bei den Registern die oberen 16bit nicht zu nutzen, oder immer vorher irgendwohin retten zu müssen - das fand ich dann unpraktisch.
Aber vielleicht werde ich da "später" nochmal ganz andere Sachen machen. Für einen neuen C64-Emulator (mein alter ist schon etwas angestaubt) wäre das - vor allem für die Emulation der Grafikmodes - sehr praktisch.
Hier noch, falls Interesse, ein kleiner Exkurs meinerseits zum Thema:
Wie hält man bei größeren Projekten überhaupt "Ordnung" in seinem Assembler-Code?
OK, ich sage mal, wie ICH es mache:
Ich mache vorher einen Plan.
(Ja, Planwirtschaft in der DDR war Mist, weil ein 5-Jahres-Plan davon ausgeht, daß sich Bedarf, Bedürfnisse, Gesellschaft, Entwicklung usw in den nächsten 5 Jahren nicht - oder nur planbar - ändern werden. So war das ganze Denken der Obrigkeit der DDR ja ausgelegt: Veränderungen existieren nicht. Außer, wenn von oben befohlen.)
Beim Programmieren sind Pläne aber gut.
Dieser Plan sieht vor allem vor, welche Register ich (hauptsächlich) für welchen Zweck einsetze, das betrifft sowohl die "Standardregister", als auch die Segmentregister. Warum? Naja - in Hochsprachen definiert man einfach neue Variablen, wenn man welche braucht - die Grenze ist nur der RAM. In Assembler kann man zwar auch alles in den Speicher legen, aber man merkt natürlich schnell, daß Register praktischer sind und die Abarbeitung schneller. Aber natürlich ist die Anzahl Register begrenzt und man kann nicht einfach neue "dazudefinieren".
Bei der Programmierung könnte man natürlich auch "immer ein Register nehmen, was gerade frei ist". Bei kleineren Subroutinen von Hochsprachen ist das auch weitestgehend egal.
Bei größeren Projekten kann man sich dabei aber manchmal etwas verzetteln - das führt dann dazu, daß man ständig aufwendig Register sichern muß (zeitfressende PUSH/POP-Orgien... - nein,
nicht Popp-Orgien!), weil die gerade wieder anderweitig benötigt werden.
Gerade im RM (16bit), wo nicht jedes Register ein Indexregister sein kann und wo außerdem einige Register Spezialfähigkeiten haben, die nicht auf ein anderes Register übertragen werden können, macht es Sinn, wenn man sich vorher überlegt, welche Register man wofür einsetzt.
Ich liste die mal auf, inklusive Fähigkeiten. Das E (EAX) lasse ich mal weg. Die 32bit-Erweiterung haben ja alle.
AX
Teilbar in 8bit AH/AL. Multiplikationen, Divisionen. AL nutzbar für XLAT Befehl. Enthält den Wert für Port-Ein/Ausgaben (IN und OUT Befehl). AH kann mit den Flags geladen, Flags von AH geladen werden. AL, AX und EAX werden auch für den Wert benutzt, wenn es z.B. um die MOVSB, MOVSW, MOVSD Befehle geht, die dann, in Kombination mit SI und DI zum Kopieren/Testen/Füllen und bei Nutzung von REP auch für ganze Bereiche benutzt werden können.
CX
Teilbar in 8bit CH/CL. CL nutzbar als variabler Parameter für Bit-Shift/Roll-Befehle (SHL, ROR usw.). LOOP-Befehl. Abwärtscounter für REP (Wiederholungen). Spezialbefehl vorhanden für Testen auf CX=0 mit anschließendem Sprung (JCXZ)
DX
Teilbar in 8bit DH/DL. Erweitertes Register für Multiplikationen, Divisionen, sobald mehr als 8bit. Außerdem für mehr als 8bit-Port einziges Register, das die Portadresse enthalten kann (IN / OUT Befehle).
BX
Teilbar in 8bit BH/BL. Als Indexregister nutzbar. Für XLAT Befehl.
(Irgendwie mein "Lieblingsregister". Gleichzeitig 8bit-teilbar und als Indexregister nutzbar, das hat nur BX.)
SP
Stackpointer - das an wenigsten "frei nutzbare" Register. ES IST NICHT ZU EMPFEHLEN, SP ZU ÄNDERN/NUTZEN, AUßER MAN WEIß, WAS MAN TUT! Will man mit SP herummanipulieren und es braucht mehr als EINEN Befehl, damit es wieder einen gültigen Wert enthält, so müssen davor IMMER die Interrupts abgeschaltet werden!
Schade, daß SP NICHT als Indexregister nutzbar ist...
BP
(Längere Erklärung. Aber diese ganzen Erklärungen für BP sind wichtig.)
Als Indexregister nutzbar. (Wichtig: SS wird dann Standardsegment! Ich kann das nur immer wieder betonen!)
Wenn ohne Kombination, wird aber immer intern [BP+0] daraus, da der Fall [BP] ersetzt wurde durch [Offset], damit auch Zugriff auf einen Offset ohne Indizierung möglich ist.
Wird durch ENTER/LEAVE beeinflußt. Hochsprachen nutzen das gern, um einen "Stackframe" zu erschaffen beim Eintritt in eine Subroutine. BP wird dazu auf den Stack gerettet und erhält danach den momentanen Wert des Stackpointers.
So ist es leichter, auf die lokalen Variablen, die das Unterprogramm angelegt hat (und auf die im Header übergebenen Variablen/Werte) zuzugreifen.
Also, WICHTIG: Wenn man ein Pascal-Unterprogramm erzeugt, das außer Assembler auch Hochsprache enthält (also NICHT als
definiert ist, erzeugt das den ENTER/LEAVE Rahmen.
Man kann (ohne "assembler;") dann auch auf lokale oder übergebene Variablen auch im asm-Teil zugreifen, intern werden daraus Adressierungen mit [BP+Offset] gemacht. (Das merkt man z.B., wenn man mit [Lokale_Variable+BX] indizieren will. Geht nicht, weil Kombi [BP+BX+Offset] als Index nicht vorgesehen!, hier muß dann SI oder DI herhalten.)
Wichtig ist: WENN man BP dann selbst nutzen oder ändern will, sollte man entweder schon alle lokalen Variablen "in Sicherheit gebracht" haben oder BP vorher sichern - da sonst natürlich spätere Zugriffe auf lokale Variablen (zu denen auch die im Procedure-Header übergegebenen gehören) nicht mehr funktionieren.
In reinem Assembler kann man BP natürlich wie jedes andere Register nutzen.
SI
Als Indexregister nutzbar. Ist "Source-Index" (daher SI) für die speziellen MOV/Test-Kopier-Befehle, die auch mit REP kombinierbar sind (mit CX als Counter).
DI
Als Indexregister nutzbar. Ist "Destination-Index" (daher DI) für die speziellen MOV/Test-Kopier-Befehle, die auch mit REP kombinierbar sind (mit CX als Counter). Wichtig: Bei den speziellen Befehlen, wo DI als Ziel gilt, ist das Zielsegment IMMER ES, das kann nicht "präfixt" werden. Das Quellsegment (für SI) ist standardmäßig DS und kann "präfixt" werden (in jedes andere Segment).
Wieso dieser ganze Kram, den ich hier schreibe? Naja , diese ganzen Sachen (habe sicher noch ein paar vergessen) sind so Spezialzwecke, die die verschiedenen Register so haben und die ich immer so im Kopf behalte, um die Register optimal ihrem Zweck entsprechend auszubeuten...
Das Ganze schließt natürlich nicht aus, Register zu "zweckentfremden" - das kann und wird oft passieren, gerade wenn man seinen Code optimiert. Und natürlich können, um Werte zu halten, laden/speichern, zu rotieren, addieren, subtrahieren auf dem x86 erstmal ALLE diese Register gleichermaßen genutzt werden (bis auf SP vielleicht). Und selbst wenn man sich einen Registernutzungs-Plan gemacht hat, wird man von diesem auch intern oft abweichen.
So ein Plan ist (auch für mich) nicht dazu gedacht, mir selbst vorzuschreiben, wann ich welches Register nutzen soll, sondern eher dazu, daß ich, wenn ich gerade mehrere Register zur Auswahl hätte, mir die Entscheidung zu erleichtern, indem ich das "geplante" den anderen vorziehe. Meiner Erfahrung nach kann man so das dauernde Sichern/Laden/Zwischenspeichern oder Tauschen von Registerinhalten erheblich minimieren.
Anderes Beispiel: im RM benutzt man sowieso nur 16bit-Indizes. Das muß aber nicht heißen, daß man deshalb die oberen 16bit der Indexregister brachliegen lassen muß. Sie eignen sich ebenfalls zum Speichern von Werten. Speicheroperationen sind auch immer langsamer als direkte Registeroperationen.
Ich gebe mal ein Beispiel:
Ich will DI für eine Schleife nutzen, von 0 angefangen. Die anderen Register werden gleich noch gebraucht, ich kann also keins der Register ändern außer DI. Aber der Wert, den DI enthält, müßte ich vorher auch noch sichern, der wird danach wieder gebraucht.
Die offensichtliche, aber dümmere Methode:
Code: Alles auswählen
push DI {DI auf den Stack sichern}
xor DI,DI {DI Nullsetzen}
.... {die Schleife abarbeiten}
pop DI {DI vom Stack zurückholen}
Die weniger offensichtliche, aber schlauere Methode:
Code: Alles auswählen
shl EDI,16 {alten Wert von DI sichern, DI gleichzeitig nullsetzen}
.... {die Schleife abarbeiten}
shr EDI,16 {alten Wert von DI zurückholen}
Ein Befehl weniger - und zusätzlich keine Stackbefehle nötig, alles schnelle Registerbefehle. Das Nullsetzen und Sichern passiert gleichzeitig. Man kann so auch zwei verschiedene 16-Bit-Indexwerte tauschen, indem man immer wieder mit ROL DI,16 (oder ROR DI,16) arbeitet.
(Dieses Tauschen der Indexwerte benutze ich z.B. in meinem GameSys2. Es gibt 128 Befehle, das obere Bit löst den Indextausch aus. Der Sinn ist, daß man so im gleichen Unterprogramm mit den gleichen Befehlen die "Variablen" zwei verschiedener "Figuren" ansprechen kann. Der Index gibt den Offset der Variablen einer Figur im Figurenstack an.)
Die Idee, mit Shiftbefehlen gleichzeitig Werte zu sichern und Teilregister (wie AL oder AX) eines Registers nullzusetzen, ist zwar einfach - aber ich bin da auch nicht gleich am Anfang meiner ASM-Laufbahn drauf gekommen.
Das Ganze macht schon ab 286ern Sinn - ab da hat die Anzahl der Schiebungen keinen Einfluß mehr auf die benötigte Ausführungszeit. D.h. shl DI,5 dauert genausolange wie shl DI,13
OK, das soll erstmal genug dazu sein. Nur noch soviel: Sowohl in GameSys2, als auch in ISM habe ich so einen "Registernutzungsplan", d.h., außer an Stellen, wo es nicht anders geht (d.h. wo entweder keine Register mehr frei sind oder wo ein bestimmtes Register zwingend erforderlich ist, weil es als einziges das benötigte Feature hat) werden die Register entsprechend ihrem Plan eingesetzt.
Die ganze Idee wie ich in den letzten Jahren so programmiere, basiert im Grunde darauf, daß ich größere Projekte, vor allem solche Dinge wie GameSys2 und ISM, so konstruiere, als würde ich eine Maschine zusammenschrauben. Möglichst wenig Teile verbauen - Teile mehrfach und für typgleichen Zweck verwenden, um die Maschine klein und schnell zu bekommen.
Hochsprachen haben hier manchmal gewisse Nachteile. Sie erlauben dem Programmierer, wild Variablen zu deklarieren, bis der Speicher voll ist. Weil man ja "ordentlich" sein will, werden Variablen nur ihrem Namen entsprechend benutzt, für jeden Kram wird eine neue definiert, selbst wenn sie nur einmal kurz benutzt wird. Der "Ich-habe-Programmieren-in-einem-Kurs/Lehrgang-gelernt"-Mensch wird keine "Hilfsvariablen" nutzen, die auch mal mehrfach verwendet werden können. Das macht den Source zwar schick, aber erhöht den Speicherverbrauch.
Das Schöne an Assembler ist, daß man hier gut lernen kann, sich auf Wesentliches zu beschränken, EINFACH zu denken, keinen Platz und keine Register zu verschwenden.
Meiner (persönlichen) Meinung nach wird man erst dann ein guter und effizienter Hochsprachenprogrammierer, wenn man sich auch mal etwas eingehender mit Assembler beschäftigt hat. Erst dann wird einem klar, was man mit vielen Dingen, die man früher in Hochsprachen "mal einfach so" gemacht hat, wahrscheinlich im Speicher und für die CPU so angerichtet hat. - Meistens ein Disaster oder einen Worst Case.
Erst dann wird einem klar, wieso erfahrene Programmierer dem Hochsprachen-Einsteiger immer Sachen sagen wie "Keep it simple". Oder wieso Entscheidungstabellen (oder überhaupt Tabellen) oft besser sind als Code.
Riesige komplizierte Formeln, IF-Entscheidungs-Kaskaden und ähnliches im Quellcode sehen für einen Laien so aus wie: "Wahnsinn, das ist ein totaler Experte."
Tja, und ein Experte, der sich denselben Quelltext anschaut, würde denken: "Wahnsinn, das ist ein totaler Laie."
Ich weiß natülich, daß eine solche Meinung unpopulär erscheint, in Zeiten, in denen der unbedarfteste Copy-&-Paste-Skripter einen Assembler-Coder auslachen darf (nur weil er schneller fertig ist, egal wie ineffizient das Ergebnis ist), in denen so mancher angebliche Programmierer noch nie einen einzigen Assembler-Opcode gesehen oder im Binärcode gerechnet hat (also immer *32 statt SHL 5 rechnen würde), in denen Hochsprachen ganze 1 MB-Blöcke an Modulen zu einer *.EXE zusammenfügen (selbst wenn nur 1 Subroutine daraus gebraucht wird), in denen die Qualität eines "Programmierers" proportional zu den Abstraktionsleveln ermessen wird, die er über der Maschinenebene steht (obwohl, wie jeder wissen sollte, jedes Abstraktionslevel das Programm größer und langsamer macht und mögliche Fehlerquellen hinzufügt) - und eben in Zeiten, in denen zwar "Retro" so hochgeschätzt wird und wieder beliebt ist, aber es kein Problem ist, daß ein 2D-Tetris eine VM benötigt, die auf einer 2 GHz-Maschine laufen müß...
Aber wem sage ich das? Wir sind ja hier im DOSforum. Der Eine oder Andere wird wahrscheinlich wissen oder nachvollziehen können, was ich meine.
@zatzen:
Ich Bitte um Entschuldigung Wenn das zuviel Kram auf einmal war. Sag Bescheid, wenn Dich das eine oder andere davon besonders interessiert.