在FASM中打印float为何需先推送同值+4?REAL4本质是什么?
关于FASM打印浮点数的疑问解答
问题背景
在FASM中尝试打印浮点数时遇到异常,观察x64dbg生成的C程序汇编代码后尝试编写代码但失败,最终找到可行方案但不理解原理:
观察到的C程序汇编片段
mov dword ptr ss:[esp+1C],eax fld dword ptr ss:[esp+1C] ; 加载单精度浮点数到FPU栈st(0) fstp qword ptr ss:[esp+4] ; 将st(0)的值转成双精度并存入栈中 mov dword ptr ss:[esp],c.404000 call <JMP.&printf>
最初的错误尝试代码
format PE entry start include '%include%\win32a.inc' section '.text' code readable executable start: fld dword ptr dVal fstp qword ptr dVal2 push msg call [printf] call [getchar] call [exit] section '.idata' import data readable library msvcrt, 'msvcrt.dll' import msvcrt, printf, 'printf'\ exit, 'exit', getchar, 'getchar' section '.bss' data readable writeable dVal2 dq ? section '.data' data readable msg db '%f',10,13,0 dVal dq 3.14
程序要么崩溃,要么输出错误值,最终找到两种可行写法:
写法一:先转双精度再拆分压栈
... section '.text' code readable executable start: fld dword ptr dVal fstp qword ptr dVal2 push dword ptr dVal2+4 push dword ptr dVal2 push msg call [printf] call [getchar] call [exit] ...
写法二:直接拆分双精度变量压栈
... section '.text' code readable executable start: push dword ptr dVal+4 push dword ptr dVal push msg call [printf] call [getchar] call [exit] ...
疑问解答
1. 为什么要推送值+4和值两个部分?
核心原因有两点:
printf的%f格式符要求参数是双精度浮点数(8字节):不管传入的原本是单精度(float,4字节)还是双精度(double,8字节),%f都会按双精度解析。如果只传4字节,printf会读取栈上额外的4字节随机数据,导致解析错误或程序崩溃。- x86架构的小端字节序:多字节数据在内存中是低字节存低地址、高字节存高地址。以8字节的double为例,变量
dVal的起始地址对应低4字节,dVal+4对应高4字节。而x86的push指令每次只能压入4字节,且调用printf时参数要从右往左压栈,所以需要先压入高4字节(dVal+4),再压入低4字节(dVal),这样栈里的8字节布局正好符合double的小端存储格式,printf才能正确解析。
另外你最初的错误代码还有一个问题:调用printf时只压入了msg,没有传入浮点数参数,这直接导致printf读取栈上的随机值,必然引发异常。
2. MASM中的REAL4本质是什么?
REAL4就是32位单精度浮点数,对应C语言里的float,和FASM中用dd定义的浮点数完全等价。MASM用REAL4、REAL8(64位双精度,对应double,即FASM的dq)这类关键字,是为了明确指定浮点数的精度,避免和同长度的整数类型混淆。
内容的提问来源于stack exchange,提问作者Radioactive Epic Face
相关产品推荐
相关产品推荐

