1. 程式人生 > 實用技巧 >C++ 棧記憶體與堆記憶體小探究

C++ 棧記憶體與堆記憶體小探究

實驗方式:嘗試以不同方式建立超大號二維陣列
測試程式碼:

#include <iostream>
using namespace std; 
const int maxn=1000000;
class C{  
	int arr[maxn];
};
//int a[maxn]; 全域性大陣列
//C a; 含大陣列的全域性物件 
int main(){
	//int a[maxn]; 區域性大陣列
	//C a; 含大陣列的區域性物件
	//C a=*new C; 通過new建立的含大陣列的物件 
	//C* a=new C; 通過new建立的指向含大陣列的物件的指標 
	cout<<"hello";
	return 0;
}

結果:
全域性大陣列:正常
含大陣列的全域性物件:正常
區域性大陣列:段錯誤
含大陣列的區域性物件:段錯誤
通過new建立的含大陣列的物件 :段錯誤
通過new建立的指向含大陣列的物件的指標 :正常

分析:C++中可能存在像java一樣的堆疊記憶體機制,對於不同作用域的變數有不同記憶體管理策略

知識匯入:
這篇文章講的真是太好了:
關於堆疊的講解(我見過的最經典的)
1、棧區(stack)— 由編譯器自動分配釋放 ,存放函式的引數值,區域性變數的值等。其操作方式類似於資料結構中的棧。
2、堆區(heap) — 一般由程式設計師分配釋放, 若程式設計師不釋放,程式結束時可能由OS回收 。注意它與資料結構中的堆是兩回事,分配方式倒是類似於連結串列,呵呵。
3、全域性區(靜態區)(static)—,全域性變數和靜態變數的儲存是放在一塊的,初始化的全域性變數和靜態變數在一塊區域, 未初始化的全域性變數和未初始化的靜態變數在相鄰的另一塊區域。 - 程式結束後有系統釋放
4、文字常量區—常量字串就是放在這裡的。 程式結束後由系統釋放
5、程式程式碼區—存放函式體的二進位制程式碼。

在WINDOWS下,棧的大小是2M或4M

變數型別+變數名出來的變數都在棧裡,new出來的記憶體都在堆裡,全域性變數有自己的地兒。棧記憶體有限,少在棧上開大陣列。

達成成就:

Stack Overflow


2020.7.13更新

過去這一年我學了組合語言、作業系統,又讀完了《CSAPP》,對記憶體佈局又有了新的理解。對於一個程式來說,它的記憶體佈局可以概括為一張圖:

逐個解釋一下:

  • 核心記憶體:作業系統核心的程式碼和資料,無法直接訪問這塊記憶體。注意使用各種作業系統提供的函式直接不是訪問作業系統的程式碼,而是通過“中斷機制”將控制權暫時交給了作業系統,再由作業系統執行對應的例程,執行完畢後再將控制權交還給使用者程式。
  • 使用者棧:存放了函式內定義的臨時變數,傳給函式的引數,以及函式的呼叫棧。所謂的函式的呼叫棧,上個圖就是:

在這幅圖裡main呼叫了solve呼叫了dfs,這一層層巢狀的資訊就儲存在使用者棧中。我們常講的棧溢位、爆棧、stack over flow指的就是臨時變數佔記憶體太多或函式巢狀呼叫過多,同時使用者棧的記憶體空間又太小,導致了記憶體溢位。

  • 共享庫的記憶體對映區域:我們以快速排序函式qsort()為例,如果每個要用qsort的使用者程式都在自己的程式碼裡嵌入qsort的程式碼就造成了重複,是一種浪費行為。為了節省空間作業系統只在記憶體中存放一份qsort的程式碼,對於每個需要呼叫qsort的程式,都在他們的記憶體佈局裡新增一個對應的“對映”,類似於只添加了一個快捷方式,並不佔用實際空間。
  • 執行時堆:所有通過malloc()函式、new關鍵字等等方式動態分配的記憶體都在這一區域。
  • 讀寫段:包括了可讀可寫的全域性變數和用static定義的變數,其中.data指初始化完成的,.bss指初始化未完成的
  • 只讀程式碼段:.init指作業系統初始化程式時用到的一段小函式,.text指程式的程式碼,只能讀不能修改,.rodata指程式中定義的各種常量,典型的如提前定義好的字串常量。

引申閱讀:記憶體中的堆和棧到底是什麼 - 簡書

參考內容:《深入理解計算機系統 第三版》連結器相關章節