內存的分配與釋放,內存泄漏

初始化的重要性

和在使用一個數據以前必需要對數據進行初始化同樣,不然可能會使得數據的值不肯定,那就會給程序埋下很大的隱患,在使用指針以前也必需要對指針進行」初始化「,參見下面的例程1:編程

#include<stdio.h>
int main(void)
{
    int *x;
    *x = 3;
    return 0;
}

這樣的代碼可能會出現段錯誤,由於x指針不知道會指向哪一塊內存,使用*x=3來更改那塊內存的數據有可能訪問到非法內存致使段錯誤,固然也有可能由於沒訪問到非法內存而沒有產生段錯誤,可是一個健壯的程序不容許存在這樣的隱患。segmentfault

再看下面的一個例程2:測試

#include<stdio.h>
#include<stdlib.h>
int main(void)
{
    int *x;
    x = (int *)malloc(sizeof(int));
    //上面一行代碼至關於對指針的初始化,使得指針指向一個合法的內存區域
    //準確的表述應該是,使用malloc動態分配一塊sizeof(int)大小的內存空間,而後讓x指向這塊內存
    *x = 3;    
    //上面這行代碼的方式就不會有訪問非法內存的可能,就不會產生段錯誤
    free(x);
    return 0;
}

指向非法內存的指針

經過指針釋放內存後別忘了將指針置爲NULL,或者將這個指針指向另外一個合法的內存地址,總之不能再有指針指向被釋放過了的非法的內存空間。網站

上面的例程2中直接調用free來釋放內存,由於這是一個很簡短的程序因此運行的效果會正如你但願的那樣,可是假如在一個比較大型的項目中,這樣的寫法就存在着隱患,更爲穩定的程序應該這樣寫,見例程3:線程

#include<stdio.h>
#include<stdlib.h>
int main(void)
{
    int *x;
    x = (int *)malloc(sizeof(int));
    //上面一行代碼至關於對指針的初始化,使得指針指向一個合法的內存區域
    *x = 3;    
    //上面這行代碼的方式就不會有訪問非法內存的可能,就不會產生段錯誤
    free(x);
    x = NULL;
    return 0;
}

有人會說這不是畫蛇添足嗎,確實在這個例程裏面是畫蛇添足,由於這個程序太過簡單,free(x) 以後,程序就運行結束了,不會存在再經過x訪問被釋放了的內存的非法訪問內存的問題。指針

可是假如在一個比較大的程序中,在程序的某個地方使用free(x)釋放了x所指向的內存空間(free操做只是釋放該指針所指向的內存,並不會將指針置爲NULL),可是忘記將x指針置爲NULL,那麼x將還會指向這塊內存,假如後面經過if(x==NULL) 來進行判斷,顯然x不等於NULL,就可能出現非法訪問x所指向的內存(可是這塊內存以前已經被釋放過了)的狀況,顯然會出現很嚴重的錯誤。code

另外須要強調的一點,有可能有多個指針指向同一塊內存,因此這種時候若是釋放了內存空間的話,必須保證全部指向這塊內存的指針都再也不指向這塊內存,能夠是置爲NULL,固然也可使其指向其餘合法的內存地址。對象

下面以一個簡單的例程4展現free以後的指針將還指向原來的內存內存

#include<stdio.h>
int main(){
    int *x;
    x = (int*)malloc(sizeof(int)); //爲x動態分配一塊內存,內存大小爲int型大小
    *x = 3; //將3存儲到分配的內存中去
    printf("%d\n ", x); //以整數形式輸出指針,也就是對應的內存的地址
    printf("%d\n", *x); //輸出內存中存儲的數值
    
    free(x);
    if(x != NULL){
        printf("%d\n", x); //將內存釋放後再輸出指針的值
        printf("%d\n", *x); //看看釋放了內存以後還能不能經過指針訪問這塊內存
    }
    
    x=NULL;
    printf("%d\n", x); //將指針置爲NULL以後再輸出指針的值
    printf("%d\n", *x); //看看將指針置爲NULL以後,再用*x會有什麼效果
    
    return 0;
}

使用gcc編譯的時候(我暫時將源文件命名爲test1.c,使用gcc test1.c -o test1來編譯),會有關於不正確使用指針的警告信息,可是並非語法錯誤,因此仍是能夠編譯經過,可是這就存在潛藏的大問題了。資源

最後運行輸出的結果是:

29540368
3
29540368
0
0
Segmentation fault (core dumped)

因此能夠清晰的看出,經過指針釋放了動態分配的內存以後,指針仍是指向原來的地址,還能夠訪問原來的地址(不過原來的地址中的值可能變了),而最後將指針置爲NULL以後,顯然指針再也不指向原來的地址,並且若是這時候再想經過指針訪問對應的內存,就會報段錯誤。

這個程序演示了幾種不規範的使用指針的方法:

- 使用free釋放了內存以後沒有將指針置爲NULL或者將指針再次指向另外一個合法的內存而致使的再次訪問到已經被釋放了的內存的狀況。Free以後而沒有將指針置爲NULL或者再次指向合法的地址,而後根據`if(pointer != NULL)`來進行判斷指針是否合法實際上是沒有意義的。
- 指針置爲NULL以後錯誤的經過*運算符取指針所指向內存的數據而致使的段錯誤。

另外,關於這個小的測試程序,有一個關於printf使用的問題,我已經在問答網站上問了,請參見:http://segmentfault.com/q/101...

補充:使用Delphi開發語言進行開發的時候,Delphi的類、對象名也須要注意釋放的問題。直接以一個例程5講解

var
        objectA : ClassA;  {聲明一個ClassA類型的變量}
    begin
        objectA := ClassA.Create;  {爲objectA建立實體,要注意的是Delphi的對象的變量名其實就是一個指針}
                                  {因此這裏會建立一個實體,objectA實際上是指針,會指向這個實體}
                                  
        {使用對象進行一些操做}
        
        objectA.Free;    {最後釋放變量}
        objectA:= nil;  {別忘了將變量置爲nil,由於Delphi中的類的對象名就是指針}
    end;

說明:Delphi的面向對象編程中,一個對象名就至關於一個指針!

可能出現內存泄漏的幾種狀況

狀況1

屢次malloc可是沒有對應的釋放次

這樣一個C語言的例子

#include<stdio.h>
int main(){
    int *x;
    int i;
    for(i=0; i<=100; i++)
        x = (int *)malloc(size(int));
    free(x);
    x = NULL;
    return 0;
}

在這個例子中,屢次用malloc分配內存,而後每次將新分配的內存的地址賦值給x,可是x只能指向一個地址,因此最後x只能指向最後一次分配的內存的地址,因此也就只能釋放最後一次分配的內存,而前99次分配的內存由於丟失了地址,因此沒有辦法釋放,只能形成內存泄漏。

相似的dephi的例子多是這樣的:

var
    i: Integer;
    objectA: ClassA;
begin
    for i:=0 to 100 do
    begin
        objectA:= ClassA.Create;
    end;
    
    {...}
    objectA.Free;
    objectA:= nil;
end;

這個delphi的程序,每次ClassA.Create就在內存中建立一個實體,而後將objectA指向這個實體的內存,可是和上面的C程序相似,objectA只能指向一個地址,因此只能指向最後一次建立的對象實體的地址,因此前面99個建立的對象由於丟失了地址,因此沒有辦法釋放,因此也就形成了內存泄露。

固然也有時候能夠這樣使用:就是在某種狀況下,delphi的某個線程類是能夠運行結束的(不是無限循環執行的),而且將FreeOnTerminate設爲True(表示線程運行結束以後會自動釋放線程的相關資源),這時候能夠以這樣的方式建立多個不使用指針保存其內存實體地址的線程類。可是這樣的狀況也實在是少,就算是能夠採起這樣的策略,我仍是建議選擇用個指針保存其地址以保證更爲穩妥。總而言之,Delphi面向對象的一個好的編程規範是:建立類的對象實體,要有一個對象名(指針)保存這個實體的地址。

狀況2

動態分配的內存尚未釋放就直接將指向該內存的指針置爲NULL

這樣的一個C語言的例子

#include<stdio.h>
int main(){
    int *x;
    x = (int *)malloc(sizeof(int));
    //...
    x = NULL;
    //free(x);
    return 0;
}

這樣的程序分配了內存空間,而後x指向這塊內存(注意不要這樣表述:使用malloc爲x指針分配了空間,這樣的表達方式是錯誤的,而應該是malloc分配了內存空間,而後x指針指向這塊內存空間)。可是卻將x置爲NULL,這時候原來分配的內存空間的地址就丟失了,就沒有辦法對其釋放,因此就會致使內存泄漏。

另外關於free的參數是空指針的問題(注意也不要表達成:free釋放指針,應該是:釋放指針所指向的內存空間),我在問答網站上提問了,請參見:http://segmentfault.com/q/101...

相似的Delphi的程序多是這樣的:

var
    ObjectA: ClassA;
begin
    ObjectA:= ClassA.Create;
    {...}
    ObjectA:= nil;
    //ObjectA.Free;    
end;

思考和總結

你可能會說,這種錯誤太明顯了,我怎麼可能會犯呢?

可是,當一個項目特別大的時候,有上萬行,甚至數十萬行的代碼的時候,你還能保證不由於你的不細心或者各類意料以外的偶然而不出現這種狀況嗎?

相關文章
相關標籤/搜索