01-2. Process 的組成(Code, Data, Stack, Heap)

⏱️ 閱讀時間: 10 分鐘 🎯 難度: ⭐⭐ (簡單)


🎯 本篇重點

理解 Process 在記憶體中的結構:Code(程式碼)、Data(資料)、Stack(堆疊)、Heap(堆積)。


🏢 Process 記憶體結構總覽

Process 記憶體空間(4GB 範例)
┌─────────────────────────────────┐ ← 高位址 (0xFFFFFFFF)
│         Kernel Space            │
│      (作業系統保留區域)          │
├─────────────────────────────────┤ ← 0xC0000000
│           Stack ⬇️               │ 向下增長
│        (區域變數、函數呼叫)      │
│                                 │
│         ↕️ 空閒空間 ↕️            │
│                                 │
│           Heap ⬆️                │ 向上增長
│      (動態分配的記憶體)          │
├─────────────────────────────────┤
│           Data                  │
│    (全域變數、靜態變數)          │
├─────────────────────────────────┤
│           Code                  │
│        (程式碼指令)              │
└─────────────────────────────────┘ ← 低位址 (0x00000000)

📦 四大區域詳解

1. Code Segment(程式碼段)

作用: 存放程式的執行指令(機器碼)

特性:

  • ✅ 唯讀(Read-Only)
  • ✅ 可共享(多個 Process 可共享同一份程式碼)
  • ✅ 固定大小
# 這些程式碼會被編譯成機器碼,存放在 Code Segment
def add(a, b):
    return a + b

def multiply(a, b):
    return a * b

result = add(10, 20)

比喻:

  • Code Segment = 公司的 SOP 手冊
  • 所有員工(Process)共用同一份 SOP
  • SOP 不能修改(唯讀)

2. Data Segment(資料段)

作用: 存放全域變數、靜態變數

特性:

  • ✅ 可讀寫
  • ✅ 程式啟動時初始化
  • ✅ 生命週期 = Process 生命週期
# 全域變數 → 存放在 Data Segment
counter = 0          # 初始化的全域變數
config = {"debug": True}

# 靜態變數(Python 沒有真正的靜態變數,但類變數類似)
class Config:
    MAX_CONNECTIONS = 100  # 類變數 → Data Segment

比喻:

  • Data Segment = 公司的共用白板
  • 所有函數都能看到和修改
  • Process 結束時才清除

3. Stack(堆疊)

作用: 存放區域變數、函數呼叫資訊、返回位址

特性:

  • ✅ LIFO(後進先出)
  • ✅ 自動管理(函數結束自動釋放)
  • ✅ 大小固定(通常 8MB)
  • ✅ 向下增長
def function_a():
    x = 10  # x 存放在 Stack
    y = 20  # y 存放在 Stack
    return function_b(x, y)

def function_b(a, b):
    result = a + b  # result 存放在 Stack
    return result

function_a()

Stack 的變化過程:

1. 呼叫 function_a()
   Stack: [x=10, y=20]

2. 呼叫 function_b(10, 20)
   Stack: [x=10, y=20] ← function_a 的 frame
          [a=10, b=20, result=30] ← function_b 的 frame

3. function_b 返回
   Stack: [x=10, y=20] ← function_b 的變數已清除

4. function_a 返回
   Stack: [] ← 所有變數都清除了

比喻:

  • Stack = 餐廳的點餐單疊
  • 最後點的最先做(LIFO)
  • 做完就扔掉(自動清除)

4. Heap(堆積)

作用: 存放動態分配的記憶體

特性:

  • ✅ 手動管理(需要明確分配和釋放)
  • ✅ 大小靈活
  • ✅ 向上增長
  • ⚠️ 需要注意記憶體洩漏
# Python 中的 Heap 使用(自動垃圾回收)
class User:
    def __init__(self, name):
        self.name = name

# 創建物件 → 在 Heap 分配記憶體
user1 = User("Alice")  # Heap: User 物件
user2 = User("Bob")    # Heap: 另一個 User 物件

# Python 的垃圾回收會自動清理不再使用的物件
user1 = None  # User("Alice") 會被垃圾回收

C 語言範例(手動管理):

#include <stdlib.h>

int main() {
    // 在 Heap 分配記憶體
    int *ptr = (int *)malloc(sizeof(int) * 100);

    // 使用記憶體
    ptr[0] = 42;

    // 必須手動釋放,否則記憶體洩漏!
    free(ptr);

    return 0;
}

比喻:

  • Heap = 倉庫
  • 需要空間時自己申請
  • 不用了要記得歸還

🔍 完整範例

Python 範例:各區域的變數

import sys

# Data Segment:全域變數
global_var = 100
global_list = [1, 2, 3]

class Calculator:
    # Data Segment:類變數
    version = "1.0"

    def __init__(self, name):
        # Heap:實例變數(物件存在 Heap)
        self.name = name

    def calculate(self, a, b):
        # Stack:區域變數
        result = a + b
        temp = result * 2
        return temp

def main():
    # Stack:區域變數
    x = 10
    y = 20

    # Heap:創建物件
    calc = Calculator("MyCalc")

    # Stack:函數呼叫
    answer = calc.calculate(x, y)

    print(f"答案: {answer}")

# Code Segment:函數定義
main()

記憶體分布:

Stack:
  ├─ main() 的 frame
  │  ├─ x = 10
  │  ├─ y = 20
  │  └─ calc (引用,指向 Heap 中的物件)
  └─ calculate() 的 frame
     ├─ a = 10
     ├─ b = 20
     ├─ result = 30
     └─ temp = 60

Heap:
  ├─ Calculator 物件 (name="MyCalc")
  └─ global_list [1, 2, 3]

Data Segment:
  ├─ global_var = 100
  ├─ Calculator.version = "1.0"
  └─ global_list (引用,指向 Heap)

Code Segment:
  ├─ Calculator.calculate() 的機器碼
  └─ main() 的機器碼

📊 四大區域對比

區域存放內容大小生命週期管理方式
Code程式碼指令固定Process 全程作業系統
Data全域變數、靜態變數固定Process 全程作業系統
Stack區域變數、函數呼叫固定(~8MB)函數呼叫期間自動(LIFO)
Heap動態分配物件靈活明確釋放前手動/GC

⚠️ 常見問題

問題 1:Stack Overflow(堆疊溢位)

原因: 遞迴太深或區域變數太大

def recursive(n):
    if n == 0:
        return
    x = [0] * 1000000  # 大型區域變數
    recursive(n - 1)

# Stack Overflow!
recursive(10000)

錯誤訊息:

RecursionError: maximum recursion depth exceeded

解決方法:

  • 減少遞迴深度
  • 使用迭代代替遞迴
  • 將大型資料放到 Heap

問題 2:Memory Leak(記憶體洩漏)

原因: Heap 的記憶體沒有釋放

class DataProcessor:
    _cache = {}  # 類變數,永遠不會被清除

    def process(self, data):
        # 不斷累積,記憶體洩漏!
        self._cache[len(self._cache)] = data

# 每次呼叫都累積在 _cache 中
processor = DataProcessor()
for i in range(1000000):
    processor.process(f"data_{i}")

解決方法:

from collections import OrderedDict

class DataProcessor:
    def __init__(self, max_cache=1000):
        self._cache = OrderedDict()
        self._max_cache = max_cache

    def process(self, data):
        self._cache[len(self._cache)] = data

        # 限制 cache 大小
        if len(self._cache) > self._max_cache:
            self._cache.popitem(last=False)

問題 3:Stack vs Heap 如何選擇?

情況使用 Stack使用 Heap
小型區域變數
大型資料結構
需要跨函數使用
生命週期 = 函數
需要動態大小
def example():
    # Stack:小型、短生命週期
    counter = 0
    temp_sum = 100

    # Heap:大型、長生命週期
    large_data = [0] * 1000000
    user_object = User("Alice")

    return user_object  # 返回 Heap 中的物件

🎯 實用工具

查看 Process 記憶體使用

import os
import psutil

def print_memory_info():
    process = psutil.Process(os.getpid())
    mem_info = process.memory_info()

    print(f"RSS (實際使用): {mem_info.rss / 1024 / 1024:.2f} MB")
    print(f"VMS (虛擬記憶體): {mem_info.vms / 1024 / 1024:.2f} MB")

# 測試記憶體增長
print("啟動時:")
print_memory_info()

# 分配大量 Heap 記憶體
large_list = [0] * 10000000

print("\n分配記憶體後:")
print_memory_info()

✅ 重點回顧

Process 記憶體四大區域:

  1. Code Segment

    • 程式碼指令
    • 唯讀、可共享
  2. Data Segment

    • 全域變數、靜態變數
    • Process 全程存在
  3. Stack

    • 區域變數、函數呼叫
    • 自動管理、LIFO
  4. Heap

    • 動態分配記憶體
    • 手動管理(或 GC)

關鍵理解:

  • ✅ Stack 適合小型、短期資料
  • ✅ Heap 適合大型、長期資料
  • ✅ 注意 Stack Overflow 和 Memory Leak
  • ✅ Python 的 GC 會自動管理 Heap

上一篇: 01-1. Process 是什麼 下一篇: 01-3. Process 的生命週期


最後更新:2025-01-04

0%