與內(nèi)存有關(guān)的錯(cuò)誤,屬于那種最令人驚恐的錯(cuò)誤。在時(shí)間和空間上,經(jīng)常在距離錯(cuò)誤源一段距離之后才表現(xiàn)出來(lái)。將錯(cuò)誤的數(shù)據(jù)寫到錯(cuò)誤的位置,你的程序可能在最終失敗之前運(yùn)行了一段時(shí)間。 下面列舉并分析了與內(nèi)存有關(guān)的幾種錯(cuò)誤:
1、間接引用壞指針
如果間接引用一個(gè)指向沒(méi)有任何意義的數(shù)據(jù)的指針,那么操作系統(tǒng)會(huì)以段異常終止程序。如果向只讀區(qū)域中寫入數(shù)據(jù),這些區(qū)域會(huì)以保護(hù)異常終止這個(gè)程序。 一個(gè)常見(jiàn)的經(jīng)典示例是scanf錯(cuò)誤。這個(gè)函數(shù)用處是從標(biāo)準(zhǔn)輸入讀入一個(gè)整數(shù)到一個(gè)變量,正確的寫法是傳遞給scanf一個(gè)格式串和變量的地址:
scanf("%d", &value);
?
然而,常見(jiàn)的書寫錯(cuò)誤如下:
?
scanf("%d", value);
?
這種情況下,scanf將把value內(nèi)容解釋為一個(gè)地址,并試圖將一個(gè)字寫到這個(gè)位置。這會(huì)導(dǎo)致程序出現(xiàn)異常,有時(shí)會(huì)立即終止;有時(shí)會(huì)在相當(dāng)長(zhǎng)的時(shí)間后造成災(zāi)難性、令人困惑的后果。
2、讀未初始化的內(nèi)存
常見(jiàn)的錯(cuò)誤是假設(shè)堆內(nèi)存被初始化為零:
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īng)該假設(shè)新申請(qǐng)的內(nèi)存地址(y指向的地址)被初始化為零;正確的做法是顯式地將y[i]設(shè)置為零,或者使用calloc申請(qǐng)內(nèi)存。
?
3、棧緩沖區(qū)溢出
如果一個(gè)程序不檢查輸入字符串的大小就寫入棧中目標(biāo)緩沖區(qū),那么這個(gè)程序就會(huì)出現(xiàn)緩沖區(qū)溢出的錯(cuò)誤,如下程序:
void buff() { ????char buf[64]; ????gets(buf); ????return; }這個(gè)函數(shù)會(huì)出現(xiàn)緩沖區(qū)溢出錯(cuò)誤,因?yàn)間ets函數(shù)只是簡(jiǎn)單復(fù)制一個(gè)任意長(zhǎng)度的字符串到緩沖區(qū),不限制輸入串的大小。解決這個(gè)問(wèn)題的方法是,可以用限制了輸入串大小的fgets函數(shù)。
?
4、假設(shè)指針和它們指向的對(duì)象大小相同
常見(jiàn)的錯(cuò)誤是,假設(shè)指向?qū)ο蟮闹羔樅退鼈兯赶虻膶?duì)象是相同大小的,示例程序:
int **makeArray(int n, int m) { ????int i; ????int **A = (int **)malloc(n * sizeof(int)); /* 注意此處語(yǔ)句,存在問(wèn)題 */ ????for(i = 0; i < n; i++) ????{ ????????A[j] = (int *)malloc(m * sizeof(int)); ????} ????return A; }此程序的目的是創(chuàng)建一個(gè)由n個(gè)指針組成的數(shù)組,每個(gè)指針都指向一個(gè)包含m個(gè)int的數(shù)組。然而,第4行程序代碼將sizeof(int *)寫成了sizeof(int),代碼實(shí)際上創(chuàng)建的是一個(gè)int的數(shù)組。 這段代碼只有在int和指向int的指針大小相同的機(jī)器上運(yùn)行良好,否則就會(huì)出現(xiàn)錯(cuò)誤。
?
5、內(nèi)存越界
這種錯(cuò)誤會(huì)越界覆蓋原有內(nèi)存的數(shù)據(jù),導(dǎo)致出錯(cuò):
int **makeArray(int n, int m) { ????int i; ????int **A = (int **)malloc(n * sizeof(int)); /* 注意此處語(yǔ)句,存在問(wèn)題 */ ????for(i = 0; i <= n; i++) /* 注意循環(huán)終止條件 */ ????{ ????????A[j] = (int *)malloc(m * sizeof(int)); ????} ????return A; }程序在第6行和第8行試圖初始化這個(gè)數(shù)組的n+1個(gè)元素,這個(gè)過(guò)程會(huì)覆蓋A數(shù)組后面的某個(gè)內(nèi)存位置。
?
6、引用指針,而不是它所指向的對(duì)象
如果不太注意C操作符的優(yōu)先級(jí)和結(jié)合性,我們就會(huì)錯(cuò)誤地操作指針,而不是指針?biāo)赶虻膶?duì)象。如果想要減少某個(gè)指針指向的整數(shù)的值,代碼書寫如下:
?
*ptr--;
?
然而,因?yàn)橐辉\(yùn)算符“--”和“*”的優(yōu)先級(jí)相同,且從右向左結(jié)合。那么上述代碼實(shí)際的效果為*(ptr--),即減少的是指針自己的值,而不是它所指向的整數(shù)的值。
如果對(duì)優(yōu)先級(jí)和結(jié)合性有疑問(wèn)的時(shí)候,就用括號(hào)。修正后的代碼如下:
(*ptr)--;
?
7、誤解指針運(yùn)算
這類錯(cuò)誤是忘記指針的算術(shù)運(yùn)算操作是如何進(jìn)行,是以指針指向的對(duì)象的大小為單位進(jìn)行的,而這種大小單位并不一定是字節(jié)。 例如,掃描一個(gè)int的數(shù)組,并返回一個(gè)指向val首次出現(xiàn)的指針:
int *search(int *p, int val) { ????while(*p && *p != val) ????{ ????????p += sizeof(int); ????} ????return p; }每次循環(huán)時(shí),第5行都把指針加了4(一個(gè)整數(shù)的字節(jié)數(shù)),函數(shù)就不正確地掃描了數(shù)組中每4個(gè)整數(shù)。
?
8、引用不存在的變量
有的C程序員不太理解棧的規(guī)則,有時(shí)會(huì)引用不再合法的局部變量,如下所示:
int *stackref() { ????int val; ????return &val; }
?
這個(gè)函數(shù)返回一個(gè)指針(假設(shè)為ptr),指向棧里的一個(gè)局部變量,然后彈出它的棧幀。盡管ptr仍然指向一個(gè)合法的內(nèi)存地址,但它已經(jīng)不再指向一個(gè)合法的變量了。
以后在程序中調(diào)用其他函數(shù)時(shí),內(nèi)存將重用它們的棧幀。如果程序賦值給*ptr,那么它可能實(shí)際上正在修改另一個(gè)含的棧幀中的數(shù)據(jù),從而潛在地帶來(lái)災(zāi)難性的后果。
9、引用空閑堆塊中的數(shù)據(jù)
引用已經(jīng)被釋放了的堆塊中的數(shù)據(jù)會(huì)導(dǎo)致出錯(cuò)。例如:
int *heapref(int n, int m) { ????int i; ????int *x, *y; ????x = (int *)malloc(n * sizeof(int)); /* 申請(qǐng)內(nèi)存 */ ????... ????free(x); /* 釋放內(nèi)存 */ ????y = (int *)malloc(m * sizeof(int)); ????for(i = 0; i < m; i++) ????{ ????????y[i] = x[i]++; ????} ????return y; }
?
當(dāng)程序在第15行引用x[i]時(shí),數(shù)組x可能已經(jīng)是某個(gè)其他已分配堆塊的一部分了,其內(nèi)容也許被重寫了。導(dǎo)致程序運(yùn)行結(jié)果與預(yù)期不符合,出現(xiàn)錯(cuò)誤。
10、引起內(nèi)存泄漏
內(nèi)存泄漏是緩慢、隱形的殺手,當(dāng)程序員不小心忘記釋放已分配的內(nèi)存塊,而在堆里創(chuàng)建了垃圾時(shí),會(huì)發(fā)生這種問(wèn)題。如下:
void leak(int n) { ????int *x = (int *)malloc(n * sizeof(int)); ????return; }如果經(jīng)常調(diào)用這個(gè)函數(shù),漸漸地堆里會(huì)充滿了垃圾,造成內(nèi)存泄漏。另外,有時(shí)也會(huì)引起程序終止或其他問(wèn)題。
?
小結(jié)
以上總結(jié)了C程序中,管理和使用內(nèi)存常見(jiàn)的錯(cuò)誤類型,并舉例進(jìn)行了說(shuō)明。在實(shí)際的編程中,應(yīng)該避免出現(xiàn)這些錯(cuò)誤,否則會(huì)出現(xiàn)意想不到的后果。?
?
評(píng)論
查看更多