64位Windows NASM中连续调用printf两次输出结果不一致问题
问题分析与解决
核心问题:易失性寄存器被破坏
你遇到的问题根源是r11属于Windows x64调用约定中的易失性寄存器——这类寄存器(rax、rcx、rdx、r8-r11)在调用函数后,值不会被保留。第一次调用printf时,printf内部会使用r11,直接覆盖了你存在里面的argc值,所以第二次调用时r11已经不是原来的参数数量了。
栈操作的必要性
你提到的函数首尾栈操作,对应Windows x64调用约定的两个强制要求:
- 阴影空间(Shadow Space):所有函数必须在栈上预留至少32字节(4×8字节)的空间,供被调用函数用来备份rcx/rdx/r8/r9这四个寄存器参数。
printf作为标准库函数,会依赖这个空间完成内部操作,不预留就会触发段错误。 - 16字节栈对齐:调用任何函数前,栈指针
rsp必须是16字节对齐。进入main时,rsp因为call指令压入返回地址,处于8字节偏移状态;push rbp后rsp再减8,回到16字节对齐,后续sub rsp, 32也会保持对齐,满足调用要求。
修复方案
方案1:使用非易失性寄存器存储argc
Windows x64的非易失性寄存器包括rbx、rbp、r12-r15,这些寄存器在函数调用后会被保留。注意使用前要保存原值,调用结束后恢复:
global main extern printf section .data fmt_num: db '%ld Params...', 10, 0 section .text main: push rbp mov rbp, rsp sub rsp, 8*4 push rbx ; 保存rbx的原始值,遵守调用约定 mov rbx, rcx ; 将argc存入非易失性寄存器rbx mov rcx, fmt_num mov rdx, rbx call printf ; 第一次输出正确 mov rcx, fmt_num mov rdx, rbx ; rbx的值未被printf修改 call printf ; 输出正确结果 pop rbx ; 恢复rbx的原始值 add rsp, 8*4 mov rax, 0 pop rbp ret
方案2:将argc存入栈局部变量
把参数存在栈的局部空间里,完全不受寄存器破坏的影响:
global main extern printf section .data fmt_num: db '%ld Params...', 10, 0 section .text main: push rbp mov rbp, rsp sub rsp, 8*4 mov [rbp-8], rcx ; 将argc存入栈上的局部变量位置 mov rcx, fmt_num mov rdx, [rbp-8] call printf mov rcx, fmt_num mov rdx, [rbp-8] ; 直接从栈中读取原始argc值 call printf add rsp, 8*4 mov rax, 0 pop rbp ret
补充说明
- 你测试时用
mov rdx,23能正常运行,是因为直接给rdx赋值,没有依赖被破坏的寄存器,避开了核心问题。 - 格式符从
%ld改%d无效,是因为问题本质是寄存器值被破坏,和格式符无关——不管用哪种格式,只要传入的是错误的寄存器值,输出就会异常。
内容的提问来源于stack exchange,提问作者Digit
相关产品推荐
相关产品推荐

