做大一的C语言实验时遇到一个有趣的事情,为什么
float
强制转换成double
不会造成任何问题,而scanf("%f", &double)
, 则完全不同呢?下面是我的一点看法我想这是程序在做强制类型转换的时候会知道我从哪里来32位(单精度浮点数),我要到哪里去64位(双精度浮点数),编译器能够很好的做到这一点,进行阶码和尾数单精度到双精度的转换,所以完全没有问题。
而
scanf
的"%f"
接受float
类型的指针,所以其把&double
当做float
对待,会将用户输入的值赋值到&double
的低32位(小端法),对于低32位全是尾数的双精度浮点数来说几乎没有影响,转化为10进制时还是得看高32位,所以我们的输入就丢失了回想起大二刚学C语言的时候,知道这是bug,但不知道这为什么是bug的茫然感。在(0|1)*****->anything的道路上越走越远。虽然java在web端很优秀,python在数据分析,人工智能领域很棒,但要和冯诺依曼体系下的计算机做朋友还得看C。
通过不断的学习,可以知道强制类型转换在底层有一条指令进行机器码的转换,否则就会直接按照指定的格式进行解释,见文尾
double a, b, c;
double area, perimeter, s;
printf("Enter 3 sides of the triangle: ");
scanf("%lf%lf%lf", &a, &b, &c);
Input:3 4 5
我们考虑这个例子,首先考虑浮点数在内存中的存储方式,通过使用gdb
调式可以知道a,b,c
的存储地址,如下所示
(gdb) p &a
$2 = (double *) 0x7fffffffde48
(gdb) p &b
$3 = (double *) 0x7fffffffde50
(gdb) p &c
$4 = (double *) 0x7fffffffde58
问题一:为什么a的存储地址与b的存储地址只相差了2呢
我们知道double
是双精度浮点数,在内存中的存储应该是64
位,这里两者相差仅2
个字节,这是为什么呢?然后我通过x/24bt
指令查看从0x7fffffffde48
开始24
字节的值
(gdb) x/24bt &a
0x7fffffffde48: 00000000 00000000 00000000 00000000 00000000 00000000 00001000 01000000
0x7fffffffde50: 00000000 00000000 00000000 00000000 00000000 00000000 00010000 01000000
0x7fffffffde58: 00000000 00000000 00000000 00000000 00000000 00000000 00010100 01000000
解决一:我SB了,因为是16进制表示,8和0相差8个字节,白忙活半个小时。。。。。
问题二:是不是类型转换时出现了错误呢?
为了解决这个问题我用下面的C代码进行了测试
#include
int main(void)
{
double a = 5;
float b = a;
printf("%f",b);
return 0;
}
由我们已经学过的知识可以知道,double
类型占有64
位二进制数,float
占有32位二进制数
(gdb) p &a
$3 = (double *) 0x7fffffffde78
(gdb) p &b
$4 = (float *) 0x7fffffffde74
(gdb) x/3wt &b
0x7fffffffde74: 01000000101000000000000000000000 00000000000000000000000000000000 01000000000101000000000000000000
(gdb) p a
$5 = 5
(gdb) p b
$6 = 5
通过测验可知,结果相同。可以发现强制类型转换后
doulbe a = 01000000000101000000000000000000 00000000000000000000000000000000
float b = 01000000101000000000000000000000
可以知道double -> float
的强制类型转换不仅仅是简单的截断操作,而是正确的解释成功了
int main(void)
{
1149: f3 0f 1e fa endbr64
114d: 55 push %rbp
114e: 48 89 e5 mov %rsp,%rbp
1151: 48 83 ec 10 sub $0x10,%rsp
double a = 11;
1155: f2 0f 10 05 b3 0e 00 movsd 0xeb3(%rip),%xmm0 # 2010 <_IO_stdin_used+0x10>
115c: 00
115d: f2 0f 11 45 f8 movsd %xmm0,-0x8(%rbp)
float b = a;
1162: f2 0f 5a 45 f8 cvtsd2ss -0x8(%rbp),%xmm0
1167: f3 0f 11 45 f4 movss %xmm0,-0xc(%rbp)
printf("%lf %f\n", a, b);
116c: f3 0f 5a 45 f4 cvtss2sd -0xc(%rbp),%xmm0
1171: 48 8b 45 f8 mov -0x8(%rbp),%rax
1175: 66 0f 28 c8 movapd %xmm0,%xmm1
1179: 66 48 0f 6e c0 movq %rax,%xmm0
117e: 48 8d 3d 83 0e 00 00 lea 0xe83(%rip),%rdi # 2008 <_IO_stdin_used+0x8>
1185: b8 02 00 00 00 mov $0x2,%eax
118a: e8 c1 fe ff ff callq 1050
return 0;
118f: b8 00 00 00 00 mov $0x0,%eax
}
上面是汇编代码,暂时没看懂,回来再看
scanf("%f%f%f",&a,&b,&c);
执行过程中会把从键盘得到的值按照float
的格式存放在a,b,c
的地址中去。
(gdb) x/2wt &a
0x7fffffffde48: 01010101010101010101001110011101 00000000000000000101010101010101
(gdb) x/2wt &b
0x7fffffffde50: 11110111111001100001111111001000 00000000000000000111111111111111
(gdb) x/2wt &c
0x7fffffffde58: 01010101010101010101001101010000 00000000000000000101010101010101
上面是程序开始时0x7fffffffde48
,0x7fffffffde50
,0x7fffffffde58
存放的值
(gdb) x/2wt &a
0x7fffffffde48: 01000000101000000000000000000000 00000000000000000101010101010101
(gdb) x/2wt &b
0x7fffffffde50: 01000000101000000000000000000000 00000000000000000111111111111111
(gdb) x/2wt &c
0x7fffffffde58: 01000000010000000000000000000000 00000000000000000101010101010101
上面是输入5 5 3
后存放的值,可以看到低32位发生了改变,改变的数值是按照IEEE 754
定义的单精度浮点数的值比如0 10000001 01000000000000000000000
根据下面的公式
( − 1 ) s × 1. f × 2 e − 127 \left( -1 \right) ^s\times 1.f\times 2^{e-127} (−1)s×1.f×2e−127
s = 0 s=0 s=0, e = 10000001 b = 129 e=10000001b=129 e=10000001b=129, f = . 01000000000000000000000 = 0.25 f=.01000000000000000000000=0.25 f=.01000000000000000000000=0.25
可以计算得出 r e s u l t = 0 ∗ 1.25 ∗ 2 2 = 5 result=0*1.25*2^2=5 result=0∗1.25∗22=5,与我们输入的值相符合。但定义a,b,c
时是按照double类型定义的,分别分配了8
个字节,解释的时候也应该按照双精度浮点数进行解释,即11位阶码,52位尾数,最最最重要的是这是小端法,故解释的值应该是 00000000000000000101010101010101 01000000101000000000000000000000
也就是说,输入的值改变的是后32位,对double
类型的解释几乎毫无影响,这严格来说是一个未定义现象
… If this object does not have an appropriate type, or if the result of the conversion cannot be represented in the object, the behavior is undefined. —C语言标准(第7.21.6.2节,第10段,fscanf)
通过查看C语言的机器级代码表示,可以很清楚的了解在底层发生了什么
用到的指令
gcc -g -o test test.c
gcc -E test.c -o test.i
gcc -S test.i -o test.s
https://www.cnblogs.com/niniwzw/archive/2012/06/09/2542944.html