眾所周知,我們在編程的時候經常會在函數中聲明局部變量(包括普通類型的變量、指針、引用等等)。
同時,為了滿足程序功能的需要,函數的返回值也經常是指針類型或是引用類型,而這返回的指針或是引用也經常指向函數中我們自己聲明的局部變量。
這樣,程序在某些情況下就可能存在一定的問題。看似很簡單的問題,通過仔細的分析,我們就能夠更好的理解C++中內存分配和釋放的問題。
好,廢話不多說,我們進入正題。首先,簡單介紹一下程序的內存區域的分配:
程序的內存分配
①堆區(heap)。這一部分主要是由程序開發人員自己通過new和malloc等操作創建的“對象”所保存的內存區域,該區域的變量需要開發人員自己釋放。存儲方式類似鏈表。
②棧區(stack)。這一部分主要是程序中的函數所以及其中的變量所占用的內存區域(包括了main函數)。存儲方式類似棧。
③全局區或常量區(static)。這一部分主要是存放全局變量、靜態數據、常量。程序結束後由系統釋放。
④文字常量區。這一部分除妖存放常量字符串。 程序結束後由系統釋放。
⑤程序代碼區。程序的二進制代碼。
1、函數中聲明普通變量
void func1()
{
int i;
}
在函數func1()中,我們聲明了一個int變量i,它保存在棧區中,當func1執行完成後,i也將被釋放。
2、函數中聲明指針類型變量
void func2()
{
int *p = (int*)malloc(sizeof(int));
A *a = new A; //A是一個類
}
在函數func2()中,我們聲明了一個int型指針變量p和一個用戶自定義類A指針變量a,它們都保存在堆區中,在函數func2執行完成後,仍然不會被釋放,需要程序開發人員自己使用free()或delete等等進行釋放。否則就會導致內存洩漏的問題。
3、函數返回值為指針或引用類型
函數的返回值為指針和引用類型是非常常見而且實用的,如果我們的函數返回的指針或者引用來自函數中聲明的指針類型變量,則沒有問題。如下:
int* CreatePointerFactory()
{
int *p = (int*)malloc(sizeof(int));
//to do sth here
return p;
}
這樣的代碼也是常見的簡單工廠模式。但是需要注意的是,在通過函數CreatePointerFactory得到指針之後,在完成了相關操作之後,要進行對應的內存釋放工作。最後需要將指針的值設置為NULL,防止野指針的產生。
第二種情況,函數返回的指針或者引用來自函數中聲明的普通類型變量,則會產生非常嚴重的問題。如下:
int* getPointer()
{
int i = 1;
//to do sth here
return &i;
}
//main.cpp
int* p = getPointer();
因為變量i在棧區創造,在getPointer函數執行結束之後,這片內存區域將被釋放,變量i所對應的內存也將被釋放掉。因此,如果我們在其他某處(如主函數中)調用了該函數,那麼返回得到的指針則是非常危險的,指向的位置是沒有任何意義的。
但是,在做實驗的時候,我們會發現,此時在某些編譯器下,打印*p的值,可能仍是1,有的也可能是亂碼。
這裡的原因主要如下:
①不同的編譯器處理和優化不同。
②OS在釋放了這一部分內存之後,並不是完全清空內存中的數據,而只是將標志位flag只為true,表示此塊內存是未被占用的。