開篇
與內存有關的錯誤屬于那種最令人驚恐的錯誤。在時間和空間上,經常在距離錯誤源一段距離之后才表現出來。將錯誤的數據寫到錯誤的位置,你的程序可能在最終失敗之前運行了一段時間。
下面列舉并分析與內存有關的幾種錯誤:
如果間接引用一個指向沒有任何意義的數據的指針,那么操作系統會以段異常終止程序。如果向只讀區域中寫入數據,這些區域會以保護異常終止這個程序。
一個常見的經典示例是 scanf 錯誤。這個函數用處是從標準輸入讀入一個整數到一個變量,正確的寫法是傳遞給 scanf 一個格式串和變量的地址:
scanf("%d", &value);然而,常見的書寫錯誤如下:
scanf("%d", value);這種情況下,scanf 將把 value 內容解釋為一個地址,并試圖將一個字寫到這個位置。這會導致程序出現異常,有時會立即終止;有時會在相當長的時間后造成災難性、令人困惑的后果。
常見的錯誤是假設堆內存被初始化為零:
int *matvec(int **A, int *x, int n)
{
????int i, j;
????int *y = (int *)malloc(n * sizeof(int));
????for(i = 0; i < n; i++)
????{
????????for(j = 0; j < n; j++)
????????{
????????????y[i] += A[i][j] * x[j]
????????}
????}
????return y;
}示例中不應該假設新申請的內存地址(y 指向的地址)被初始化為零。正確的做法是顯式地將 y[i] 設置為零,或者使用 calloc 申請內存。
如果一個程序不檢查輸入字符串的大小就寫入棧中目標緩沖區,那么這個程序就會出現緩沖區溢出的錯誤。如下程序:
void buff()
{
????char buf[64];
????gets(buf);
????return;
}這個函數會出現緩沖區溢出錯誤,因為 gets 函數只是簡單復制一個任意長度的字符串到緩沖區,不限制輸入串的大小。解決這個問題的方法是,可以用限制了輸入串大小的 fgets 函數。
常見的錯誤是,假設指向對象的指針和它們所指向的對象是相同大小的,示例程序:
int **makeArray(int n, int m)
{
????int i;
????int **A = (int **)malloc(n * sizeof(int)); /* 注意此處語句,存在問題 */
????for(i = 0; i < n; i++)
????{
????????A[j] = (int *)malloc(m * sizeof(int));
????}
????return A;
}此程序的目的是創建一個由 n 個指針組成的數組,每個指針都指向一個包含 m 個 int 的數組。然而,第 4 行程序代碼將 sizeof(int *)寫成了 sizeof(int),代碼實際上創建的是一個 int 的數組。
這段代碼只有在 int 和指向 int 的指針大小相同的機器上運行良好;否則,就會出現錯誤。
這種錯誤會越界覆蓋原有內存的數據,導致出錯:
int **makeArray(int n, int m)
{
????int i;
????int **A = (int **)malloc(n * sizeof(int)); /* 注意此處語句,存在問題 */
????for(i = 0; i <= n; i++) /* 注意循環終止條件 */
????{
????????A[j] = (int *)malloc(m * sizeof(int));
????}
????return A;
}程序在第 6 行 和第 8 行試圖初始化這個數組的 n+ 1 個元素,這個過程會覆蓋 A 數組后面的某個內存位置。
如果不太注意 C 操作符的優先級和結合性,我們就會錯誤地操作指針,而不是指針所指向的對象。如果想要減少某個指針指向的整數的值,代碼書寫如下:
*ptr--;然而,因為一元運算符 “--” 和 “*” 的優先級相同,且從右向左結合。那么上述代碼實際的效果為 *(ptr--),即減少的是指針自己的值,而不是它所指向的整數的值。
如果對優先級和結合性有疑問的時候,就用括號。修正后的代碼:
(*ptr)--;這類錯誤是忘記指針的算術運算操作是如何進行。是以指針指向的對象的大小為單位進行的,而這種大小單位并不一定是字節。例如,掃描一個 int 的數組,并返回一個指向 val 首次出現的指針:
int *search(int *p, int val)
{
????while(*p && *p != val)
????{
????????p += sizeof(int);
????}
????return p;
}每次循環時,第 5 行都把指針加了 4 (一個整數的字節數),函數就不正確地掃描了數組中每 4 個整數。
有的 C 程序員不太理解棧的規則,有時會引用不再合法的局部變量,如下所示:
int *stackref()
{
????int val;
????return &val;
}這個函數返回一個指針(假設為 ptr),指向棧里的一個局部變量,然后彈出它的棧幀。盡管 ptr 仍然指向一個合法的內存地址,但是它已經不再指向一個合法的變量了。
以后在程序中調用其他函數時,內存將重用它們的棧幀。如果程序賦值給 *ptr,那么它可能實際上正在修改另一個含的棧幀中的數據。從而潛在地帶來災難性的后果。
引用已經被釋放了的堆塊中的數據會導致出錯。例如:
int *heapref(int n, int m)
{
????int i;
????int *x, *y;
????x = (int *)malloc(n * sizeof(int)); /* 申請內存 */
????...
????free(x); /* 釋放內存 */
????y = (int *)malloc(m * sizeof(int));
????for(i = 0; i < m; i++)
????{
????????y[i] = x[i]++;
????}
????return y;
}當程序在第 15 行引用 x[i] 時,數組 x 可能已經是某個其他已分配堆塊的一部分了,其內容也許被重寫了。導致程序運行結果與預期不符合,出現錯誤。
內存泄漏是緩慢、隱形的殺手,當程序員不小心忘記釋放已分配的內存塊,而在堆里創建了垃圾時,會發生這種問題。如下:
void leak(int n)
{
????int *x = (int *)malloc(n * sizeof(int));
????return;
}如果經常調用這個函數,漸漸地,堆里會充滿了垃圾,造成內存泄漏。有時會引起程序終止或其他問題。
以上總結了 C 程序中,管理和使用內存常見的錯誤類型,并舉例進行了說明。在實際的編程中,應該避免出現這些錯誤,否則會出現意想不到的后果。
參考資料,深入理解計算機系統。
本文來源網絡,免費傳達知識,版權歸原作者所有。如涉及作品版權問題,請聯系我進行刪除。
《嵌入式Linux驅動大全》