C中printf与scanf函数读取与储存参数实现办法

1 篇文章 0 订阅
1 篇文章 0 订阅

我是C语言的初学者,学到printf和scanf时有一些疑惑,于是自己研究总结了一些东西,可能会有错误,所以希望各位大牛指正哦~\(≧▽≦)/~啦啦啦

===============================================

I   关于Printf向堆栈传参方式及储存读取方式

一、printf的格式为:

printf(“格式字符串”,参数1,参数2,……);

其传递原理是,先将参数1、参数2、…传递到计算机堆栈中,堆栈类似于一个寄存器。计算机根据变量类型(Datatype)去储存而非根据格式说明符(Format specifier)去储存这些数据。之后printf根据格式说明符的形式去从堆栈中读取这些数据而非根据变量类型。所以一旦变量类型与格式说明符不匹配就会出现错误。

二、考察一下代码

 

输出结果如下所示

 

甲行输出的n1n2并没有问题,而n3n4出现异常,这是因为n3n4是长整形变量而使用%e的浮点指数格式符非法输出所导致。同理,乙行的四个参量输出均出现异常,这是因为当第一个浮点型变量用%ld格式符输出出现异常时导致后面都出现异常。

三、原因深层探究

首先根据1中所说,考察printf如何向堆栈传参。首先先将四个参数都变成二进制数,为了方便起见,统一以十六进制表示,两个十六进制数代表一个字节。需要指出,浮点型变量是以IEEE754标准储存(即包括符号位、阶码、尾数),另外,单精度浮点型float自动转换为双精度浮点型double(64位,包括1个符号位、11个阶码位以及52位尾数位)


向堆栈传参的原则是:每传递一个参量,事先开辟一个与之匹配的大小的空间;由低字节位向高字节位依次储存;对于一个数,其最右位低字节,最左位高字节;所以n1n2n3n4储存方式为

 

如图所示,n1~n4分别从低字节位向高字节位储存,在储存64位的double型的n1float自动转换)的时候,先开辟64位连续空间,从低字节向高字节储存,其读取方式则是从下到上蛇形读取。后面同理。

值得注意的是,当printf连续储存参量时,开辟的空间不一定是连续的。

在甲行中,printf%.1e格式符开始读取时,它会一次性按照储存顺序读取8字节,所以n1n2输出正常。但是当扫描至n3时,由于n3只有4字节,所以会将n4的四个字节数据也读取,即一次性划出8字节的范围,从下至上蛇形读取:

 

此时n3的读取结果如图所示,而这个数据被用%.1e读取时,会被认为是浮点型,所以尽管n3long型,但是用IEEE754标准下的double规则翻译为十进制就是3.1e+46

继续读取第四个%.1e,由于n4的位置已被读取,所以继续读取时会随机读取8个字节,这些内存的数据将是未知的。从而n4的结果也是未知的。

 

乙行按照%ld读取,%ld默认是读取4个字节数据,所以同上,它的读取结果是

 

n1被按照4个字节读取,n2也是,这样连带n3n4都被赋予原本是n2的数据,则原n3n4数据被丢弃。

他们都是按照%ld也就是整形数的方法读取,0X0000’0000=0,而0X4808’0000=1074266112

这与输出结果是相符的。

【由此可得关于printf的几点注意问题:A. 当第一个参量输出异常时,往往后面的参量都输出异常。B. Printf向堆栈传参时是按照参数数据类型传递的而非格式说明符;当读取数据时候是以格式说明符读取,此时与参数本身及其类型无关。】

 

 

 

 

II   关于Scanf的传参方式及赋值方式

四、考察下列代码

 

当输入3.59的结果如下

 

这是因为丙行中,%f的格式符与后面的int型参量类型不匹配所导致。

五、原因分析

Scanf首先要从外设得到输入数据,这些数据的储存方式是按照格式说明符所储存的,在这里与printf不同的是,%f说明输入数据是按照float储存的,它并不自动转换为double。由于输入3.59,这两个数按照IEEE754标准的float型储存方式(32位,1个符号位,8个阶码位,23个尾数位)储存,所以其数值为


现在scanf将这两个二进制数赋给rh两个变量,巧合的是,int型在VC下也是4字节的,从而赋值是无截尾无损失的,于是


则在丁行中代入表达式得(pi被预编译定义为3.1415

 

由于在计算时候浮点均按照double型计算,double能精确表示数字个数是16~17位,可见输出结果和计算结果的前18位是符合的,从而证明了其正确性。

 

【验证】

现在我们再输入6.253.5这两个量,按照如上方式做预测计算:

首先按照IEEE754float型将这两个量做转换


全部赋给rh


代入丁行表达式得

 

下面用程序验证结果

 

16位是符合的,这也符合double的精确表示数字个数范围。

六、输入流阻塞问题

继续上面的程序,若输入变成

 

即在两个数之间加一个逗号,此时会发生如下情况

 

这是由于scanf的特性所造成的。其输入机制包括一个键盘缓冲区。第一个%f要求scanf向键盘索要一个合法数,此时输入3.5是正常的。而第二个%f同样要求scanf向键盘索要一个合法数,而scanf得到的却是3.5之后的一个逗号,这个逗号不是一个合法数,此时输入异常,scanf会将逗号放回输入流内(即键盘缓冲区内仍然是由一个逗号),且不将其消除,这就造成输入流的阻塞:scanf一旦从键盘缓冲区读取就会读到逗号,这个逗号却又不会被%f所接受,于是发生矛盾。这个时候sacnf结束,但是第二个参量h并没有被有效赋值,此时它是一个随机值。

我们利用intk来接收scanf函数的返回值,它的返回值是成功赋值的变量数目

 

分别查看k

    

左图是正常输入,k=2,表面scanf成功赋值两个量;而右图是异常输入,k=1表明只成功赋值了一个变量,显然是r=3.5,但是h并没有成功赋值为9

 

查看异常赋值后变量值

七、解决方案

1.常规输入字符添加逗号

 

  得到

 

2.设置预宽用于截断所需数值,用滞后符%*c来储存逗号

 

  首先将输入的数据先截取三位赋给r,显然r=3.5,之后用一个%c来读取逗号,但是由于有滞后符%*c,它不把逗号赋给任何参数,逗号之后的赋给h,得到

 

3.变量h的再赋值

 

得到


  第一个%f读取时读到非法字符逗号停止,3.5全部赋值给r,然后%c读取逗号给h,显然显然h是一个错误数值,但是赋值仍然是成功的,之后9被第三个的%f读取再次赋值给h

  于是结果正确。

  【注意!不可以写为%f %f %f,这是因为逗号是字符,%f无法读取字符从而造成输入流阻

  塞,h不会被成功赋值的。】

  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值