我基本上都能理解
那么你比我要高明一些...(尽管从你的进一步评论中,你意识到代码中还有其他无意义的东西 :) )。
为什么我们要推送 ecx 并弹出 ecx,因为我看不出它与其余代码有何关系。Ecx 的值为 256,因为我们想要所有字符,但不知道它在哪里和如何使用。
它被 LOOP
指令使用(这不是一个好主意:为什么循环指令很慢?),它将递减 ecx
,并在值大于零时跳转,即它是一个倒计时循环机制。
作为int 0x80服务调用需要ecx作为内存地址值,计数器通过push/pop保存/恢复。更高效的方法是将计数器值放入某个备用寄存器,例如esi,并执行dec esi jnz next。更高效的方法是重新使用字符值本身,如果输出以零值而不是零数字开头,则可以使用inc byte [achar]后的零标志来检测循环条件。
achar db '0'
我不太明白为什么“显示所有ASCII字符”的起始数字是零(值为
48
),这对我来说很奇怪,我会从零开始。但这又有另一个问题,在任何常见的Linux安装中,它都是UTF8编码,因此有效的可打印单字节字符只有32-126的值(与普通7位ASCII编码相同,使示例的这部分工作正常),而0-31和127的值是不可打印的控制字符,也与常见的7b ASCII编码相同。值128-255表示UTF8编码的多字节字符(例如:
ř
是两个字节
0xC5 0x99
),而作为单个字节时,它们是无效的字节序列,因为缺少UTF8“代码点”字节的其余部分。
在DOS时代,你可以直接编写代码写入VGA文本模式视频内存中的8位值,取值范围从零到255,每个值都有
不同的图形表示方式,你可以指定VGA自定义字体或已知的代码页来表示特定字符,这有时也被称为“扩展ASCII”,但常见的DOS安装与你评论中的链接有所不同,包含了更多的绘制框字符。这包括
\r
和
\n
控制字符,对于VGA来说,它们只是另一个字体字形,而不是换行和新行控制字符(这个意义是由BIOS/DOS服务调用创建的,该调用将内部光标移动到下一行并且丢弃输出的字符,而不是输出
\n
字符)。
在Linux控制台I/O中无法重新创建这个过程(除非UTF8字体包含所有奇怪的DOS字形,并且你将输出它们的正确UTF8编码而不是单字节值)。
结论是,该示例以值
'0'
(
48
)开始,直到值
126
输出正确的可打印ASCII字符,在
126
之后输出"something",由于这些字节有时会形成无效的UTF8编码,因此我技术上称其为具有未定义行为的“伪造”输出,您可能会在不同的Linux版本和控制台设置下获得不同的结果。
另外,NASM风格的注意事项:在标签后面加上冒号,即
achar: db '0'
,这将在您意外使用指令助记符作为标签时保护您,例如
loop:
或
dec: db 'd'
。
mov dx, [achar]
dx
没有被进一步使用,因此这是无用的指令。
cmp byte [achar], 0dh
这个比较的标志位也没有被进一步使用,因此这也是无用的。
所以调整后的示例可以看起来像这样:
section .text
global _start
_start:
call display
mov eax,1
int 0x80
display:
mov byte [achar], ' '
next:
mov eax, 4
mov ebx, 1
mov ecx, achar
mov edx, 1
int 0x80
inc byte [achar]
cmp byte [achar], 126
jbe next
mov byte [achar], `\n`
mov eax, 4
int 0x80
ret
section .bss
achar: resb 1
但更合理的做法是先将整个输出准备好,然后一次性输出,就像这样:
section .text
global _start
_start:
call display
mov eax,1
int 0x80
display:
mov al,' '
mov edi, allAsciiChars
mov ecx, edi
nextChar:
mov [edi], al
inc edi
inc al
cmp al, 126
jbe nextChar
mov byte [edi], `\n`
mov eax, 4
mov ebx, 1
lea edx, [edi+1]
sub edx, ecx
int 0x80
ret
section .bss
allAsciiChars: resb 126-' '+1+1
所有示例都是在64位Linux上使用nasm 2.11.08尝试的(基于Ubuntu 16.04的“KDE neon”发行版),并通过以下命令构建:
nasm -f elf32 -F dwarf -g test.asm -l test.lst -w+all
ld -m elf_i386 -o test test.o
带有输出:
$ ./test
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
ecx
被用作循环计数器,由于它加载了achar
用于系统调用,所以它的值需要保留。push
/pop
是一种实现这一目的的方法。至于mov dx, [achar]
,似乎确实是不必要的。 - Jestercmp
是无意义的。自己编写这个程序可能是一个很好的练习;我认为作者正在尝试一种不同的方法,并在其中留下了一些垃圾代码。 - Ray Toal