天天看点

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

作者:小智雅汇

看下面的代码:

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

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

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

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

然后要压入参数:

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

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

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

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

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

-End-

继续阅读