C 語言程式的記憶體配置概念教學

本篇介紹 C 語言程式的記憶體配置概念,並以實際的範例程式碼來說明。

在使用 C 語言開發比較低階的程式之前,多少都要了解一下程式實際在執行時的記憶體配置情況,以下我們以最簡單的實際範例來說明。

C 語言程式記憶體配置概念

下圖為典型的 C 語言程式在執行時的記憶體配置圖,記憶體的使用主要可分為 text、data、bss、stack、heap 與 system 這幾個部分。

C 語言程式記憶體配置(LibreOffice 原始檔

以下是各個區塊的說明。

text:程式碼

文字區段(text segment)也稱為程式碼區段(code segment),這裡存放的是可執行的 CPU 指令(instructions)。

這個區段通常位於 heap 或 stack 之後,避免因 heap 或 stack 溢位而覆寫 CPU 指令。

通常文字區段的資料是可以共用的,當多個同樣的程式在執行時,在記憶體中只需要存有一份就夠了,而這個文字區段通常都是唯讀的,避免程式本身誤改了自己的 CPU 指令。

data:初始化靜態變數

初始化資料區段(initialized data segment)儲存的是一些已經初始化的靜態變數,例如有經過初始化的 C 語言的全域變數(global variables)以及靜態變數(static variables)都是儲存於此處。

這個區段的變數又可分為唯讀區域(read-only area)以及可讀寫區域(read-write area),可讀寫區域用於存放一般變數,其資料會隨著程式的執行而改變,而唯讀區域則是存放固定的常數。

bss:未初始化靜態變數

未初始化資料區段(uninitialized data segment)又稱為 bss 區段(這個名稱的起源來自於古老的組譯器,代表 block started by symbol)是儲存尚未被初始化的靜態變數,而這些變數在程式執行之前會被系統初始化為 0 或是 null

stack:區域變數

堆疊區段(stack segment)用於儲存函數的區域變數,以及各種函數呼叫時需要儲存的資訊(例如函數返回的記憶體位址還有呼叫者函數的狀態等),每一次的函數呼叫就會在堆疊區段建立一個 stack frame,儲存該次呼叫的所有變數與狀態,這樣一來同一個函數重複被呼叫時就會有不同的 stack frame,不會互相干擾,遞迴函數就是透過這樣的機制來執行的。

heap:動態配置變數

heap 區段的記憶體空間用於儲存動態配置的變數,例如 C 語言的 malloc 以及 C++ 的 new 所建立的變數都是儲存於此。

堆疊區段一般的狀況會從高記憶體位址往低記憶體位址成長,而 heap 剛好從對面以相反的方向成長。

system:命令列參數與環境變數

system 區段用於儲存一些命令列參數與環境變數,這部分會跟系統有關。

實際範例

這是一個最簡單的 C 語言程式:

#include <stdio.h>
int main() {
  return 0;
}

編譯之後,可使用 size 查看它的內部記憶體配置:

gcc source.c
size a.out
   text	   data	    bss	    dec	    hex	filename
   1099	    544	      8	   1651	    673	a.out

新增一個未初始化的全域靜態變數:

#include <stdio.h>
double global[30];  // 儲存於 bss 的未初始化靜態變數
int main() {
  return 0;
}

查看內部記憶體配置:

gcc source.c
size a.out
   text	   data	    bss	    dec	    hex	filename
   1099	    544	    272	   1915	    77b	a.out

這一個未被初始化的陣列被放在 bss 區段。

接著增加一個已初始化的靜態變數:

#include <stdio.h>
int main() {
  // 儲存於 data 的已初始化靜態變數
  static int x[5] = {1, 2, 3, 4, 5};
  return 0;
}

查看內部記憶體配置:

gcc source.c
size a.out
   text	   data	    bss	    dec	    hex	filename
   1099	    564	      4	   1667	    683	a.out

有初始化的靜態變數或全域變數都會被放進 data 區段中。

儲存於 stack 與 heap 的變數在這裡看不到,以下是儲存於各種區段的變數:

#include <stdio.h>
const int global_x = 1;  // 儲存於 data 區段(唯讀區域)
int global_y = 1;        // 儲存於 data 區段(可讀寫區域)
int global_z;            // 儲存於 bss 區段
int main() {
  const static int x = 1; // 儲存於 data 區段(唯讀區域)
  static int y = 1;       // 儲存於 data 區段(可讀寫區域)
  static int z;           // 儲存於 bss 區段
  int w = 1;              // 儲存於 stack 區段

  // 儲存於 heap 區段
  char *buf = (char*) malloc(sizeof(char) * 100);
  // ...
  free(buf);

  return 0;
}

參考書籍:

  • Randal E. Bryant and David R. O’Hallaron. 2010. Computer Systems: A Programmer’s Perspective (2nd ed.). Addison-Wesley Publishing Company, USA.
  • W. Richard Stevens and Stephen A. Rago. 2013. Advanced Programming in the UNIX Environment (3rd ed.). Addison-Wesley Professional

參考資料:GeeksforGeekshackerearthcs-Fundamentals.comCodingfreakPro Programming

程式設計

4 留言

  1. Anson Jiang

    堆疊區段一般的狀況會從低記憶體位址往高記憶體位址成長,而 heap 剛好從對面以相反的方向成長。

  2. Anson Jiang

    這句話和開頭的記憶體配置圖示不符合, 請問哪一邊是正確的呢?

    • G. T. Wang

      抱歉,文字有誤,圖示是正確的。已將錯誤文字修正,感謝。

  3. 我想按讚但找不到地方按讚,非常感謝

Comments are Closed