我是C语言的初学者,学到printf和scanf时有一些疑惑,于是自己研究总结了一些东西,可能会有错误,所以希望各位大牛指正哦~\(≧▽≦)/~啦啦啦
===============================================
I 关于Printf向堆栈传参方式及储存读取方式
一、printf的格式为:
printf(“格式字符串”,参数1,参数2,……);
其传递原理是,先将参数1、参数2、…传递到计算机堆栈中,堆栈类似于一个寄存器。计算机根据变量类型(Datatype)去储存而非根据格式说明符(Format specifier)去储存这些数据。之后printf根据格式说明符的形式去从堆栈中读取这些数据而非根据变量类型。所以一旦变量类型与格式说明符不匹配就会出现错误。
二、考察一下代码
输出结果如下所示
甲行输出的n1、n2并没有问题,而n3、n4出现异常,这是因为n3、n4是长整形变量而使用%e的浮点指数格式符非法输出所导致。同理,乙行的四个参量输出均出现异常,这是因为当第一个浮点型变量用%ld格式符输出出现异常时导致后面都出现异常。
三、原因深层探究
首先根据1中所说,考察printf如何向堆栈传参。首先先将四个参数都变成二进制数,为了方便起见,统一以十六进制表示,两个十六进制数代表一个字节。需要指出,浮点型变量是以IEEE754标准储存(即包括符号位、阶码、尾数),另外,单精度浮点型float自动转换为双精度浮点型double(即64位,包括1个符号位、11个阶码位以及52位尾数位):
向堆栈传参的原则是:每传递一个参量,事先开辟一个与之匹配的大小的空间;由低字节位向高字节位依次储存;对于一个数,其最右位低字节,最左位高字节;所以n1、n2、n3、n4储存方式为
如图所示,n1~n4分别从低字节位向高字节位储存,在储存64位的double型的n1(float自动转换)的时候,先开辟64位连续空间,从低字节向高字节储存,其读取方式则是从下到上蛇形读取。后面同理。
值得注意的是,当printf连续储存参量时,开辟的空间不一定是连续的。
在甲行中,printf用%.1e格式符开始读取时,它会一次性按照储存顺序读取8字节,所以n1、n2输出正常。但是当扫描至n3时,由于n3只有4字节,所以会将n4的四个字节数据也读取,即一次性划出8字节的范围,从下至上蛇形读取:
此时n3的读取结果如图所示,而这个数据被用%.1e读取时,会被认为是浮点型,所以尽管n3是long型,但是用IEEE754标准下的double规则翻译为十进制就是3.1e+46。
继续读取第四个%.1e,由于n4的位置已被读取,所以继续读取时会随机读取8个字节,这些内存的数据将是未知的。从而n4的结果也是未知的。
乙行按照%ld读取,%ld默认是读取4个字节数据,所以同上,它的读取结果是
n1被按照4个字节读取,n2也是,这样连带n3、n4都被赋予原本是n2的数据,则原n3、n4数据被丢弃。
他们都是按照%ld也就是整形数的方法读取,0X0000’0000=0,而0X4808’0000=1074266112
这与输出结果是相符的。
【由此可得关于printf的几点注意问题:A. 当第一个参量输出异常时,往往后面的参量都输出异常。B. Printf向堆栈传参时是按照参数数据类型传递的而非格式说明符;当读取数据时候是以格式说明符读取,此时与参数本身及其类型无关。】
II 关于Scanf的传参方式及赋值方式
四、考察下列代码
当输入3.5和9的结果如下
这是因为丙行中,%f的格式符与后面的int型参量类型不匹配所导致。
五、原因分析
Scanf首先要从外设得到输入数据,这些数据的储存方式是按照格式说明符所储存的,在这里与printf不同的是,%f说明输入数据是按照float储存的,它并不自动转换为double。由于输入3.5和9,这两个数按照IEEE754标准的float型储存方式(32位,1个符号位,8个阶码位,23个尾数位)储存,所以其数值为
现在scanf将这两个二进制数赋给r、h两个变量,巧合的是,int型在VC下也是4字节的,从而赋值是无截尾无损失的,于是
则在丁行中代入表达式得(pi被预编译定义为3.1415)
由于在计算时候浮点均按照double型计算,double能精确表示数字个数是16~17位,可见输出结果和计算结果的前18位是符合的,从而证明了其正确性。
【验证】
现在我们再输入6.25和3.5这两个量,按照如上方式做预测计算:
首先按照IEEE754的float型将这两个量做转换
全部赋给r、h:
代入丁行表达式得
下面用程序验证结果
前16位是符合的,这也符合double的精确表示数字个数范围。
六、输入流阻塞问题
继续上面的程序,若输入变成
即在两个数之间加一个逗号,此时会发生如下情况
这是由于scanf的特性所造成的。其输入机制包括一个键盘缓冲区。第一个%f要求scanf向键盘索要一个合法数,此时输入3.5是正常的。而第二个%f同样要求scanf向键盘索要一个合法数,而scanf得到的却是3.5之后的一个逗号,这个逗号不是一个合法数,此时输入异常,scanf会将逗号放回输入流内(即键盘缓冲区内仍然是由一个逗号),且不将其消除,这就造成输入流的阻塞:scanf一旦从键盘缓冲区读取就会读到逗号,这个逗号却又不会被%f所接受,于是发生矛盾。这个时候sacnf结束,但是第二个参量h并没有被有效赋值,此时它是一个随机值。
我们利用int型k来接收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不会被成功赋值的。】