賀 瓊,祝豐菊
(湖北工業(yè)職業(yè)技術(shù)學(xué)院 智能工程學(xué)院,湖北 十堰 442000)
在C語言程序設(shè)計課程教學(xué)中,經(jīng)常會遇到溢出問題。而且這些問題軟件通常不會報錯,但是危害不容小覷。下面將對溢出的現(xiàn)象進行分類說明,分析其產(chǎn)生的原因并提出有效解決方案。
這類現(xiàn)象比較容易在數(shù)據(jù)本身比較大而其數(shù)據(jù)類型取值范圍比較小的時候,或者涉及數(shù)據(jù)類型強制轉(zhuǎn)換的時候發(fā)生。如例1程序:
例1:#include
int main(int argc, char * argv[])
{
char c=300; //輸入數(shù)據(jù)較大而數(shù)據(jù)類型范圍較小
printf("c=%d ",c);
system("pause");
return 0;
}
本程序預(yù)期輸出為“c=300”,結(jié)果編譯運行后實際結(jié)果為“c=44”。哪里出了問題?
數(shù)組的溢出主要是指輸入的數(shù)據(jù)內(nèi)容大于數(shù)組長度而導(dǎo)致的錯誤。如下例2所示:
例2:#include
int main(int argc, char * argv[])
{
int i, c[10];
for(i = 0; i <= 12; ++i) //循環(huán)次數(shù)大于數(shù)組長度,將導(dǎo)致溢出
{
c[i] = i;
printf("%-3d",c[i]);
}
printf(" ");
system("pause");
return 0;
}
本例中輸出結(jié)果為“0 1 2 3 4 5 6 7 8 9 10 11 12”共13個數(shù)據(jù),可是定義的數(shù)組長度只有10。多出的3個數(shù)據(jù)放在那里?如何放置,有無危害?
C語言程序設(shè)計中的溢出不僅僅包含上述兩類,還有指針溢出、函數(shù)溢出等。
由例1可以看出,數(shù)據(jù)溢出將直接導(dǎo)致運行結(jié)果與預(yù)期不符。由于這類錯誤沒有警告,因此,操作中要么得到錯誤的結(jié)果卻不自知,要么糾結(jié)錯誤原因卻不知如何排故,尤其對初學(xué)者,可能極大打擊自信心。那么這類錯誤產(chǎn)生的原因又是什么?
其實問題是出在計算機內(nèi)部的數(shù)據(jù)存儲機制上。任何數(shù)據(jù)在計算機內(nèi)存中均以二進制形式存儲,其中正數(shù)直接用原碼存儲,負(fù)數(shù)將原碼轉(zhuǎn)換為反碼并加“1”轉(zhuǎn)換成補碼存儲。如本例中十進制“300”為正數(shù),轉(zhuǎn)化為二進制碼為“100101100”共9位??墒窃摂?shù)據(jù)類型為char(字符型),在內(nèi)存中僅占1個字節(jié),即最多只能有8位二進制位??梢姅?shù)據(jù)“300”超出了取值范圍,最高位發(fā)生了溢出,丟掉了。所以在計算機內(nèi)存中實際存儲值為八位的二進制碼“00101100”,轉(zhuǎn)化為十進制輸出就成了“44”。
由例2可以看出數(shù)組c[10]定義時有10個元素,內(nèi)存僅分配10個數(shù)據(jù)單元,可是最后卻輸出了13個元素。多出來的3個元素放在那里?當(dāng)數(shù)據(jù)寫入個數(shù)超過數(shù)組長度時,系統(tǒng)將多出的數(shù)據(jù)直接覆蓋相鄰的數(shù)據(jù)存儲單元完成強制寫操作,如果這部分區(qū)域剛好有其他的數(shù)據(jù),則完成對這部分?jǐn)?shù)據(jù)的改寫,從而造成嚴(yán)重的安全漏洞。黑客利用這種方法,精心設(shè)計寫入的數(shù)據(jù),可導(dǎo)致程序去執(zhí)行惡意代碼,讓系統(tǒng)死機或非法獲得系統(tǒng)訪問權(quán)[1]。這將帶來非常大的安全隱患。
造成這種錯誤的原因是粗心,忽略數(shù)組的長度為10,在for循環(huán)中將循環(huán)條件“i<10”錯寫成了“i<=12”,導(dǎo)致循環(huán)次數(shù)多出了3次,從而多寫入及輸出3個數(shù)據(jù)。
其他類型的溢出和上述的溢出很類似,其中指針溢出是指針發(fā)生運算后,其偏移后的指向超出了預(yù)定單元范圍,從而導(dǎo)致輸出亂碼。函數(shù)溢出主要是指函數(shù)調(diào)用時尤其是遞歸調(diào)用時發(fā)生的棧溢出,其本質(zhì)也是改寫緩存區(qū)中不屬于自己存儲單元的內(nèi)容,將產(chǎn)生潛在的安全隱患。這部分產(chǎn)生原因主要是理解不透徹,邏輯不清晰及粗心。
由上面的分析可以看出,C語言程序設(shè)計中溢出是比較重要的一種錯誤類型,危害很大,產(chǎn)生原因卻很簡單,主要是邏輯不清晰和粗心大意等問題。針對學(xué)生主要是初學(xué)者這一特點,在教學(xué)中要把握住以下幾方面。
雖然不同的程序員均有自己獨特的編程風(fēng)格和習(xí)慣,但是自己的獨特不是讓別人看不懂或者不愿意看。當(dāng)前很多大型編程項目通常是由多個程序員共同完成,因此在編寫代碼時遵循一定的行業(yè)規(guī)范顯得尤其重要。
初學(xué)者就是一張白紙,學(xué)習(xí)伊始接觸的如果是規(guī)范的代碼,將有助于提升學(xué)生的規(guī)范意識,并逐步養(yǎng)成良好的編程習(xí)慣。在后期的學(xué)習(xí)中,學(xué)生就會尊重規(guī)范,遵守規(guī)則,將來自己寫的代碼就會比較專業(yè)。在未來的工作中如果與別人合作也會溝通更順暢,從而提高代碼書寫效率。
教師在授課中對容易發(fā)生溢出的地方講解清楚,從發(fā)生現(xiàn)象到產(chǎn)生原因再到可能造成的危害提點到位,并且用程序訓(xùn)練幫助學(xué)生理解記憶。
針對學(xué)生邏輯不清晰的問題,最有效的方法就是要求學(xué)生繪制算法流程圖。表面上看好像是多花了時間,實際上磨刀不誤砍柴工。
(1)數(shù)據(jù)溢出的防范措施
這類溢出危害相對較小,發(fā)生的條件相對固定。學(xué)習(xí)中要對數(shù)據(jù)在計算機內(nèi)存中表現(xiàn)形式及不同數(shù)據(jù)類型的取值范圍比較熟悉。比如無符號char類型的數(shù)據(jù),取值范圍為0~255,符號的char類型,取值范圍在-128~+127,如果有數(shù)據(jù)超過或者接近該范圍,就要格外小心。如果數(shù)據(jù)確實超過這個范圍,不能優(yōu)化,就需要考慮更換更高一級的數(shù)據(jù)類型,如將char類型更換成int類型。
(2)數(shù)組溢出、指針溢出等類型的防范措施
針對此類錯誤,雖然系統(tǒng)不報錯,但是作為程序的設(shè)計者,定義了多少數(shù)組元素,指針參與何種運算,偏轉(zhuǎn)了多少數(shù)據(jù)單元一定要心中有數(shù),不能發(fā)生越界的現(xiàn)象。函數(shù)調(diào)用中尤其是設(shè)計遞歸調(diào)用時要防止遞歸造成的棧的溢出,可以跟蹤遞歸的深度,當(dāng)其大于某個深度就返回。也可以將遞歸算法改為非遞歸算法[2]。
(3)其他防范措施
對C語言的溢出問題還有其他的一些防范措施,如:減少棧空間的需求,不要定義占用內(nèi)存空間較多的 auto 變量,應(yīng)將此類變量修改成指針變量;檢驗內(nèi)存緩沖區(qū)的大?。痪?C語言中那些不安全的庫函數(shù),如strcat()、gets()、scanf()、getc()、fgetc()、 getchar() 等[2]。
C語言語句書寫非常靈活,實現(xiàn)某一個特定功能通常有多種途徑,編寫程序時在享受靈活帶來的方便的同時,一定要注意避免溢出問題。如果不慎出現(xiàn)了這類錯誤,一定要能根據(jù)現(xiàn)象冷靜分析并最終解決問題。