#include <stdio.h&gt;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的调用办法,这个调用办法由主调函数卖力堆栈管理(包括堆栈平衡),该种调用约定支持变长参数(数量不愿定的函数参数)。
主调函数主调变参列表的参数数量。
其余,参数由右至左压入栈帧。
其它函数调用约定由被调函数卖力堆栈管理

phpinclude就错位图文懂得printf碰到float类型晋升且类型不匹配时发生的地址错位 PHP

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

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个参数(格式化字符串,其“%”后的字符指明了数据类型)来掌握其它参数的相对偏移位置(地址),并掌握指针的逼迫类型转换和移动:

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

第一个%f:

第二个%f:

这也是变参函数随意马虎出错的问题所在。

ref:

http://www.binaryconvert.com/result_double.html?hexadecimal=C00000004008CCCC

-End-