图文理解printf遇到float类型提升且类型不匹配时产生的地址错位

回复 星标
更多

图文理解printf遇到float类型提升且类型不匹配时产生的地址错位

看下面的代码:

#include <stdio.h>int main(){    int x = 5;    float y = 3.1f;    printf("%d\n%f\n%f\n%d\n",x,x,y,y);    getchar();    return 0;}/*32位平台的输出:5-2.000000-2.0000001074318540*/

为什么会有这样的诡异输出?首先需要了解以下知识点:

1 函数调用约定

C默认的调用方式是__cdecl的调用方式,这个调用方式由主调函数负责堆栈管理(包括堆栈平衡),该种调用约定支持变长参数(数量不确定的函数参数)。主调函数主调变参列表的参数数量。另外,参数由右至左压入栈帧。其它函数调用约定由被调函数负责堆栈管理

关于函数调用约定和变参函数的细节,请参考:

C/C++|图文深入理解函数调用的5种约定

C|图文深入理解实现变参函数的4个宏和栈帧机制

2 函数栈帧按字长(32位平台4字节)对齐,数据类型不够4个字节的按4字节压栈,多余的字节填充,超过4个字节的double使用两个字节。

3 浮点数的数据处理使用一个由8个浮点寄存器循环构造的浮点栈来处理。浮点数的处理会有一个精度的问题。传参时,当有float和double之间的隐式类型转换时,会用到浮点栈。通常,float类型在数据处理时(非存储时)会提升为double类型。

4 数据存储大小端的问题,intel CPU通常是小端存储。

5 变参函数以第一个参数为基准,按%后面字符指示的数据类型的长度进行偏移来访问其它参数。

现在我们再来分析上面的三行代码:

    int x = 5;    float y = 3.1f;    printf("%d\n%f\n%f\n%d\n",x,x,y,y);

看下面的汇编代码,先是压入局部变量:

4:        int x = 5;00411188   mov         dword ptr [ebp-4],55:        float y = 3.1f;0041118F   mov         dword ptr [ebp-8],40466666h

然后要压入参数:

6:        printf("%d\n%f\n%f\n%d\n",x,x,y,y);00411196   fld         dword ptr [ebp-8]00411199   sub         esp,80041119C   fstp        qword ptr [esp]0041119F   fld         dword ptr [ebp-8]004111A2   sub         esp,8004111A5   fstp        qword ptr [esp]004111A8   mov         eax,dword ptr [ebp-4]004111AB   push        eax004111AC   mov         ecx,dword ptr [ebp-4]004111AF   push        ecx004111B0   push        offset string "%d\n%f\n%f\n%d\n" (0042701c)

float要提升到double,用到了浮点栈,压入栈帧的不是4字节的float,而是转换后的8字节的double。

变参函数的机制首先会让一个指针基于第1个参数指向第2个参数,由第1个参数(格式化字符串,其“%”后的字符指明了数据类型)来控制其它参数的相对偏移位置(地址),并控制指针的强制类型转换和移动:

513431

上面浮点数的在浮点栈的处理时有精点丢失的问题,在printf显示时也会有精度丢失的问题,上面因为类型的不匹配形成了指针偏移时的错位,结合到一起,形成了上述诡异的输出结果。

这也是变参函数容易出错的问题所在。

-End-

此帖已被锁定,无法回复
新窗口打开 关闭