對C語言入門程序員來說,管理和使用虛擬存儲器可能是個困難的,容易出錯的任務。與存儲器有關的錯誤屬于那些最令人驚恐的錯誤,因為它們經常在時間和空間上,都在距錯誤源一段距離之后,才表現出來。
將錯誤的數據編寫到錯誤的位置,你的程序可能在最終失敗之前運行了好幾個小時,且使程序中止的位置距離錯誤的位置已經很遠了。
1、間接引用壞指針
在進程的虛擬地址空間中有較大的漏洞,沒有映射到任何有意義的數據。如果我們試圖間接引用一個指向這些洞的指針,那么操作系統就會以段異常終止我們的程序。
而且,虛擬存儲器的某些區域是只讀的。試圖寫這些區域將造成以保護異常終止這個程序。
間接引用壞指針的一個常見示例是經典的scanf錯誤。假設我們想要使用scanf從stdin讀一個整數到變量。做這件事情正確的方法是傳遞給scanf一個格式串和變量的地址:
????
然而,對于c語言程序員初學者而言,很容易傳遞val的內容,而不是它的地址:
????
在這種情況下,scanf將把val的內容解釋為一個地址,并試圖將一個字寫到這個位置。在最好的情況下,程序立即以異常終止。
在最糟糕的情況下,val的內容對應于虛擬存儲器的某個合法的讀/寫區域,于是我們就覆蓋了存儲器,這通常會在相當以后造成災難性的、令人困惑的后果。
2、讀未初始化的存儲器
雖然.bss存儲器位置(諸如未初始化的全局C變量)總是被加載器初始化為零,但是對于堆存儲器卻并不是這樣的。一個常見的錯誤就是假設堆存儲器被初始化為零:
????
在這個示例中,程序員不正確地假設向量y被初始化為零。正確的實現方式是在for循環時將y[i]設置為零,或使用calloc。
3、允許棧緩沖區溢出
如果一個程序不檢查輸入串的大小就寫入棧中的目標換成區,那么這個程序就會有緩沖區溢出錯誤。例如,下面的函數就有緩沖區錯誤,因為gets函數拷貝一個任意長度的串到緩沖區。為了糾正這個錯誤,我們必須使用fgets函數,這個函數限制了輸入串的大?。?br />
4、假設指針和它們指向的對象是相同大小的
一種常見的錯誤是假設指向對象的指針和它們所指向的對象是相同大小的:
????
這里的目的是創建一個由n個指針組成的數組,每個指針都指向一個包含m個int的數組。然而,因為程序員將int **A = (int **)malloc(n * sizeof(int));中將sizeof(int)寫成了sizeof(int),代碼實際創建的是一個int的數組。這段代碼只有在int和指向int的指針大小相同的機器上運行良好。
但是,如果我們在像Alpha這樣的機器上運行這段代碼,其中指針大于int,那么在for(i = 0; i < n; i++)? A[i] = (int *)malloc(m * sizeof(int));將寫到超過A數組末端的地方。因為這些字中的一個很可能是分配塊的邊界標記腳部,所以我們可能不會發現這個錯誤,而沒有任何明顯的原因。
5、造成錯位錯位
錯位錯誤是另一種很常見的覆蓋錯誤發生的原因:
????
這是前面程序的另一個版本。這里我們創建了一個n個元素的指針數組,但是隨后試圖初始化這個數組的n+1個元素,在這個過程中覆蓋了A數組后面的某個存儲器。
6、引用指針,而不是它所指向的對象
如果我們不太注意C操作符的優先級和結合性,我們就會錯誤地操作指針,而不是期望操作指針所指向的對象。比如,考慮下面的函數,其目的是刪除一個有*size項的二叉堆里的第一項,然后對剩下的*size-1項重新建堆。
???
*size—目的是減少size指針指向的整數的值。然而,因為一元—和*運算符優先級相同,從右向左結合,所以代碼實際減少的是指針自己的值,而不是它所指向的整數的值。
如果幸運的話,程序會立即失敗,但是更有可能發生的是,當程序在它執行過程的很后面產生一個不正確的結果時,我們只能在那里抓腦袋了。這里的原則是如果你對優先級和結合性有疑問,就使用括號。使用表達式(*size)--。
7、誤解指針運算
另一種常見的錯誤是忘記了指針的算術操作是以它們指向的對象的大小為單位來進行的,而這種大小單位并不一定是字節。例如,下面函數的目的是掃描一個int的數組,并返回一個指針,指向val的首次出現:
8、引用不存在的變量
沒有太多經驗的C程序員不理解棧的規則,有時會引用不再合法的本地變量,如下列所示:
????
這個函數返回一個指針,指向棧里的一個局部變量,然后彈出它的棧幀。盡管p仍然指向一個合法的存儲器地址,但是它已經不再指向一個合法的變量了。
當以后在程序中調用其他函數時,存儲器將重用它們的幀棧。后來,如果程序分配某個值給*p,那么它可能實際正在修改另一個函數的幀棧中的一個條目,從而帶來潛在地災難性的、令人困惑的后果。
9、引用空閑堆塊中的數據
一個相似的錯誤是引用已被釋放了的堆塊中的數據。如下面的示例,示例中分配了一個整數數組x,之后釋放了塊x,最后又引用了它。
10、引起存儲器泄漏
存儲器泄漏是緩慢、隱形的殺手,當程序員不小心忘記釋放已分配塊,而在堆里創建了垃圾時,會發生這種問題。例如,下面的函數分配了一個堆塊x,然后不釋放它就返回。
????
如果leak經常被調用,堆里就會充滿了垃圾,最糟糕的情況下,會占有整個虛擬地址空間。對于像守護進程和服務器這樣的程序來說,存儲器泄漏是特別嚴重的,根據定義這些程序是不會終止的。
審核編輯:劉清
-
存儲器
+關注
關注
38文章
7452瀏覽量
163606 -
C語言
+關注
關注
180文章
7598瀏覽量
136198 -
虛擬機
+關注
關注
1文章
908瀏覽量
28093
原文標題:C程序中常見的與內存相關的錯誤
文章出處:【微信號:c-stm32,微信公眾號:STM32嵌入式開發】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論