Hex2Bin
-
Meine ersten zarten Versuche mit Assembler und ich dachte mir schreibe ich doch mal einen Hex2Bin-Converter. Er funktioniert auch. Da es aber meine ersten Versuche sind, wäre es nett wenn mal erfahrene Leute drüber schauen. Über jegliche Verbesserungsvorschläge bin ich natürlich dankbar, sowohl was die Programmlogik als auch den Coding-Style und die Verwendung der Register angeht. Das kleine Programm ist geschrieben im Emulator emu8086 und benutzt seinen Syntax und seine Interrupts.
; You may customize this and other start-up templates; ; The location of this template is c:\emu8086\inc\0_com_template.txt org 100h start: mov ax, 0abcdh ; value to be bit-printed mov di, ax ; move it to di mov cl, 0fh ; initialize the shiftcounter to 15 ; we are dealing with 16 bit wide values nextbit: mov ax, 01h ; ax hold the bitmask shl ax, cl ; shift the mask in place for the current bit and ax, di ; mask out all other bits than the one we're looking at cmp ax, 00h ; is the resulting value still greater than zero? ja printOne ; it is so print a '1' mov al, '0' ; it's not so there was a zero in place call printchar ; print the '0' jmp continue ; jump over the next block printOne: mov al, '1' ; put the '1' call printchar ; and print it continue: cmp cl, 00h ; is the shiftcounter smaller than zero? je done ; it is so it's done dec cl ; it's not so decrease it jmp nextbit ; and go for the next bit done: int 20h ; go home ; expects: ; al - char to be printed printchar: mov ah, 0eh ; single char print func-#: 0x0e int 10h ; vid interrupt ret ; go back
-
Du koenntest die Bitmaske gleichzeitig als Zaehlvariable benutzen:
mov bx, 0abcdh mov cx, 08000h ; beim obersten bit anfagen nextbit: test bx, cx setz al add al,'0' call printchar shr cx,1 jnz nextbit
-
alternativer Vorschlag:
mov bx, 0abcdh mov cx, 0010h nextbit: mov ax, 0e00h shl bx, 1 adc al, 30h int 10h ; printchar dec cx jnz nextbit
Dein Code ist fuer einen Anfaenger nicht schlecht. Beim x86 ist vor allem zu beachten, dass eigentlich jede logisch/arithmetische Rechenoperation die Flags dem Ergebnis entsprechend setzt. So kannst du in deinem Code eigentlich schon mal ohne Einschraenkungen alle cmp-Befehle einfach weglassen (evtl. statt dem ja noch ein je schreiben).
Bei einer for-Schleife, deren Abbruchbedingung ein Vergleich mit 0 ist, kann man sich einen expliziten Vergleichsbefehl, wie in unseren beiden Beispielen zu sehen, zB. oft sparen. dec dekrementiert cx und setzt zugleich die flags. Ist das Ergebnis 0, auch das z-Flag (jz ist btw. aequivalent je).
Falls es sich nicht vermeiden laesst, mit 0 zu vergleichen, macht man idR. statt einem "cmp reg, 0" auch eher "test reg, reg". Ist kuerzer, sollte aber das gleiche Ergebnis bringen.Funktionsaufrufe per call fressen uebrigens Platz auf dem Stack (Ruecksprungaddresse) und Rechenzeit. Daher ist es nicht ratsam, einfach wie in Hochsprachen leider oft ueblich, Prozeduren allein zur uebersichtlichen Gliederung des codes zu missbrauchen, sondern nur einzusetzen, wenn es aus irgendwelchen Gruenden wirklich sinnvoll ist. Ansonsten bieten viele Assembler auch flexible macros, oder falls die Funktion eh nur aus 2 Zeilen in einem kleinen Code besteht, schreib' den Code einfach direkt rein und einen Kommentar dahinter.
BTW:
Hast du zwar nicht gemacht, aber gern (zT. sogar von "Profis") arglos heruntergecodete Sachen wiemov ah, 0ch mov al, 00h
sind natuerlich eine schreckliche Suende.
1. verbraet der Zugriff auf Teilregister (hier al und ah als Teil von ax) beim x86 mitunter zusaetzlich Zeit.
2. koennen 2 direkt aufeinanderfolgende Zugriffe auf dasselbe Register die Pipeline potentiell durcheinander bringen.
3. ist ein "mov ax, 0c00h" nicht nur schneller, sondern auch noch kuerzer.
-
Danke für eure beiden Vorschläge. Viel eleganter und effizienter als mein Versuch. Mir fehlt da noch der Überblick, welche Befehle welche Flags setzen und welche nicht. Und welche Befehle es überhaupt gibt.
Sehr verwirrend alles. Dagegen ist C ein Joke mit seinem kleinen Sprachumfang. Und daher auch mein erster Versuch, so wie er ist. So würde ich es in C programmieren. In Assembler denkt man aber wohl noch anders. Noch komprimierter und logischer.
Mir ist aufgefallen, daß ihr beide den zu untersuchenden Wert in BX packt, gibt es einen Grund dafür oder ist das Wurscht? Ich hab ihn im Gegensatz zu euch ja nach DI gepackt. Ich frage weil CX ja irgendwie als "Counter"-Register benutzt wird. Also jegliche Counter Funktionialität sollte, was ich bisher gehört habe, im CX Register stattfinden.
Und last but not least. Ich benutz Windows-XP und wie ich mich informiert habe, kann ich unter Windows leider nicht mehr auf die Interrupts zugreifen, wie ich es noch zu DOS-Zeiten konnte. Gibt es da einen Workaround unter Windows? Dann könnte ich mich von diesem Emulator lösen, der btw z.B. "setz" gar nicht kennt.
-
NDEBUG schrieb:
Dagegen ist C ein Joke mit seinem kleinen Sprachumfang.
Naja, C hat dafuer andere ueble Huerden.
NDEBUG schrieb:
Mir ist aufgefallen, daß ihr beide den zu untersuchenden Wert in BX packt, gibt es einen Grund dafür oder ist das Wurscht? Ich hab ihn im Gegensatz zu euch ja nach DI gepackt. Ich frage weil CX ja irgendwie als "Counter"-Register benutzt wird. Also jegliche Counter Funktionialität sollte, was ich bisher gehört habe, im CX Register stattfinden.
Ich habe nicht nachgeschaut, ob int 10h, ah=0eh irgendwas in bx erwartet. Wahrscheinlich nicht - dann ist es wirklich egal. Ich habe da einfach nur bei hellihjb abgekupfert.
cx bietet sich hauptsaechlich vom Namen her als counter an. Ansonsten kannst du ausser bei Spezialbefehlen wie loop oder rep, die von sich aus cx zum Zaehlen benutzen, eigentlich jedes allg. Register benutzen.NDEBUG schrieb:
Ich benutz Windows-XP und wie ich mich informiert habe, kann ich unter Windows leider nicht mehr auf die Interrupts zugreifen, wie ich es noch zu DOS-Zeiten konnte. Gibt es da einen Workaround unter Windows? Dann könnte ich mich von diesem Emulator lösen, der btw z.B. "setz" gar nicht kennt.
Jo, ist richtig: In Windows kannst du die BIOS-Interrupts nicht mehr benutzen. Der einzige "Workaround" waere, DOS-Programme zu schreiben und entweder auf einem eigenen Rechner mit DOS oder in einem Emulator wie DOSBox zu starten.
Windows-Programme benutzen ansonsten idR. die Win-API (und laufen im 32Bit Protected Mode im Gegensatz zum 16Bit Real Mode bei DOS).
-
Mir ist aufgefallen, daß ihr beide den zu untersuchenden Wert in BX packt
Das lag lediglich daran, dass ich Deine printchar-Funktion intakt lassen wollte und dadurch ax als Parameter "belegt" war.
Mir fehlt da noch der Überblick, welche Befehle welche Flags setzen und welche nicht. Und welche Befehle es überhaupt gibt.
Steht zB hier.
Dann könnte ich mich von diesem Emulator lösen, der btw z.B. "setz" gar nicht kennt.
Eine Moeglichkeit waere zB, Deinen Assembler-Code in C einzubetten (was auch diverse Tuecken birgt):
// __fastcall veranlasst, dass der Parameter in (e)cx erwartet wird void __fastcall printchar(char c) { printf("%c", c); } void hex2bin(unsigned short v) { _asm { mov ax, v mov dx, 0x8000 bitloop: test ax, dx setz cl add cl, '0' pusha call printchar popa shr dx,1 jnz bitloop } } int main() { hex2bin(0xabcd); return 0; }
-
Das mit dem Einbetten nach C sieht auf jeden Fall sehr gut aus. Ich werd mal nachher genauer danach schauen, ob ich da Doks zu finde. Daher danke für den Tip. Später soll es sowieso so laufen, daß der Assembler Code als Startupcode für ein C-Programm dient und einige Funktionionalität nach Assembler ausgelagert werden soll. Dann werde ich aber auch wieder direkt auf die Interrupts zugreifen können.
Es ist nur für den Assembler Beginner ziemlich doof, wenn die Interrupts gesperrt sind, da ich ja doch hier und da mal gerne sehen will, was mein Assembler Programm im Moment gerade tut.