The two samples don't quite do the same thing. The first sample starts by setting the location of the stack and data segments to some 'magic' location in memory:
mov ax, 07C0h
add ax, 288
mov ss, ax
mov sp, 4096
mov ax, 07C0h
mov ds, ax
The second sample doesn't (so it will use the SS and DS locations which are left in those registers by the O/S which loaded the program). I don't understand what 07C0h is and why there is that difference between the two programs.
Also the styles of assembly are different, for example one uses
0Eh and the other uses
0x0E. Are they both supported by NASM?
The first sample ends with
jmp $ which I don't understand: does it mean "jump to here" i.e. "infinite loop"? The
.done: ret label seems to be unreachable.
The second sample ends with
exit_function: ret which I guess returns to the O/S.
The most significant place in both code for me is the loop for printing a char from the whole string, so I want you to tell me which variant is more correct (or maybe both variants are bad and you can provide me a much better sample).
Your using BIOS int 10h which Wikipedia says is:
Teletype output AH=0Eh AL = Character, BH = Page Number, BL = Color (only in graphic mode)
The first sample doesn't initialize the BX register (so it may may contain a semi-random value), the second does. BX may or may not have an effect (in fact this whole "BIOS Teletype output" API may or may not work) depending on current the video mode.
If you're interested in speed then the following algorithm can be much faster than using the BIOS interrupt:
- Set the right video mode
- Set registers (for example, es:di) to point to the main memory location which is shared with the video card (perhaps 0xB0000 if I recall correctly)
- Write the character into video memory
Or instead of writing characters the the BIOS one at a time, there's a BIOS API which lets you write an entire string. I copied the following from 8086 BIOS and DOS interrupts (IBM PC):
INT 10h / AH = 13h - write string.
AL = write mode:
bit 0: update cursor after writing;
bit 1: string contains attributes.
BH = page number.
BL = attribute if string contains only characters (bit 1 of AL is zero).
CX = number of characters in string (attributes are not counted).
DL,DH = column, row at which to start writing.
ES:BP points to string to be printed.
mov al, 1
mov bh, 0
mov bl, 0011_1011b
mov cx, msg1end - offset msg1 ; calculate message size.
mov dl, 10
mov dh, 7
mov bp, offset msg1
mov ah, 13h
msg1 db " hello, world! "
As for your print loops:
mov ah, 0Eh
cmp al, 0
The first one (above) looks tight: i.e. it's good and short, with the
mov ah, 0Eh performed once outside the loop.
lodsb doesn't affect flags, so the separate
cmp is necessary. There may be a faster way to compare
al with 0, perhaps something like
I don't have a BIOS reference manual, but one thing it's lacking is a check of the 'return code' to see whether the output might have failed. This suggests that if the call fails it might leave a return code in
ah. You want your
ah preserved, so you might want to check that at least the first one didn't fail, and then have a loop which assumes that subsequent won't fail.
If your string weren't null-terminated, if instead you knew the length of it ...
db 'Hello World'
... then you could load the length into
cx and use the
loop instruction, something like:
mov si, HelloString
Beware that the above may not be the fastest way to loop, anymore. I once read that the more complicated opcodes (like
loop) perform more slowly on modern CPUs than using several primitive instructions (e.g.
dec cx and
jnz). The reason for that is that a developer (or compiler) will choose and place those more primitive opcodes in such as way as to use both pipelines and not cause a stall. Writing assembly which keeps both pipelines busy is an advanced art (which, they try to build-in to compilers). If I look at the output from a compiler, I hardly recognize it any more! Because, it often uses several instructions instead of one or two. Nevertheless I think that the loop above is compact and idiomatic (if you're writing code for another programmer to read, not writing code in order use every last CPU cycle).
The second one doesn't seem to be optimized for size or speed:
mov ah, 0x0E
mov bh, 0x00
mov bl, 0x07
mov al, [si]
or al, al
- Why not use
lodsb instead of
mov al, [si] and
- Why set
bh instead of setting
- Why not set
ah before the loop begins (I think that a call to
int 10h should preserve the contents of the
- Why make
PrintCharacter a subroutine to be called, instead of including those instructions inline?
Also, about code style. I don't know which style is more correct in ASM, for e.g. in the 1st variant I'm using the name of the functions like: print_string, but as for the 2nd variant, I'm using the style, which is closer to C# name style PrintCharacter, so which variant of naming is more suitable in ASM language?
The NASM manual seems to use naming conventions more like
print_string. Chapter 9 talks about writing assembly called from high-level languages. Names of HLL functions are subject to name mangling so if you want to write the C
printf function in assembly you might need to call it