X86-64 Assembly中printf 打印 float 和 double的bug的解决

前言

笔者最近闲的无聊,学习一些Assembly,其实笔者以前也想学习,但是没有坚持下来

笔者的电脑是Windows x64,那就学习x86-64的Assembly。

笔者学习了打印hello world,然后打印int,后面在打印float和double遇到巨大的问题。

笔者才疏学浅,搞了老半天才明白。

正文

看代码

cpp 复制代码
extern printf
extern ExitProcess
section .data
    fmt_int db "Integer: %d",10,0
    fmt_float db "Float: %f",10,0
    my_int dd 42
    my_float dd 3.141
section .text
global main
main:
    sub rsp,40
    ; ========== 1. 打印整数 int ==========
    lea rcx,[rel fmt_int]
    mov rdx,[rel my_int]
    xor eax,eax
    call printf
    ; ========== 2. 打印 float(需先转 double)  ==========
    lea rcx,[rel fmt_float] ;rcx存放fmt_float的地址
    movss xmm0,[rel my_float] ;取内存里的值到xmm0
    cvtss2sd xmm0,xmm0 ;转换成8字节
    mov eax,1;AL=1,使用1个xmm寄存器
    call printf
    
    xor ecx,ecx
    call ExitProcess

这个assembly代码意思是显然的,首先打印整数,然后打印float。

rcx是第一个Windows x64的第一个参数寄存器,存放整数 / 指针 / 地址。

rdx是第一个Windows x64的第二个参数寄存器。

还有其他参数寄存器,r8,r9。

还有存放浮点数的寄存器,分别是xmm0,xmm1,xmm2,xmm3。


对于printf这个c函数,它是一个可变参数函数。

cpp 复制代码
lea rcx,[rel fmt_int]

它的参数类型和参数的个数都是不确定的。

int

首先先来看看打印int。

cpp 复制代码
printf("%d",a);

需要传入两个参数,因此,需要两个寄存器rcx和rdx。

那么第一个

cpp 复制代码
lea rcx,[rel fmt_int]

lea 是 Load Effective Address(加载有效地址)的缩写。

说白了,将fmt_int的地址存放到rcx。

cpp 复制代码
 mov rdx,[rel my_int]

mov 是 Move(传送)的缩写,作用是把数据从源操作数复制到目标操作数

意思是rdx=42。

cpp 复制代码
xor  eax, eax

eax是rax这个64位寄存器的低32位。

上面的意思就是设置eax=0,本质上其实是设置rax的低8位al为0,即,al=0

al用来告诉变参函数printf:用了多少个 XMM 寄存器传浮点参数。

现在al设置为0,其实告诉printf没有设置浮点参数。


float

对float进行考虑

cpp 复制代码
printf("%f",a)

还是需要传入两个参数。

那个第一个参数的地址存放到rcx里面,没什么问题

cpp 复制代码
 lea  rcx, [rel fmt_float] 

因为,%f 按 64 位读取,需要把32位的float变成64位的/。

先把浮点数放到xmm0中。

cpp 复制代码
movss xmm0,[rel my_float]

然后变成64位。

cpp 复制代码
cvtss2sd xmm0,xmm0

因为使用了一个浮点寄存器,因此需要设置al=1,即

cpp 复制代码
mov eax,1

调用,打印。

代码看起来没什么问题。

笔者使用NASMClang进行汇编和链接,笔者使用的终端是nushell。

Index of /pub/nasm/releasebuilds/3.01/win64https://www.nasm.us/pub/nasm/releasebuilds/3.01/win64/llvm/llvm-project: The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.https://github.com/llvm/llvm-project

笔者创建了一个build.nu文件。如下

nasm -f win64 test.asm -o test.obj
clang test.obj -o test.exe
test

运行,结果如下

cpp 复制代码
❯ nu build.nu
Integer: 42
Float: 0.000000

可以发现打印的int没什么问题,打印的float居然是0,这就非常奇怪了。

笔者在这里搞了许久,最后终于成功了。一些参考

printf打印float错误引起的思考-5kdYeW0-ChinaUnix博客http://blog.chinaunix.net/uid-20773046-id-578320.htmlhttps://stackoverflow.com/questions/78806627/nasm-x64-floating-point-part-incorrectly-printed-as-0-000000https://stackoverflow.com/questions/78806627/nasm-x64-floating-point-part-incorrectly-printed-as-0-000000这其实是windows的调用约定。

当调用 printf 这种可变参数函数时,浮点数必须同时出现在 XMM 寄存器和对应的通用寄存器中

说白了,意思就是说,xmm0中的浮点数需要复制到rdx中,即rdx=xmm0

关键的代码如下

cpp 复制代码
movq rdx,xmm0

全部的代码如下

cpp 复制代码
extern printf
extern ExitProcess
section .data
    fmt_int db "Integer: %d",10,0
    fmt_float db "Float: %f",10,0
    my_int dd 42
    my_float dd 3.141
section .text
global main
main:
    sub rsp,40
    ; ========== 1. 打印整数 int ==========
    lea rcx,[rel fmt_int]
    mov rdx,[rel my_int]
    xor eax,eax
    call printf
    ; ========== 2. 打印 float(需先转 double)  ==========
    lea rcx,[rel fmt_float] ;rcx存放fmt_float的地址
    movss xmm0,[rel my_float] ;取内存里的值到xmm0
    cvtss2sd xmm0,xmm0 ;转换成8字节
    movq rdx,xmm0
    mov eax,1;AL=1,使用1个xmm寄存器

    call printf
    
    xor ecx,ecx
    call ExitProcess

再次运行。

❯ nu build.nu
Integer: 42
Float: 3.141000

没问题。

double

那么对于double还是类似的。如下代码

cpp 复制代码
extern printf
extern ExitProcess
section .data
    fmt_double db "Double: %f",10,0
    my_double dq 2.71828
section .text
global main
main:
    sub rsp,40
    lea rcx,[rel fmt_double]
    movsd xmm0,[rel my_double]
    mov eax,1
    call printf
    
    xor ecx,ecx
    call ExitProcess

设置了两个参数,但是没有进行关键的一步,设置rdx=xmm0。

结果打印显然为0,测试一下。

cpp 复制代码
❯ nu build.nu
Double: 0.000000

没问题,添加之后。

cpp 复制代码
movq rdx,xmm0

结果如下

cpp 复制代码
❯ nu build.nu
Double: 2.718280

总结

至于为什么是这样的,笔者也不知道。

相关推荐
福大大架构师每日一题1 天前
RAGFlow v0.27.1 发布:新增 Azure DevOps、You.com 搜索,修复海量 Bug,性能大幅提升!
bug·azure·devops
树码小子1 天前
测试(6) - bug篇(上)
软件测试·bug
周杰伦的稻香2 天前
[bug]npx @deepseek-ai/dsh plugin ...无限吃内存卡死
bug
T01156183 天前
全栈项目实战手记|艺培场馆课时预约小程序底层安全、交互、分包全量迭代复盘
安全·小程序·bug·前端实战·全栈项目实战手记·小程序实战
霍格沃兹测试学院-小舟畅学4 天前
DeepSeek Harness vs Claude Code:谁更会修Bug、跑测试?
bug
咖啡星人k4 天前
2026 AI 自动化测试:让 AI 帮你写用例、跑测试、修 Bug(MonkeyCode 云端实战)
人工智能·功能测试·单元测试·测试用例·bug·集成测试
Shadow(⊙o⊙)6 天前
Bug调试1.0
bug
T01156187 天前
艺培场馆课时预约小程序用户端 & 后台联动踩坑上篇(预约、课时、缓存、消息、学员端自身问题)
java·缓存·小程序·bug·全栈项目实战手记·艺培课时系统‘’
zone_z7 天前
07 · 等待事件与 OWI 方法:从 OSW 到 BUG 的完整破案实录
linux·数据库·oracle·ffmpeg·bug·troubleshooting
黑猫学长呀8 天前
【驱动开发常见问题】编译出现rm: cannot remove `asm/arch‘: Is a directory
驱动开发·单片机·嵌入式硬件·bug·编译报错·kernrl