
任何一個程序正常運行都需要內存資源用來存放諸如變量、常量、函數代碼等等。這些不同的內容所存儲的內存區域是不同的且不同的區域有不同的特性。因此我們需要研究內存布局逐個了解不同內存區域的特性。每個 C 語言進程都擁有一片結構相同的虛擬內存不同的程序會占據不同區段的物理內存但經過系統的變換和映射它們的虛擬內存的布局是完全一樣的。將其中一個 C 語言進程的虛擬內存放大來看會發現其內部包含區域棧stack堆heap數據段代碼段1.1 棧內存棧是現代計算機程序極為重要的組成部分幾乎所有的函數調用都依賴于棧進行實現。棧空間中包括函數返回地址記錄了函數執行完成后退出函數調用時需要執行的下一條指令命令行參數 ./a.exe abc 123局部變量包括形參函數調用過程中使用的非靜態局部變量均默認保存在棧上函數退出后這些變量也會被釋放而不可使用保存的寄存器環境變量等保存了函數調用前后需要保持不變的寄存器在程序運行過程中每次執行函數調用都會建立一個棧幀連續的過程調用會讓棧不斷朝著“棧頂”的方向進行擴展在函數調用完成后棧幀會被自動釋放棧也會隨之縮減。在大多數體系結構上棧的擴展方向都是由高地址向低地址進行擴展。棧內存有什么特點空間有限尤其在嵌入式環境下。因此不可以用來存儲尺寸太大的變量。Linux 棧內存大小為 8M系統為函數分配棧內存時遵循從上高地址往下低地址分配原則由系統規定的我們無法干預每當一個函數被調用時系統將自動分配一段棧內存給該函數用于存放其局部變量每當一個函數有退出時系統將自動回收其棧內存棧空間申請的變量隨著函數結束空間自動釋放在函數的作用域中定義局部變量是保存在棧中在函數結束時系統會自動回收此函數中創建的棧空間即 return 之后不可再訪問函數的局部變量因此返回一個局部變量的地址通常是錯誤的。# include stdio.h int *add(int a, int b) { int sum; sum a b; return sum; //不能返回函數的局部變量地址 } int main(void) { int *p add(2,5); }1.2 堆內存堆內存heap又被稱為動態內存、自由內存簡稱堆。堆是唯一可被開發者自定義的區段開發者可以根據需要申請內存的大小、決定使用的時間長短等。但又由于這是一塊系統“飛地”所有的細節均由開發者自己把握系統不對此做任何干預給予開發者絕對的“自由”但也正因如此對開發者的內存管理提出了很高的要求。對堆內存的合理使用幾乎是軟件開發中的一個永恒的話題。例如 malloc 它分配的內存來自進程的虛擬內存空間。虛擬內存是操作系統提供的一種內存管理機制它允許程序使用比物理內存更多的內存堆內存基本特征相比棧內存堆的總大小僅受限于物理內存在物理內存允許的范圍內系統對堆內存的申請不做限制。相比棧內存堆內存從下往上增長。堆內存是匿名的只能由指針來訪問。自定義分配的堆內存除非開發者主動釋放否則永不釋放直到程序退出。堆內存使用完成后一定要進行手動釋放內存否則可能會使內存泄漏導致程序崩潰相關API申請堆內存malloc() / calloc()功能申請堆內存所需頭文件#include stdlib.h函數原型void *malloc(size_t size);void *calloc(size_t count, size_tsize);參數size所申請的一塊堆內存的大小單位是字節count所申請的堆內存的塊數所有的內存塊是連續分布的、無間隔的返回值成功返回所申請的內存空間的指針失敗NULL#include stdio.h #include stdlib.h int main() { // malloc: 申請一塊內存內容是隨機的 int *p1 (int *)malloc(5 * sizeof(int)); // calloc: 申請 5 塊內存申請的內存會自動清零 int *p2 (int *)calloc(5, sizeof(int)); free(p1); free(p2); return 0; }清零堆內存bzero()功能清零指定內存所需頭文件#include strings.h函數原型void bzero(void *s, size_t n);參數s指向要清零的內存的指針n要清零的內存大小單位是字節返回值無#include stdio.h #include stdlib.h #include strings.h int main() { int arr[5] {1, 2, 3, 4, 5}; bzero(arr, sizeof(arr)); // 整塊清零 printf(bzero后 arr[0] %d, arr[4] %d\n, arr[0], arr[4]); return 0; }釋放堆內存free()功能釋放堆內存所需頭文件#include stdlib.h函數原型voidfree(void *ptr);參數ptr堆內存指針返回值無#include stdio.h #include stdlib.h int main() { int *p (int *)malloc(3 * sizeof(int)); p[0] 10; p[1] 20; p[2] 30; printf(使用前: p[1] %d\n, p[1]); free(p); // 釋放堆內存 p NULL; // 置空防止懸空指針 printf(已釋放p NULL\n); return 0; }realloc()申請的堆內存,在原來內存基礎上可進行擴容。// 申請堆空間 char *p calloc(1, sizeof(char)); if (p NULL) { printf(calloc failed:); return -1; } char buf[] afjoiajfajflajlfjalflafjslfjsljfls; // 擴容如果不擴容會溢出 char *ptr realloc(p, 100); // 是將空間大小從 1 擴容到 100不是擴容了 100注意malloc()申請的堆內存默認情況下是隨機值一般需要用 bzero() 來清零。calloc()申請的堆內存默認情況下是已經清零了的不需要再清零。free()只能釋放堆內存不能釋放別的區段的內存。釋放內存的含義釋放內存意味著將內存的使用權歸還給系統。釋放內存并不會改變指針的指向。釋放內存并不會對內存做任何修改更不會將內存清零。內存泄漏示例以下是一個典型的內存泄漏代碼在函數中申請堆內存后忘記釋放導致每次調用都泄漏內存。#include stdio.h #include stdlib.h void leak_memory() { int *p (int *)malloc(100 * sizeof(int)); // 忘記調用 free(p)函數返回后指針 p 丟失內存卻未歸還系統 } int main() { for (int i 0; i 100000; i) { leak_memory(); // 每次調用泄漏 400 字節累計約 40MB } printf(程序結束\n); return 0; }后果隨著循環不斷調用leak_memory()堆內存持續增長而永不釋放。在長時間運行的程序如服務器進程中內存泄漏會逐漸耗盡可用內存最終導致系統 OOMOut of Memory或程序崩潰。即使程序結束泄漏的內存在程序運行期間也無法被其他進程使用。如何避免配對使用 malloc 和 free每次調用malloc后確保在合適的時機調用free。使用內存檢測工具如 ValgrindLinux或 Dr. MemoryWindows檢測內存泄漏。遵循誰申請誰釋放原則明確內存的所有權和釋放責任避免遺漏。在函數退出前統一釋放用goto或if分支確保所有路徑都能釋放已申請的內存。1.3 堆與棧的區別1、 管理方式不同:棧由系統自動分配空間同時系統自動釋放空間。堆需要程序員手動申請并且手動釋放并指明大小。在 C 語言中 malloc 函數申請釋放 free 函數在 C 中 new 和 delete 實現。2、 存儲內容不同:棧在函數調用時棧中存放的是函數中各個參數(局部變量)堆一般是在堆的頭部用一個字節存放堆的大小不是必須的。堆中的具體內容由程序員安排。3、 空間大小不同:棧獲取空間較小。堆比較靈活比較大。4、 能否產生碎片不同:棧不會產生碎片空間連續。堆采用的是鏈表的存儲方式會產生碎片。5、 分配方式不同:棧靜態分配由編譯器完成堆動態分配的使用malloc函數6、 分配效率不同棧由系統自動分配速度較快。但程序員是無法控制的。堆由malloc分配的內存一般速度比較慢而且容易產生內存碎片不過用起來方便。7、 生長方向不同棧向低地址擴展的數據結構是一塊連續的內存的區域。堆向高地址擴展的數據結構是不連續的內存區域。這是由于系統是用鏈表來存儲的空閑內存地址的自然是不連續的而鏈表的遍歷方向是由低地址向高地址。