C++ scanf()函数安全性问题
scanf()是C語言中的一個(gè)輸入函數(shù)。與printf函數(shù)一樣,都被聲明在頭文件stdio.h里,因此在使用scanf函數(shù)時(shí)要加上#include <stdio.h>。(在有一些實(shí)現(xiàn)中,printf函數(shù)與scanf函數(shù)在使用時(shí)可以不使用預(yù)編譯命令#include <stdio.h>。)它是格式輸入函數(shù),即按用戶指定的格式從鍵盤上把數(shù)據(jù)輸入到指定的變量之中。
int?scanf(const?char?*?restrict?format,...);函數(shù)的第一個(gè)參數(shù)是格式字符串,它指定了輸入的格式,并按照格式說明符解析輸入對應(yīng)位置的信息并存儲(chǔ)于可變參數(shù)列表中對應(yīng)的指針?biāo)肝恢谩C恳粋€(gè)指針要求非空,并且與字符串中的格式符一一順次對應(yīng)。
函數(shù) scanf() 是從標(biāo)準(zhǔn)輸入流stdin??(標(biāo)準(zhǔn)輸入設(shè)備,一般指向鍵盤)中讀內(nèi)容的通用子程序,可以說明的格式讀入多個(gè)字符,并保存在對應(yīng)地址的變量中。?
scanf函數(shù)返回成功讀入的數(shù)據(jù)項(xiàng)數(shù),讀入數(shù)據(jù)時(shí)遇到了“文件結(jié)束”則返回EOF。
scanf()函數(shù)安全性問題
(1)在高版本的 Visual Studio 編譯器中,scanf 被認(rèn)為是不安全的,被棄用,應(yīng)當(dāng)使用scanf_s代替 scanf。
(2) 對于字符串?dāng)?shù)組或字符串指針變量,由于數(shù)組名可以轉(zhuǎn)換為數(shù)組和指針變量名本身就是地址,因此使用scanf()函數(shù)時(shí),不需要在它們前面加上"&"操作符。
(3) 可以在格式化字符串中的"%"各格式化規(guī)定符之間加入一個(gè)整數(shù),表示任何讀操作中的最大位數(shù)。
(4) scanf函數(shù)中沒有類似printf的精度控制。
如: scanf("%5.2f",&a); 是非法的。不能企圖用此語句輸入小數(shù)為2位的實(shí)數(shù)。
(5) scanf中要求給出變量地址,如給出變量名則會(huì)出錯(cuò)
如 scanf("%d",a);是非法的,應(yīng)改為scanf("%d",&a);才是合法的。
(6) 在輸入多個(gè)數(shù)值數(shù)據(jù)時(shí),若格式控制串中沒有非格式字符作輸入數(shù)據(jù)之間的間隔,則可用空格,TAB或回車作間隔。
C編譯在碰到空格,TAB,回車或非法數(shù)據(jù)(如對“%d”輸入“12A”時(shí),A即為非法數(shù)據(jù))時(shí)即認(rèn)為該數(shù)據(jù)結(jié)束。
(7) 在輸入字符數(shù)據(jù)(%c)時(shí),若格式控制串中無非格式字符,則認(rèn)為所有輸入的字符均為有效字符。
例如:
scanf("%c%c%c",&a,&b,&c);輸入為:
d?e?f只有當(dāng)輸入為:def(字符間無空格) 時(shí),才能把'd'賦于a,'e'賦予b,'f'賦予c。 如果在格式控制中加入空格作為間隔,則把'd'賦予a, ' '(空格)賦予b,'e'賦予c。因?yàn)?c 只要求讀入一個(gè)字符,后面不需要用空格作為兩個(gè)字符的間隔,因此把' '作為下一個(gè)字符送給b。
如
scanf("%c?%c?%c",&a,&b,&c);我們用一些例子來說明一些規(guī)則:則輸入時(shí)各數(shù)據(jù)之間可加空格。
#include<stdio.h>int?main(void){char?a,b;printf("input?character?a,b\n");scanf("%c%c",&a,&b);/*注意兩個(gè)%c之間沒有任何符號*/printf("%c%c\n",a,b);return?0;}輸入:由于scanf函數(shù)"%c%c"中沒有空格,輸入M N,結(jié)果輸出只有M。而輸入改為MN時(shí)則可輸出MN兩字符,見下面的輸入運(yùn)行情況: input character a,b
1屏幕顯示:
#include?<stdio.h> int?main(void) {char?a,b; printf("input?character?a,b\n"); scanf("%c?%c",&a,&b);/*注意兩個(gè)%c之間的空格*/ printf("\n%c%c\n",a,b); return?0; }?
本例表示scanf格式控制串"%c %c"之間有空格時(shí), 輸入的數(shù)據(jù)之間可以有空格間隔。
(8) 如果格式控制串中有非格式字符則輸入時(shí)也要輸入該非格式字符。
例如:
scanf("%d,%d,%d",&a,&b,&c);其中用非格式符“ , ”作間隔符,故輸入時(shí)應(yīng)為:
5,6,7又如:
scanf("a=%d,b=%d,c=%d",&a,&b,&c);則輸入應(yīng)為
a=5,b=6,c=7如輸入的數(shù)據(jù)與輸出的類型不一致時(shí),雖然編譯能夠通過,但結(jié)果將不正確。
#include?<stdio.h> int?main(void) { int?a; printf("input?a?number"); scanf("%d",&a); printf("%ld",a); return?0; }如將scanf("%d",&a); 語句改為 scanf("%ld",&a);由于輸入數(shù)據(jù)類型為整型, 而輸出語句的格式串中說明為長整型,因此輸出結(jié)果和輸入數(shù)據(jù)不符。輸出并不是輸入的值。
輸入數(shù)據(jù)為長整型,輸入輸出數(shù)據(jù)才相等。
問題一
如何讓scanf()函數(shù)正確接受有空格的字符串?如: I love you!
#include?<stdio.h> int?main(void) { char?str[80]; scanf("%s",str); printf("%s",str); return?0; }輸入:
I?love?you!輸出:
I上述程序并不能達(dá)到預(yù)期目的。因?yàn)閟canf掃描到"I"后面的空格就認(rèn)為對str的掃描結(jié)束(空格沒有被掃描),并忽略后面的" love you!"。值得注意的是,我們改動(dòng)一下上面的程序來驗(yàn)證一下:
#include<stdio.h>#include<windows.h>int?main(void){char?str[80],str1[80],str2[80];scanf("%s",str);/*此處輸入:I?love?you!*/printf("%s\n",str);Sleep(5000);/*這里等待5秒,告訴你程序運(yùn)行到什么地方*//***不是sleep(5)*1,函數(shù)名是Sleep不是sleep。*2,Windows?API中,unsigned?Sleep(unsigned)應(yīng)該是毫秒ms.*/scanf("%s",str1);/*這兩句無需你再輸入,是對stdin流再掃描*/scanf("%s",str2);/*這兩句無需你再輸入,是對stdin流再掃描*/printf("%s\n",str1);printf("%s\n",str2);return?0;}輸入:
I?love?you!輸出:
Iloveyou!好了,原因知道了,所以結(jié)論是:殘留的信息 love you是存在于stdin流中,而不是在鍵盤緩沖區(qū)中。那么scanf()函數(shù)能不能完成這個(gè)任務(wù)?回答是:能!別忘了scanf()函數(shù)還有一個(gè) %[] 格式控制符(如果對%[]不了解的請查看本文的上篇),請看下面的程序
:
?
問題二
鍵盤緩沖區(qū)殘余信息問題
#include<stdio.h>int?main(void){int?a;char?c;while(c!='N'){scanf("%d",&a);scanf("%c",&c);printf("a=%dc=%c\n",a,c);/*printf("c=%d\n",c);*/}return?0;}
scanf("%c", &c);這句不能正常接收字符,什么原因呢?我們用printf("c = %d\n", c);將C用int表示出來,啟用printf("c = %d\n", c);這一句,看看scanf()函數(shù)賦給C到底是什么,結(jié)果是c=10 ,ASCII值為10是什么?換行即\n.對了,我們每擊打一下"Enter"鍵,向鍵盤緩沖區(qū)發(fā)去一個(gè)“回車”(\r),一個(gè)“換行"(\n),在這里\r被scanf()函數(shù)處理掉了(姑且這么認(rèn)為吧^_^),而\n被scanf()函數(shù)“錯(cuò)誤”地賦給了c.解決辦法:可以在兩個(gè)scanf()函數(shù)之后加getchar(),但是要視具體scanf()語句加那個(gè),這里就不分析了,讀者自己去摸索吧
。
版本1:運(yùn)行出錯(cuò)的程序這里再給一個(gè)用“空格符”來處理緩沖區(qū)殘余信息的示例:
版本2:使用了空格控制符后
#include<stdio.h>int?main(void){int?i;char?j;for(i=0;i<10;++i)scanf("?%c",&j);/*注意這里%前有個(gè)空格*/printf("%c",j);/*在輸入十個(gè)字符之后,驗(yàn)證打印出來的字符是否是自己輸入的最后一個(gè)字符(即輸入的第十個(gè)字符)*/return?0;}我們輸入:接著,我們運(yùn)行看看,首先,運(yùn)行第一個(gè)版本(錯(cuò)誤的程序)
0 1 2 3 4 5 6 7 8 9
結(jié)果是一個(gè)空字符
再運(yùn)行第二個(gè)版本(正確的程序)
同樣輸入:
0 1 2 3 4 5 6 7 8 9
這一次就顯示字符9,故此程序正確。
那么為什么第二個(gè)程序就正確呢,原因何在,在%前面加一個(gè)空格就這么有用,答案是肯定的,就是%前面的空格在起作用,讀者看看此文章的前面部分,在scanf的使用過程中應(yīng)注意的問題中已經(jīng)指出:“scanf()的格式控制串可以使用空白字符或其它非空白字符,使用空白字符會(huì)使scanf()函數(shù)在讀操作中略去輸入中的零個(gè)或多個(gè)空白字符。”
所以在%前面加上了空格(空格屬于空白字符,此外還有像制表符等也屬于空白字符),在輸入過程中,將略去輸入中的一個(gè)或多個(gè)空白字符,所以我們輸入的0 1 2 3 4 5 6 7 8 9這些字符中的空白字符就被略去了,字符9也就正確的打印出來了,這樣子解釋,相信大家都看明白勒吧!
問題三
輸入類型與格式化字符串不匹配導(dǎo)致stdin流的阻塞。
#include<stdio.h>int?main(void){int?a=0,b=0,c=0,ret=0;ret=scanf("%d%d%d",&a,&b,&c);printf("第一次讀入數(shù)量:%d\n",ret);ret=scanf("%d%d%d",&a,&b,&c);printf("第二次讀入數(shù)量:%d\n",ret);return?0;}正確輸入的話:我們定義了a,b,c三個(gè)變量來接受輸入的內(nèi)容,定義了變量ret來接收scanf函數(shù)的返回值。
但是當(dāng)輸入內(nèi)容與格式換字符串不匹配時(shí),結(jié)果會(huì)令人大跌眼鏡(仔細(xì)分析會(huì)對scanf函數(shù)和stdin流有更深入的哦):
執(zhí)行到第一個(gè)scanf時(shí),當(dāng)輸入字符’b’的時(shí)候與ret=scanf("%d%d%d",&a,&b,&c);中的格式化字符串不匹配,stdin流被阻塞,scanf函數(shù)不在讀取后面的部分,直接將1返回,表示只將stdin流中的1讀入到了變量a中。
執(zhí)行到第二個(gè)scanf時(shí),字符’b’還是與格式化字符串不匹配,stdin流仍然被阻塞,所以沒有提示輸入,scanf函數(shù)將0返回。
將代碼作如下修改,可以有力的證明上述結(jié)論。
#include<stdio.h>int?main(void){int?a=0,b=0,c=0,ret=0;ret=scanf("%d%d%d",&a,&b,&c);printf("第一次讀入數(shù)量:%d\n",ret);ret=scanf("%c%d%d",&a,&b,&c);printf("第二次讀入數(shù)量:%d\n",ret);return?0;}當(dāng)把第二個(gè)scanf函數(shù)內(nèi)的格式化字符串改為”%c%d%d”時(shí),運(yùn)行結(jié)果如下:
執(zhí)行到第一個(gè)scanf函數(shù)時(shí),由于輸入’b’的原因scanf函數(shù)直接返回1,stdin流阻塞。
執(zhí)行到第二個(gè)scanf函數(shù)時(shí),字符’b’與格式化字符串”%c%d%d”中的%c匹配,stdin流終于疏通,在輸入6,則將變量a,b,c分別賦值為98(‘b’的ASCII碼)、2、6,scanf函數(shù)返回3。
有上述問題可知,當(dāng)使用scanf函數(shù)時(shí),如果遇到一些匪夷所思的問題,在scanf函數(shù)后正確使用fflush(stdin);,清空輸入緩沖區(qū),可以解決很多問題。以本題為例:
#include<stdio.h>int?main(void){int?a=0,b=0,c=0,ret=0;ret=scanf("%d%d%d",&a,&b,&c);fflush(stdin);printf("第一次讀入數(shù)量:%d\n",ret);ret=scanf("%d%d%d",&a,&b,&c);fflush(stdin);printf("第二次讀入數(shù)量:%d\n",ret);return?0;}運(yùn)行結(jié)果:
問題解決。
問題四
如何處理scanf()函數(shù)誤輸入造成程序死鎖或出錯(cuò)
#include<stdio.h>int?main(void){int?a,b,c;scanf("%d,%d",&a,&b);c=a+b;/*計(jì)算a+b*/printf("%d+%d=%d",a,b,c);return?0;}如上程序,如果正確輸入a,b的值,那么沒什么問題,但是,你不能保證使用者每一次都能正確輸入,一旦輸入了錯(cuò)誤的類型,你的程序不是死鎖,就是得到一個(gè)錯(cuò)誤的結(jié)果,呵呵,這可能所有人都遇到過的問題吧?解決方法:scanf()函數(shù)執(zhí)行成功時(shí)的返回值是成功讀取的變量數(shù),也就是說,你這個(gè)scanf()函數(shù)有幾個(gè)變量,如果scanf()函數(shù)全部正常讀取,它就返回幾。但這里還要注意另一個(gè)問題,如果輸入了非法數(shù)據(jù),鍵盤緩沖區(qū)就可能還個(gè)有殘余信息問題。正確的例程
:
fflush(stdin)這個(gè)方法在GCC下不可用。(在VC6.0下可以)補(bǔ)充
以下是 C99 對?fflush?函數(shù)的定義:
int fflush(FILE *stream);
如果stream指向輸出流或者更新流(update stream),并且這個(gè)更新流
執(zhí)行的操作不是輸入,那么fflush函數(shù)將把任何未被寫入的數(shù)據(jù)寫入stream
指向的文件(如標(biāo)準(zhǔn)輸出文件stdout)。否則,fflush函數(shù)的行為是不確定的。
C和C++的標(biāo)準(zhǔn)里從來沒有定義過?fflush(stdin)。
fflush(NULL)清空所有輸出流和上面提到的更新流。如果發(fā)生寫錯(cuò)誤,fflush
函數(shù)會(huì)給那些流打上錯(cuò)誤標(biāo)記,并且返回EOF,否則返回0。
由此可知,如果 stream 指向輸入流(如 stdin),那么 fflush 函數(shù)的行為是不確定的。故而使用
fflush(stdin) 是不正確的,至少是移植性不好的。
可采用如下方法:
方法一:
?
/*此函數(shù)可以和scanf函數(shù)一起使用,但使用%c輸入時(shí)要注意,即此函數(shù)只能用于緩沖區(qū)非空的情況*/#include<stdio.h>void?flush(){char?c;while((c=getchar())!='\n'&&c!=EOF);}intmain(void){int?a,b,c;/*計(jì)算a+b*/while(scanf("%d%d",&a,&b)!=2)flush();c=a+b;printf("%d+%d=%d",a,b,c);return?0;}方法二:
使用getchar()代替fflush(stdin)
程序示例:
#include<stdio.h>int?main(void){inti,c;while(1){printf("Pleaseinputaninteger:");scanf("%d",&i);if(feof(stdin)||ferror(stdin)){//如果用戶輸入文件結(jié)束標(biāo)志(或文件已被讀完),或者發(fā)生讀寫錯(cuò)誤,則退出循環(huán)//dosomethingbreak;}//沒有發(fā)生錯(cuò)誤,清空輸入流。通過while循環(huán)把輸入流中的余留數(shù)據(jù)“吃”掉while((c=getchar())!='\n'&&c!=EOF);//可直接將這句代碼當(dāng)成fflush(stdin)的替代,直接運(yùn)行可清除輸入緩存流//使用scanf("%*[^\n]");也可以清空輸入流,不過會(huì)殘留\n字符。printf("%d\n",i);}return?0;}?
總結(jié)
以上是生活随笔為你收集整理的C++ scanf()函数安全性问题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。