Assembly 如果没有c库中的printf,如何在汇编级编程中打印整数?

Assembly 如果没有c库中的printf,如何在汇编级编程中打印整数?,assembly,x86,integer,output,nasm,Assembly,X86,Integer,Output,Nasm,有人能告诉我以十进制格式在寄存器中显示值的纯汇编代码吗?请不要建议使用printfhack然后使用gcc编译 说明: 我用NASM做了一些研究和实验,我想我可以使用c库中的printf函数来打印一个整数。我是通过使用GCC编译器编译对象文件来实现的,而且一切都很正常 然而,我想要实现的是以十进制形式打印存储在任何寄存器中的值 我做了一些研究,发现DOS命令行的中断向量021h可以显示字符串和字符,而2或9在ah寄存器中,数据在dx中 结论: 我发现的所有示例都没有演示如何在不使用C库的print

有人能告诉我以十进制格式在寄存器中显示值的纯汇编代码吗?请不要建议使用printfhack然后使用gcc编译

说明:

我用NASM做了一些研究和实验,我想我可以使用c库中的printf函数来打印一个整数。我是通过使用GCC编译器编译对象文件来实现的,而且一切都很正常

然而,我想要实现的是以十进制形式打印存储在任何寄存器中的值

我做了一些研究,发现DOS命令行的中断向量021h可以显示字符串和字符,而2或9在ah寄存器中,数据在dx中

结论:


我发现的所有示例都没有演示如何在不使用C库的printf的情况下以十进制形式显示寄存器的内容值。有人知道如何在汇编中执行此操作吗?

我想您想将值打印到stdout?如果是这样的话
您必须使用a来执行此操作。系统调用依赖于操作系统

e、 g.Linux:


这篇文章中的hello world节目可能会给你一些见解

您需要编写一个二进制到十进制转换例程,然后使用十进制数字生成要打印的“数字字符”

你必须假设某处会有东西在你选择的输出设备上打印一个字符。调用这个子程序“print_character”;假定它接受EAX中的字符代码并保留所有寄存器。。(如果您没有这样的子例程,那么您还有一个额外的问题,应该是另一个问题的基础)

如果您在寄存器(例如EAX)中有一个数字的二进制代码(例如,0-9之间的值),您可以通过将“零”字符的ASCII代码添加到寄存器中,将该值转换为该数字的字符。这很简单:

       add     eax, 0x30    ; convert digit in EAX to corresponding character digit
然后可以调用print_character来打印数字字符代码

要输出任意值,您需要选取数字并打印它们

从根本上说,提取数字需要使用10的幂。使用十的一次方最容易,例如10本身。假设我们有一个除以10的例程,它在EAX中取一个值,在EDX中生成一个商,在EAX中生成一个余数。我把它作为一个练习留给你们去弄清楚如何实现这样一个例行程序

然后,一个简单的例程有一个正确的想法,即为该值可能具有的所有数字生成一个数字。32位寄存器将值存储为40亿,因此您可能会打印出10位数字。因此:

         mov    eax, valuetoprint
         mov    ecx, 10        ;  digit count to produce
loop:    call   dividebyten
         add    eax, 0x30
         call   printcharacter
         mov    eax, edx
         dec    ecx
         jne    loop
这很有效。。。但按相反的顺序打印数字。哎呀!我们可以利用下推堆栈存储生成的数字,然后按相反顺序弹出:

         mov    eax, valuetoprint
         mov    ecx, 10        ;  digit count to generate
loop1:   call   dividebyten
         add    eax, 0x30
         push   eax
         mov    eax, edx
         dec    ecx
         jne    loop1
         mov    ecx, 10        ;  digit count to print
loop2:   pop    eax
         call   printcharacter
         dec    ecx
         jne    loop2

作为练习留给读者:抑制前导零。此外,由于我们正在将数字字符写入内存,因此我们可以将它们写入缓冲区,然后打印缓冲区内容,而不是将它们写入堆栈。也留给读者作为练习。

无法评论,因此我以这种方式发布回复。 @Ira Baxter,完美的答案,我只想补充一点,当你将寄存器cx设置为值10时,你不需要除以10。只需将数除以ax,直到“ax==0”

您还必须存储原始数字中有多少位数

       mov cx,0
loop1: call dividebyten
       inc cx
不管怎样,你Ira Baxter帮助我优化代码的方法很少:)


这不仅与优化有关,还与格式化有关。当您想要打印数字54时,您想要打印的是54而不是00000000 54:)

1-9是1-9。在那之后,一定有一些转换,我也不知道。假设您在AX(EAX)中有一个41H,并且您希望在不进行服务呼叫的情况下打印一个65,而不是“a”。我想你需要打印一个6和5的字符表示,不管是什么。必须有一个常数,可以添加到那里。您需要一个模运算符(无论您在汇编中如何操作)并循环所有数字


不确定,但这是我的猜测。

您需要手动将二进制整数转换为ASCII十进制数字的字符串/数组。ASCII数字由范围为
'0'
(0x30)到
'9'
(0x39)的1字节整数表示

对于像十六进制这样的二次幂进制,请参阅二进制和二次幂进制之间的转换允许进行更多的优化和简化,因为每组位分别映射到十六进制/八进制数字


大多数操作系统/环境没有接受整数并将其转换为十进制的系统调用。在将字节发送到操作系统之前,或者在将字节复制到视频内存之前,或者在视频内存中绘制相应的字体标志符号之前,您必须自己完成这些操作

到目前为止,最有效的方法是进行单个系统调用,一次完成整个字符串,因为写入8个字节的系统调用与写入1个字节的成本基本相同

这意味着我们需要一个缓冲区,但这根本不会增加我们的复杂性。2^32-1仅为4294967295,仅为10位十进制数字。我们的缓冲区不需要很大,所以我们可以直接使用堆栈

通常的算法首先生成数字LSD(最低有效数字优先)。因为打印顺序是MSD优先,所以我们可以从缓冲区的末尾开始,然后向后工作。对于其他地方的打印或复制,只需跟踪它的起始位置,而不必费心让它到达固定缓冲区的起始位置。不需要使用push/pop来反转任何内容,只需首先向后生成即可

char*itoa\u end(无符号长val,char*p\u end){
常数无符号基=10;
char*p=p_端;
做{
*--p=(val%基)+“0”;
val/=基;
}while(val);//至少运行一次以打印val=0的“0”。
//写入(1,p,p_end-p);
       mov cx,0
loop1: call dividebyten
       inc cx
ALIGN 16
global _start
_start:
    mov    ebx, 100
.repeat:
    lea    edi, [rbx + 0]      ; put +whatever constant you want here.
    call   print_uint32
    dec    ebx
    jge   .repeat


    xor    edi, edi
    mov    eax, 231
    syscall                             ; sys_exit_group(0)
yasm -felf64 -Worphan-labels -gdwarf2 print-integer.asm &&
ld -o print-integer print-integer.o

./print_integer
100
99
...
1
0