将系统调用与linux上的printf混合使用

wal*_*876 4 c linux assembly printf x86-64

我在汇编调用c函数时做了一些测试,当我使用ansi转义代码并调用使用printf的ac函数时,我得到了我认为的奇怪行为.

这是装配部分:

section .data               

    red db 27,"[31;1m",0
    redlen equ $ - red

    cyan db 27,"[36;1m",0
    cyanlen equ $ - cyan

    colorReset db 27,"[0m",0
    colorResetLen equ $ - colorReset

section .text

extern printLetter
extern letter

global main

main:
        mov  BYTE [letter], 'H'
        call ansiSetRed
        call printLetter
        mov  BYTE [letter], 'e'
        call ansiSetCyan
        call printLetter
        mov  BYTE [letter], 'l'
        call ansiReset
        call printLetter
        mov  BYTE [letter], 'l'
        call ansiSetRed
        call printLetter
        mov  BYTE [letter], 'o'
        call ansiSetCyan
        call printLetter
        mov  BYTE [letter], '!'
        call ansiReset
        call printLetter
        mov  BYTE [letter], 10
        call printLetter

        ret

ansiSetRed:
        mov rax, 1
        mov rdi, 1
        mov rsi, red
        mov rdx, redlen
        syscall
        ret

ansiSetCyan:
        mov rax, 1
        mov rdi, 1
        mov rsi, cyan
        mov rdx, cyanlen
        syscall
        ret

ansiReset:
        mov rax, 1
        mov rdi, 1
        mov rsi, colorReset
        mov rdx, colorResetLen
        syscall
        ret    
Run Code Online (Sandbox Code Playgroud)

看起来很长,但我所做的只是在开头用ansi代码定义一些字符串,一个用于将前景色设置为红色,一个用于青色,一个用于重置颜色.

然后我有使用syscall write打印这个ansi字符串的函数.

主要功能是打印"你好!" 通过首先调用打印相应ansi字符串的汇编函数,然后调用extern c函数来打印存储在全局变量中的字符,来交替每个字母的颜色.

这里是c部分:

#include <stdio.h>

char letter;

void printLetter(void) {

    printf("%c", letter);

}
Run Code Online (Sandbox Code Playgroud)

当我运行它时,消息"你好!" 如果装配零件没有打印ansi代码,则显示全白色

在此输入图像描述

但如果我改变c部分只是在每个字符后面打印一个新行:

#include <stdio.h>

char letter;

void printLetter(void) {

    printf("%c\n", letter);

}
Run Code Online (Sandbox Code Playgroud)

然后这些字母显示我开始时预期的每种颜色中的一种颜色.

在此输入图像描述

这种行为的原因是什么?

fuz*_*fuz 8

这是因为如果stdout进入终端,stdio(C标准I/O包)对stdout使用行缓冲.这意味着您编写的数据不会立即发送到终端,而是缓冲直到整行可用.你在第一个程序中看到的(你好,在一行上)是在用换行符Hello调用之前没有实际写入的字符printLetter,导致stdout的缓冲区被刷新到终端.

我看到了以下解决问题的方法(其中任何一个都可以解决问题,但只使用一种方法):

  • 编辑ansiSetRed等以调用fwrite而不是直接执行write系统调用.这应该使缓冲按预期工作.
  • setbuf(stdout, NULL)在写入任何数据之前调用关闭缓冲.
  • stderr,而不是stdout因为stderr是无缓冲
  • fflush(stdout)每次执行后printf手动冲洗stdout.
  • 重写printLetter使用系统调用write而不是printf.

  • 更一般地说,永远不要将IO调用混合到软件堆栈的不同"级别"的函数,而是在同一设备上工作,除非你注意正确地同步它们(flush buffers&co.). (3认同)
  • @MatteoItalia我的所有建议都是这样做的(在POSIX中记录了禁用缓冲的行为就像每次写入时执行`fflush`一样,如果我没记错的话). (2认同)
  • 当然,事实上我赞成你的答案=)我只是更明确地说明了一般观点. (2认同)