汇编中如何使用Printf函数打印不换行的输出

7

我最近读到了一篇关于在汇编中使用printf和scanf的文章:

在汇编中intfmt: db "%d", 10, 0的含义

特别是它说:“在printf中,换行符会打印一个换行符,然后(如果输出处于行缓冲模式下,这很可能是),刷新内部输出缓冲区,以便您实际上可以看到结果。因此,当您删除10时,没有刷新,您将看不到输出。”

但是,如果我不想在我的汇编文件中输出换行符,我该怎么办呢? 以下是我编写的一个简单测试文件,尝试在没有换行符的情况下打印:

extern printf


LINUX        equ     80H      ; interupt number for entering Linux kernel
EXIT         equ     60       ; Linux system call 1 i.e. exit ()




section .data
    int_output_format: db "%ld", 0


segment .text
    global  main


main:
    mov r8, 10
    push rdi
    push rsi
    push r10
    push r9
    mov rsi, r8
    mov rdi, int_output_format
    xor rax, rax
    call printf
    pop r9
    pop r10
    pop rsi
    pop rdi
    call os_return      ; return to operating system


os_return:
    mov  rax, EXIT      ; Linux system call 1 i.e. exit ()
    mov  rdi, 0     ; Error code 0 i.e. no errors
    syscall     ; Interrupt Linux kernel 64-bit

但是,我阅读的文章表明stdout没有被刷新。我在想,也许我需要在输出数字后进行刷新?但我真的不确定。

我正在使用NASM汇编语言。

提前感谢!

5个回答

8

fflush() 函数用于刷新行缓冲或全缓冲的标准I/O流中的输出:

extern fflush
...
xor  edi, edi          ; RDI = 0
call fflush            ; fflush(NULL) flushes all streams
...

或者,mov rdi, [stdout] / call fflush 也可以用于刷新该流。 (使用default rel进行高效的RIP相对寻址,并且您还需要extern stdout。)


1
fflush 接受一个 FILE* 类型的参数(存储在 RDI 中)。如果你想传递一个 NULL 指针(表示刷新所有打开的流),你需要将 RDI 清零,而不是 RAX;或者你需要 mov rdi, [rel stdout] 来加载特定全局变量的值。fflush 不是可变参数函数,因此它不关心 AL 的值。(编辑:我会自己修复这个问题) - Peter Cordes
通常情况下,如果你从C标准库使用了stdio函数调用,你应该使用 call exit 退出程序,而不是使用原始的 _exit 系统调用。 - Peter Cordes

3

调用fflush(stdout);来显示当前缓冲区中的内容。


根据C标准,stdout是一个宏,它会扩展成一个类型为FILE*的表达式。我不知道在你的编译器中它是如何定义的。你需要从stdio.h中提取该值。 - Alexey Frunze
4
你可以调用 fflush(NULL),这在汇编代码中更容易实现,也许是 xor %eax, %eax; call fflush - Basile Starynkevitch
@BasileStarynkevitch:你说得对!我完全忘记了那种特殊的行为。 - Alexey Frunze
@BasileStarynkevitch 正确。我建议您将其编写为单独的答案,以便Sarah可以将其标记为她所需的答案! - Pete Hamilton
@BasileStarynkevitch: fflush像其他C函数一样在堆栈上获取它的参数,参见我的问题:https://dev59.com/n2HVa4cB1Zd3GeqPo6mI - Janus Troelsen
显示剩余3条评论

3

对于 Windows 32 位模式(FASM):

push [_iob]
call [fflush]   ; call into DLL.  Callee-pops calling convention

GNU/Linux 32位模式(NASM)

extern fflush
extern stdout
...
push dword [stdout]
call fflush            ; linker takes care of PLT stub for dynamic linking (in a non-PIE executable)
add  esp, 4            ; caller-pops calling convention
etc...

你会如何在NASM中完成这个任务?是否相同? - Pete Hamilton
我假设FASM代码是为Windows编写的?Linux glibc没有定义它,而调用约定看起来像是Windows风格的。但实际上,FASM在Linux上也可以完美运行。 - Peter Cordes

1
另一种可能性是删除stdout流的默认行缓冲。这里是C调用来做到这一点。将其翻译为汇编作为练习,因为我认为在汇编中进行文件/流I/O甚至没有意义,成本/效益极其不对等。
setvbuf(stdout, NULL, _IONBF, 0);

这样每个printf(以及fputsputcputs等)都会有一个隐式的fflush


0

我的回答是为那些寻找快速绕过问题的人准备的,而不是真正的修复。

我尝试逐位输出数字 1234,并遇到了与这里的人相同的问题。在尝试了提到的解决方案后没有成功,并且不想花费太多时间在此上,我已经找到了一种简单的方法来显示数字。

在您的输出字符串格式中,只需有一个输出字符串为空行(带有换行符)即可。

Digit_out: db "%u", 0
Number_end: db "", 10, 0

按照你的输出方式;在我的情况下,我使用了4次数字输出(pushes of digit_out),并且调用printf 4次。

完成后,push Number_end并进行最后一次printf调用。整个数字将会显示出来 :)


简而言之,最后使用任何stdio函数打印一个换行符即可退出。是的,只要您的stdout是终端,而不是重定向到文件或管道(这将使其默认为完全缓冲),它就可以工作。通常,您希望输出以换行符结尾,但如果您一直在使用stdio函数而不是原始的_exit系统调用,则还应该使用“call exit”(或从“main”返回“ret”)。然后,即使输出到常规文件或其他非终端,您的程序也将始终正常工作。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接