如果scanf格式错误会发生什么?

如果scanf格式错误会发生什么?

做大一的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。

通过不断的学习,可以知道强制类型转换在底层有一条指令进行机器码的转换,否则就会直接按照指定的格式进行解释,见文尾

下面是在ubuntu上的实践过程

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

上面是程序开始时0x7fffffffde480x7fffffffde500x7fffffffde58存放的值

(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×2e127
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=01.2522=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
float -> double

https://www.cnblogs.com/niniwzw/archive/2012/06/09/2542944.html

如果scanf格式错误会发生什么?_第1张图片

你可能感兴趣的:(C语言,c++)