在x86 AT&T汇编中实现简易sprintf及字符数组遍历技术问询
从零实现x86 AT&T汇编版简易sprintf
嘿,作为汇编零基础新手,能想到**逐字节读取格式字符串并比对'%'**的思路已经非常到位了!咱们一步步把这个简易sprintf落地,我会尽量把每个细节讲得直白些——毕竟刚接触汇编的时候,每一条指令都得抠明白才行。
先明确需求边界
根据你的描述,咱们的目标是实现一个极简版sprintf:
- 输入:格式字符串(含
%d占位符) + 无符号数字(比如你例子里的'3',其实是数值3) - 输出:把
%d替换成对应数字的ASCII字符,其他原封不动输出(比如"%d\n"+ 3 →"3\n")
咱们先从32位Linux环境入手,AT&T汇编在这个环境下的语法更适合新手入门,编译运行也方便。
核心实现思路拆解
你已经想到了逐字节处理的核心逻辑,咱们把它细化成可执行的汇编步骤:
- 初始化数据区:定义目标缓冲区、格式字符串、要替换的数字。
- 指针初始化:用两个寄存器分别指向格式字符串的当前读取位置,和目标缓冲区的当前写入位置。
- 循环处理每个字符:
- 如果当前字符不是
%:直接复制到目标缓冲区,指针后移。 - 如果是
%:读取下一个字符,判断是否为d:- 是
d:把数字转换成ASCII字符(数字+48,因为'0'的ASCII是48),写入缓冲区。 - 不是
d:把%和后续字符原样复制(容错处理)。
- 是
- 如果当前字符不是
- 添加字符串结束符:所有字符处理完后,在目标缓冲区末尾加
\0。 - 测试输出:调用Linux系统调用把结果打印到终端,验证效果。
完整代码示例(带详细注释)
.data buf: .space 256 # 目标缓冲区,分配256字节空间 fmt: .string "%d\n" # 格式字符串,含%d占位符 num: .long 3 # 要替换的无符号数字(这里是3) .text .global _start _start: # 初始化指针:%esi指向格式字符串起始,%edi指向目标缓冲区起始 leal fmt, %esi leal buf, %edi loop_start: # 读取格式字符串当前字节到%al(8位寄存器,存单个字符) movb (%esi), %al # 判断是否到字符串结束(\0),是就跳转到处理结束 cmpb $0, %al je loop_end # 判断当前字符是否是'%' cmpb $'%', %al # 不是的话,直接复制到目标缓冲区 jne copy_char # 是'%',处理格式化逻辑 inc %esi # 指针后移,读取下一个字符(占位符类型) movb (%esi), %al cmpb $'d', %al # 判断是否是%d je handle_d_format # 是就处理%d替换 # 如果不是%d,把'%'和当前字符都复制到缓冲区 movb $'%', (%edi) inc %edi movb %al, (%edi) inc %edi inc %esi jmp loop_start handle_d_format: # 把数字转换成ASCII字符:数字 + '0'(ASCII 48) movl num, %eax addl $48, %eax movb %al, (%edi) # 写入目标缓冲区 inc %edi # 目标指针后移 inc %esi # 格式字符串指针后移,跳过'd' jmp loop_start copy_char: # 把当前字符复制到目标缓冲区 movb %al, (%edi) inc %edi # 目标指针后移 inc %esi # 格式字符串指针后移 jmp loop_start loop_end: # 在目标缓冲区末尾加字符串结束符'\0' movb $0, (%edi) # 调用Linux系统调用write,把缓冲区内容打印到终端 movl $4, %eax # write系统调用号是4 movl $1, %ebx # 文件描述符1是标准输出 leal buf, %ecx # 要输出的缓冲区地址 # 计算输出长度:缓冲区当前位置 - 起始位置 subl $buf, %edi movl %edi, %edx # 输出长度 int $0x80 # 触发系统调用 # 调用exit系统调用退出程序 movl $1, %eax movl $0, %ebx int $0x80
编译运行方法
- 把代码保存为
sprintf.s - 用gcc编译32位程序:
gcc -m32 -o sprintf sprintf.s - 运行程序:
./sprintf - 你会看到终端输出:
3(带换行),和预期一致!
新手常见问题说明
- AT&T汇编的语法细节:和Intel汇编相反,AT&T是
源操作数, 目标操作数,比如movb (%esi), %al是把%esi指向的内存字节读到%al里。 - 寄存器的选择:%esi、%edi常用作指针寄存器(分别是源索引、目标索引),%al用来存单个字节,%eax存双字数据。
- 系统调用的使用:32位Linux下用
int $0x80触发系统调用,%eax存调用号,%ebx/%ecx/%edx存参数。
如果后续要扩展支持更多占位符(比如%c、%s),只需要在handle_d_format的位置添加对应的判断和处理逻辑就好啦!
内容的提问来源于stack exchange,提问作者Telanore
相关产品推荐
相关产品推荐

