1. 程式人生 > >c程序內存模型

c程序內存模型

pan 新的 分享 技術分享 ack 常量 平衡 中斷 而不是

這篇文章主要記錄一下c程序運行時內存空間如何使用。(摘抄自網絡)

在一個多任務操作系統中的每個進程都運行在它自己的內存“沙箱”中。這個沙箱是一個虛擬地址空間(virtual address space),在 32 位系統中它總共有 4GB 的內存地址空間,包含內核空間和用戶空間:

技術分享圖片

這些虛擬地址是通過內核頁表(page table)映射到物理地址的,並由操作系統內核維護。在典型的Linux 中,內核/用戶空間的劃分比例為1:3。但是,這並不意味著內核就使用了1G物理內存,它只使用了很少一部分可用的地址空間映射到其所需要的物理內存。內核空間的頁表被標記,因此,如果一個用戶模式的程序嘗試去訪問它,將觸發一個頁面故障錯誤。

在 Linux 中,內核空間是始終存在的,並且在所有進程中都映射相同的物理內存。內核代碼和數據總是可尋址的,準備隨時去處理中斷或者系統調用。相比之下,用戶模式中的地址空間,在每次進程切換時都會發生變化:

技術分享圖片

一個 Linux 進程的標準段布局如下:

技術分享圖片

  1. 在進程地址空間中最高的段是,存儲函數參數和本地變量。調用一個方法或者函數將推送一個新的棧幀stack frame到這個棧。當函數返回時這個棧幀被刪除。進程中的每個線程都有它自己的棧。如果向棧中壓入過多數據,可能會導致棧空間被耗盡,這將觸發一個頁面故障,系統會調用相關函數來

    檢查棧的增長是否正常。如果棧的大小低於 RLIMIT_STACK

    的值(一般是 8MB 大小),那麽這是一個正常的棧增長,否則可能是發生了未知問題。這是一個棧大小按需調節的常見機制。但是,棧的大小達到了上述限制,將會發生一個棧溢出,並且,程序將會收到一個段故障Segmentation Fault錯誤。當映射的棧區為滿足需要而擴展後,在棧縮小時,映射區域並不會收縮。訪問任何未映射的內存區域都是非法的,並將觸發一個頁面故障,導致段故障。但

    動態棧增長是唯一例外的情況,當它去訪問一個未映射的內存區域是允許的。

  2. 在棧的下面,有內存映射段。在這裏,內核將文件內容直接映射到內存。任何應用程序都可以通過 Linux 的 mmap() 系統調用來請求一個映射,內存映射是實現文件 I/O 的高效的方式。因此,它經常被用於加載動態庫。有時候,也被用於去創建一個匿名內存映射,這種映射經常被用做程序數據的替代。在 Linux 中,如果你通過 malloc() 去請求一個大的內存塊,C 庫將會創建這樣一個匿名映射而不是使用堆內存。這裏所謂的“大”表示是超過了MMAP_THRESHOLD
    設置的字節數,它的缺省值是 128 kB,可以通過mallopt()去調整這個設置值。
  3. 內存映射段下面是,堆提供運行時內存分配,它分配的數據生存期要長於分配它的函數。大多數編程語言都為程序提供了堆管理支持,在 C 中,堆分配的接口是 malloc() 一族。

    如果在堆中有足夠的空間可以滿足內存請求,它可以由編程語言運行時來處理內存分配請求,而無需內核參與。否則將通過 brk() 系統調用來擴大堆以滿足內存請求所需的大小。堆管理比較復雜,在面對我們程序的混亂分配模式時,它通過復雜的算法,努力在速度和內存使用效率之間取得一種平衡。堆也會出現碎片化

  4. 堆下面是BSS段,在 C 中,BSS 保存未初始化的全局變量、靜態變量的內容,它的值在源代碼中並沒有被程序員設置。
  5. 接下來是數據段,用於保存在源代碼中已初始化的全局變量、靜態變量的內容。這個內存區域是非匿名的。它映射了程序的二進值鏡像上的一部分,即在源代碼中給定初始化值的全局變量、靜態變量內容。

  6. 最後一部分就是代碼段,通常是指用來存放程序執行代碼的一塊內存區域。這部分區域的大小在程序運行前就已經確定,並且內存區域通常屬於只讀。在代碼段中,也有可能包含一些只讀的常數變量,例如字符串常量等。

最後關於變量保存位置:

1、經過初始化的全局變量和靜態變量保存在數據段中。
2、未經初始化的全局變量和靜態變量保存在BSS段。
3、函數內部聲明的局部變量保存在堆棧段中。
4、const修飾的全局變量保存在文本段中,const修飾的局部變量保存在堆棧段中。
5、字符串常量保存在文本段中。

c程序內存模型