c语言|详解C语言数据类型:float与double


c语言|详解C语言数据类型:float与double
文章图片
【c语言|详解C语言数据类型:float与double】
c语言|详解C语言数据类型:float与double
文章图片
c语言|详解C语言数据类型:float与double
文章图片
使用printf时 , 它们具有相同的格式说明符 , 但使用scanf时 , 它们没有相同的格式说明符 。
为什么是这样?因为printf的参数被提升 , 而scanf的参数(作为指针)却不被提升 。
这种论点提升到底是什么?当较小尺寸的参数(特别是char , short和float)传递给可变参数函数(如printf之类的函数 , 其参数数量不固定)时 , 它们将转换为较大尺寸 。 Char和short转换为int , float转换为double 。
为什么这样 据我所知 , 纯粹出于历史原因 。 C的设计师认为这是个好主意 , 因为这些转换基本上是免费的 , 因为所有类型的尺寸都足够小 , 可以放入单个寄存器或堆栈中的单个“单元”(将内容压入堆栈必须与某些字节边界对齐 , 例如 , 堆栈上的每个项目都必须以4的倍数的地址开头 。 同样 , 显然 , 这种转换减少了传递参数时的错误 。
因此 , 当您向printf传递float类型的参数时 , 实际上它会在转换为printf之前就转换为double类型 。 我们可以使用调试器证明这一点 。 我写了下面的C代码:
主要功能编译成:
我在调用printf之前(在地址0x63b处)放了一个断点 。 根据Linux x86_64调用约定 , 浮点参数在XMM寄存器中传递(CPU中特殊的小内存位置 , 可用于对多条数据并行执行同一条指令 , 但实际上可用于大多数事情)。 因此 , 我查看了第一个XMM寄存器xmm0 , 然后:
xmm0中的值之一 , 当解释为双精度值时 , 是1 , 恰好是我们传递给printf的值 。 同时 , 当将该寄存器中的值解释为浮点数时 , 它们是这样(巧合的是 , 我们得到1.875) 。 因此 , 转换确实发生了 。 这就解释了为什么对于printf , 我们在浮点数和双精度参数中都使用%f -浮点数无论如何都会转换为双精度 , 因此printf不能分辨出两者之间的区别 。
同时 , scanf的参数是指针 , 因此不受此转换的限制 。 其原因是因为所有指针类型仅包含内存地址 , 并且所有内存地址都具有相同的大小(在我的64位计算机上为64位) 。 因此 , 当scanf在其格式字符串中获得%f时 , 它将期望一个float *类型的指针 , 而当它获得%lf时 , 将期望得到double *类型的指针 。 如果格式说明符和指针的类型不匹配 , 则会产生一些有趣的结果 。
由于float的大小为4个字节 , 而double的大小为8个字节(至少在我的机器上) , 因此当我们写入float *类型的指针所指向的位置时 , 我们将覆盖4个字节的内存 。 同时 , 如果我们写入由双*指向的位置 , 则将覆盖8个字节的内存 。 考虑以下代码:
printf说明符上的.15标志只是使printf精确度更高 。 由于我们使用的是双精度值(并且正如我刚刚说的那样 , %f也适用于printf的双精度值) , 因此我们实际上可以访问具有这种精度的数字(只要它们不是太大而不能填充即可 。 我实际上不是对浮点表示非常了解)
编译并运行后 , 结果如下:
如您所见 , 如果我们忽略所有类型的fuckery , 则数字应该匹配 , 但它们甚至不相近 。 这是为什么?
好吧 , 我们给scanf%f说明符 , 所以它期望一个浮点数* 。 但是我们传递了一个双* 。 现在 , 这些指针的实际值都只是地址-scanf不知道它们之间的区别 。 它进行了下去 , 读取我们输入的值 , 并将其存储为float 。 但是浮点数仅占用4个字节 , 因此scanf只会在我们可用的double变量的8个字节中写入4个字节 。 在我的情况下 , 由于我的机器是低位字节序的 , 因此似乎对应于double变量的细粒度(小有效位)数字的第4个字节将被覆盖 。 因此 , 当我们打印出double double back时 , 我们得到的数字几乎与以前相同 , 但最低有效数字有所变化 。


推荐阅读