編譯器分配內存空間
在C++中,內存分成5個區,他們分別是堆、棧、自由存儲區、全局/靜態存儲區和常量存儲區
1.棧,就是那些由編譯器在需要的時候分配,在不需要的時候自動清楚的變數的存儲區。裡面的變數通常是局部變數、函數參數等。
2.堆,就是那些由new分配的內存塊,他們的釋放編譯器不去管,由我們的應用程序去控制,一般一個new就要對應一個delete。如果程序員沒有釋放掉,那麼在程序結束後,操作系統會自動回收。
3.自由存儲區,就是那些由malloc等分配的內存塊,他和堆是十分相似的,不過它是用free來結束自己的生命的。
4.全局/靜態存儲區,全局變數和靜態變數被分配到同一塊內存中,在以前的C語言中,全局變數又分為初始化的和未初始化的,在C++裡面沒有這個區分了,他們共同佔用同一塊內存區。
5.常量存儲區,這是一塊比較特殊的存儲區,他們裡面存放的是常量,不允許修改(當然,你要通過非正當手段也可以修改)
② 我們經常看到書上面說的 某某變數的內存單元是編譯器在編譯時候分配的 是什麼意思
所謂在編譯期間分配空間指的是靜態分配空間(相對於用new動態申請空間),如全局變數或靜態變數(包括一些復雜類型的常量),它們所需要的空間大小可以
明確計算出來,並且不會再改變,因此它們可以直接存放在可執行文件的特定的節里(而且包含初始化的值),程序運行時也是直接將這個節載入到特定的段中,不
必在程序運行期間用額外的代碼來產生這些變數。
其實在運行期間再看「變數」這個概念就不再具備編譯期間那麼多的屬性了(諸如名稱,類型,作用
域,生存期等等),對應的只是一塊內存(只有首址和大小),
所以在運行期間動態申請的空間,是需要額外的代碼維護,以確保不同變數不會混用內存。比如寫new表示有一塊內存已經被佔用了,其它變數就不能再用它了;
寫delete表示這塊內存自由了,可以被其它變數使用了。(通常我們都是通過變數來使用內存的,就編碼而言變數是給內存塊起了個名字,用以區分彼此)
內存申請和釋放時機很重要,過早會丟失數據,過遲會耗費內存。特定情況下編譯器可以幫我們完成這項復雜的工作(增加額外的代碼維護內存空間,實
現申請和釋 放)。從這個意義上講,局部自動變數也是由編譯器負責分配空間的。進一步講,內存管理用到了我們常常掛在嘴邊的堆和棧這兩種數據結構。
最後對於「編譯器分配空間」這種不嚴謹的說法,你可以理解成編譯期間它為你規劃好了這些變數的內存使用方案,這個方案寫到可執行文件裡面了(該文件中包含若干並非出自你大腦衍生的代碼),直到程序運行時才真正拿出來執行。
③ C語言 + 單片機-內存分布詳解
C語言內存分區示意圖如下:
1. 代碼區
2. 常量區
3. 全局(靜態)區
.bss段
.data段
4. 堆區(heap)
調用函數參數size_t是分配的位元組大小,返回值是一個void型的指針,該指針指向分配空間的首地址。參數是開辟的內存的首地址。
5. 棧區(stack)
在STM32中,內存分配如下:
1. 隨機存儲器—RAM
2. 只讀存儲器—ROM
STM32F103晶元的內部分區如下圖所示。
編譯程序完成後,可以看到編譯後的大小信息,包括Code、RO-data、RW-data和ZI-data的大小。這有助於開發人員和嵌入式系統設計者更好地管理內存資源並確保程序在目標設備上正常運行。
Code:代碼段,指程序由編譯器生成的可執行的機器指令。
RO-data:數據段,指程序中的只讀數據部分,包括常量、字元串、const定義的變數等。
RW-data:數據段,指初始化為「非0值「的可讀寫數據,程序運行的時候這些數據又會常駐在RAM區,應用程序可以修改其內容。包括初始化為非零的全局變數和靜態變數。
ZI-data:數據段,指初始化為0值的可讀可寫數據,它與RW-data的區別是程序剛運行時這些數據初始值全都為0,程序運行時和RW-data的性質一樣,它們也常駐在RAM區,應用程序可以更改其內容。包括未初始化和初始化為零的全局變數和靜態變數。
ZI-data 的棧空間(Stack)及堆空間(Heap):在C語言中,函數內部定義的局部變數屬於棧空間,而使用malloc動態分配的變數屬於堆空間。在程序中的棧空間和堆空間都是屬於ZI-data區域的,這些空間都會被初始值化為0值。
關於哪些數據存儲在Flash區域,哪些數據存儲在SRAM區域,這涉及到程序的存儲狀態。程序具有靜止和運行兩種狀態,靜止態的程序被存儲在非易失存儲器中,如內部FLASH區域。當程序在運行狀態的時候,程序常常需要修改一些暫存數據,這些數據往往存放在Flash中,但需要被復制到RAM中。
程序存儲分布如下圖展示。當程序存儲到晶元的內部FLASH時,它佔用的空間為Code+RO-data+RW-data的總和。程序在執行的時候,需要佔用內部SRAM空間,佔用的空間為RW-data+ZI-data之和。
結論,想要讓一個程序正常運行,必須滿足以下兩個條件:程序編譯後打開工程的map文件,在map文件的最後一段也可以看到ROM的總大小。
劃分依據及好處:首先區分代碼段和數據段。程序源代碼編譯後的機器指令放在代碼段;數據段包括" .data "、" .bss "、" .rodata ",將程序中定義的全局變數和局部變數都稱為數據段。
把程序的「代碼段」和「數據段」分開存放,數據和指令分別被映射到兩個虛擬內存區域,數據段對進程是可讀寫的,而代碼段對進程是只讀的。現代CPU緩存設計成數據緩存和指令緩存分離,程序的指令和數據分開存放提高緩存命中率。
數據段還需要分 ".data"、".bss"、".rodata",主要根據是否占內存空間、讀寫許可權進行區分。".data"段和".bss"段都是可讀寫的數據段,而".rodata"存放的是只讀數據,主要是一些const變數和字元串常量。".rodata"段單獨設立的好處是可以將屬性映射成只讀,減少修改操作,還可以將".rodata"段存放在只讀存儲器中。
全局變數也有細分初始化和未初始化,把全局變數分開存放,初始化為0和未初始化的全局變數放在BSS區,初始化不為0的全局變數存放在數據區。程序有兩個存儲狀態,靜止狀態的程序被存儲在非易失存儲器中,運行狀態的程序需要修改一些暫存數據存放在內存中,減少從ROM讀數據的次數提高效率。
④ C語言內存管理機制--malloc/calloc/free原理與實現
一、C程序的存儲空間布局
在C程序中,存儲空間布局通常分為棧和堆兩種類型。棧用於函數調用時的局部變數存儲,其大小由編譯器自動管理,遵循後進先出(LIFO)原則。堆用於動態內存分配,可以由程序在運行時動態地請求和釋放內存。
二、Heap內存模型
在堆內存中,malloc所申請的內存主要從堆區域分配。Linux內核通過維護一個break指針來管理堆空間。這個指針指向堆空間的某個地址,從堆起始地址(Heap』s Start)到break之間的地址空間為映射好的(虛擬地址與物理地址的映射,通過MMU實現),可以供進程訪問。從break向上,是未映射的地址空間,訪問這些空間會導致程序報錯。
三、調整break:brk()和sbrk()
break指針最初位於bss段的末尾之後,當break指針升高時,程序可以訪問新分配區域內的任何內存地址,而此時物理內存頁尚未分配,內存會在進程首次試圖訪問這些虛擬內存地址時自動分配新的物理內存頁。
Linux通過brk和sbrk系統調用操作break指針。brk()將break指針設置為指定位置,地址四捨五入到下一個內存頁的邊界處。sbrk()將break指針在原有地址基礎上增加指定的大小。sbrk(0)返回當前break指針的位置。系統對進程所分配的資源有限,包括映射的內存空間。
四、malloc
malloc函數用於在系統中動態分配連續的可用內存。它要求內存大小至少為指定的位元組數,返回指向內存塊起始地址的指針,多次調用不重疊分配地址,實現內存分配和釋放。malloc函數的返回值總是位元組對齊,適合高效訪問C語言數據結構。
五、初探實現malloc
一個簡單實現的malloc函數直接從未映射區域劃出內存,但忽略了記錄分配的內存塊信息,導致內存釋放時無法確定釋放的大小,需要額外數據結構記錄塊信息。
六、正式實現malloc
實現一個完整的malloc需要一個數據結構組織堆內存,每個內存塊包含元信息(大小、空閑狀態、指針)和實際數據區域。查找合適的內存塊、分配新的塊、分裂塊等操作需實現相應函數。
七、calloc的實現
calloc函數用於給一組相同對象分配內存,並初始化它們。實現只需兩次調用malloc,一次分配內存,另一次初始化。
八、free的實現
free函數需要驗證地址的有效性,並解決碎片問題。實現策略包括合並相鄰空閑內存塊,確保釋放的地址與未映射區域之間是空閑的。
九、realloc的實現
realloc函數調整已分配內存的大小。實現包括復制現有內存、調整大小、釋放舊內存等操作。
十、總結
通過上述機制,C語言提供內存管理功能,允許程序動態分配和釋放內存。優化空間和實際應用的內存管理策略如Linux內核夥伴演算法、STL空間配置器等提供了更高效的實現。