C程序內存布局


> 關注公眾號【高性能架構探索】,第一時間獲取干貨;回復【pdf】,免費獲取計算機經典書籍

 

作為計算機專業的來說,程序入門基本都是從C語言開始的,了解C程序中的內存布局,對我們了解整個程序運行,分析程序出錯原因,會起到事半功倍的作用 。

C程序的內存布局包含五個段,分別是STACK(棧段),HEAP(堆段),BSS(以符號開頭的塊),DS(數據段)和TEXT(文本段)。

每個段都有自己的讀取,寫入和可執行權限。如果程序嘗試以不允許的方式訪問內存,則會發生段錯誤,也就是我們常說的coredump。

段錯誤是導致程序崩潰的常見問題。核心文件(核心轉儲文件)也與段錯誤相關聯,開發人員使用該文件來查找崩潰的根本原因(段錯誤)。

下面我們將深入這五個段,更加詳細的講解每個段在程序開發或者運行中的作用。

High Addresses ---> .----------------------.
                    |      Environment     |
                    |----------------------|
                    |                      |   Functions and variable are declared
                    |         STACK        |   on the stack.
base pointer ->     | - - - - - - - - - - -|
                    |           |          |
                    |           v          |
                    :                      :
                    .                      .   The stack grows down into unused space
                    .         Empty        .   while the heap grows up. 
                    .                      .
                    .                      .   (other memory maps do occur here, such 
                    .                      .    as dynamic libraries, and different memory
                    :                      :    allocate)
                    |           ^          |
                    |           |          |
 brk point ->       | - - - - - - - - - - -|   Dynamic memory is declared on the heap
                    |          HEAP        |
                    |                      |
                    |----------------------|
                    |          BSS         |   Uninitialized data (BSS)
                    |----------------------|   
                    |          Data        |   Initialized data (DS)
                    |----------------------|
                    |          Text        |   Binary code
Low Addresses ----> '----------------------'

  • 它位於較高的地址,與堆段的增長和收縮方向正好相反。
  • 函數的局部變量存在於棧上
  • 調用函數時,將在棧中創建一個棧幀。
  • 每個函數都有一個棧幀。
  • 棧幀包含函數的局部變量參數和返回值。
  • 棧包含一個LIFO結構。函數變量在調用時被壓入棧,返回時將函數變量從棧彈出。
  • SP(棧指針)寄存器跟蹤棧的頂部。
#include 
int main(void) {
    int data; // 局部變量,存儲在棧上
    return 0;
}

  • 用於在運行時分配內存。
  • 由內存管理函數(如malloc、calloc、free等)管理的堆區域,這些函數可以在內部使用brk和sbrk系統調用來調整其大小。
  • 堆區域由進程中的所有共享庫和動態加載的模塊共享。
  • 它在堆棧的相反方向上增長和收縮。
#include 
int main(void) {
    char *pStr = malloc(sizeof(char)*4); //pStr指向堆地址
    return 0;
}

BSS(未初始化的數據塊)

  • 包含所有未初始化的全局和靜態變量。
  • 此段中的所有變量都由零或者空指針初始化。
  • 程序加載器在加載程序時為BSS節分配內存。
#include 
int data1; // 未初始化的全局變量存儲在BSS段
int main(void) {
    static int data2;  // 未初始化的靜態變量存儲在BSS段
    return 0;
}

DS(初始化的數據塊)

  • 包含顯式初始化的全局變量和靜態變量。
  • 此段的大小由程序源代碼中值的大小決定,在運行時不會更改。
  • 它具有讀寫權限,因此可以在運行時更改此段的變量值。
  • 該段可進一步分為初始化只讀區和初始化讀寫區。
#include 
int data1 = 10 ; //初始化的全局變量存儲在DS段
int main(void) {
    static int data2 = 3;  //初始化的靜態變量存儲在DS段
    return 0;
}

TEXT

  • 該段包含已編譯程序的二進制文件。
  • 該段是一個只讀段,用於防止程序被意外修改。
  • 該段是可共享的,因此對於文本編輯器等頻繁執行的程序,內存中只需要一個副本。

深入

現在有一個簡單的程序,代碼如下:

#include  
  
int main(void) { 
    return 0; 
}

我們通過如下命令進行編譯

gcc -g a.cc -o a

然后通過size命令,可以看到各個段的大小

[root@build src]# gcc a.c -o a
[root@build src]# size a
   text    data     bss     dec     hex filename
   1040     484      16    1540     604 a

其中前三列分別為可執行程序a的text、data以及bss段的大小,第四列為該三段大小之和,第四列為該大小的十六進制表示,最后一列是文件名。

增加一個未初始化的靜態變量

#include  
    
int main(void) { 
    static int data;
    return 0; 
}

通過size命令

[root@build src]# size a
   text    data     bss     dec     hex filename
   1040     484      24    1548     60c a

從上面可以看出,bss段size變大

增加一個初始化的靜態變量

#include  
    
int main(void) { 
    static int data = 10;
    return 0; 
}

通過size命令

[root@build src]# size a
   text    data     bss     dec     hex filename
   1040     488      16    1544     608 a

從上面可以看出,data段size變大

增加一個未初始化的全局變量

#include  
int data;
int main(void) { 
    return 0; 
}

通過size命令

[root@build src]# size a
   text    data     bss     dec     hex filename
   1040     484      24    1548     60c a

從上面可以看出,bss段size變大

數據段的只讀區域和讀寫區域

#include 
char str[]= "Hello world";
int main(void) {
    printf("%s\n",str);
    str[0]='K';
    printf("%s\n",str);
    return 0;
}

輸出

Hello world
Kello world

可以看到上面的示例str是一個全局數組,因此它將進入數據段。 還可以看到能夠更改該值,因此它具有讀取和寫入權限。

現在查看其他示例代碼

#include 
char *str= "Hello world";
int main(void) {
    str[0]='K';
    printf("%s\n",str);
    return 0;
}

在上面的示例中,我們無法更改數組字符是因為它是文字字符串。常量字符串不僅會出現在數據部分,而且所有類型的const全局數據都將進入該部分。

數據塊只讀部分,通常除了const變量和常量字符串外,程序的文本部分(通常是.rodata段)也存在於數據塊的只讀部分,因為通常無法通過程序進行修改。

 

> 關注公眾號【高性能架構探索】,第一時間獲取干貨;回復【pdf】,免費獲取計算機經典書籍


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM