C++入門教程(四十八):堆內存

小古銀的官方網站(完整教程):http://www.xiaoguyin.com/
C++入門教程視頻:https://www.bilibili.com/video/av20868986/

目錄

指針的用途二:保存申請堆內存後返回的地址。這個用途應該用智能指針代替。

堆內存

堆內存在內存上和棧內存的區別是:申請棧內存有上限而且很少,當達到上限時操作系統不再給程序分配內存,程序就會崩潰;而申請堆內存理論上是會得到內存條剩餘可用內存,實際上大多數情況下,申請堆內存是沒有上限的,操作系統都有辦法分配給你(操作系統的虛擬內存技術)。

堆內存在使用上與棧內存不同的是:棧內存運行到離開作用域時操作系統會自動回收內存;而堆內存則需要程序員自己手動釋放。如果不斷申請堆內存而不釋放的話,可用的內存將越來越少,直至操作系統卡死。

堆內存的申請和釋放

基礎示例

向操作系統申請堆內存使用關鍵字new,將堆內存釋放還給操作系統使用關鍵字delete

#include <iostream>

int main(void)
{
    int *pointer = new int(2333); // 申請int類型的堆內存, 並且將內存數據初始化爲2333
    std::cout << *pointer << std::endl; // 輸出內存中的數據
    delete pointer; // 釋放堆內存
    pointer = nullptr; // 賦值爲空, 防止誤操作
    return 0;
}

輸出結果:

2333

基礎講解

以下代碼只是申請堆內存:

auto pointer = new int;

以下是申請堆內存然後初始化內存中的數據(前面教程說過int()就是int(0)):

auto pointer = new int();

當使用new向操作系統申請堆內存後,操作系統就會找有沒有可用內存,如果有,操作系統就會將這份內存分配給程序,並把這份內存的首地址返回給程序以供對內存操作,然後你需要用指針保存下來;如果沒有可用內存,則new會拋出異常(異常將在後續講解),但是現代的操作系統有虛擬內存技術,所以不會沒有可用內存。

當不需要內存的時候,需要用delete 堆內存的地址;手動釋放堆內存。由於內存已經被釋放,指針再保存這個地址將很有可能誤操作,所以需要及時賦值爲nullptr

新手常犯錯誤

錯誤一:

#include <iostream>

int main(void)
{
    {
        auto pointer = new int(2333);
    } // 錯誤, 棧內存自動釋放, 堆內存沒有被釋放
    return 0;
}

前面教程說得很清楚,聰明的你肯定知道棧內存會自動釋放, 堆內存要手動釋放;應該也知道pointer是棧內存,只是保存了堆內存的地址,並不是堆內存。

上面代碼中,pointer作爲變量是棧內存,會自動釋放;但是new出來的是堆內存,需要手動釋放。而上面代碼只有pointer知道堆內存的地址,當pointer自動釋放後,堆內存地址就沒有任何人也沒有任何變量知道了。那麼這份堆內存就找不到了,也就是說,這份堆內存永遠都釋放不了,這種情況叫做內存泄漏

錯誤二,這是更常見的粗心大意:

#include <iostream>

int * create_int(int value)
{
    return new int(value); // 堆內存不會自動釋放, 這裏沒有錯
}

int main(void)
{
    auto pointer = new int(666);
    pointer = create_int(2333); // 錯誤, 內存泄漏
    delete pointer; // 不良設計
    pointer = nullptr;
    return 0;
}

這裏有兩個問題,是新手常見的。雖然上面代碼新手也可以清晰看出來會內存泄漏,但是新手代碼一寫長一點,就很容易忽略這個內存泄漏,這是我看到很多萌新容易出錯的地方。下面詳細講解。

對於內存泄漏的錯誤,新手寫代碼寫着寫着就萌萌噠,就覺得pointer = create_int(2333);是給pointer“賦值”,這個真的是賦值,不過要看清楚,賦的是地址而不是2333喲~。給pointer再賦值,導致原來的堆內存沒人知道它的地址,從而造成內存泄漏。經常加班的人也容易犯這個錯誤,因爲加班使人萌萌噠。 o(〃’▽’〃)o

接下來就是說這個不良的設計,這種設計很容易使人忘記delete

用堆內存數據來進行處理時,一般都會盡量在同一個作用域內創建和釋放,即在同一個作用域內newdelete,這個是正常的用法。以下這種做法是非常容易出錯的:在一個函數裏new之後處理一點,然後在其他函數又處理一點再delete,這個真的是作死。 Ծ‸ Ծ

如果功能是類似我上面代碼這種:需要用一個函數創建一份內存,那麼也請務必再寫一個對應的釋放函數。例如:create_int()release_int()一套、allocate()deallocate()一套。不要只給出單獨一個,好寂寞的。

使用智能指針就沒有以上煩惱。

注意異常

異常將在後續詳細講解,現在先大概瞭解。

以下代碼看上去不會內存泄漏。而實際上當text是字符串"abc"(即不是數字)時,std::stoi()會拋出異常,並且會在這一行令函數直接退出,不繼續執行剩下的代碼,這樣也會導致內存泄漏。

int to_int(const std::string &text)
{
    // 假設這個指針是非常必要的
    auto pointer = new int();
    *pointer = std::stoi(text); // 當text是"abc"時會直接退出函數導致內存泄漏
    auto num = *pointer;
    delete pointer;
    pointer = nullptr;
    return num;
}

雖然我知道你看這個可能會有點蒙,但是沒關係,我的重點不是這個。我的重點是:

使用智能指針就不需要注意這個。

補充知識(瞭解即可)

虛擬內存技術:簡單地說,就是操作系統不夠內存分配的時候,將不太忙的程序(包括操作系統)的內存暫時用硬盤保存,保存完畢後將這個內存分配給需要的程序。由於操作系統也會將自身的內存給程序,而且硬盤的讀寫慢,當操作系統自身不夠內存時,就會直接卡死。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章