---
# System prepended metadata

title: 記憶體 & 操作系統
tags: [C]

---

---
title: '記憶體 & 操作系統'
disqus: kyleAlien
---

記憶體 & 操作系統
===

## OverView of Content

早期電腦是沒有記憶體的（記憶體也稱之為內存），但現在的電腦要十分注重記憶體，接下來將以記憶體為重點往外拓展

:::success
* 如果喜歡讀更好看一點的網頁版本，可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/)
    
    本篇文章對應的是 [**理解電腦記憶體管理 | 深入瞭解記憶體 | C 語言程式與記憶體**](https://devtechascendancy.com/computer-memory_manager-c-explained/)
:::

[TOC]

## 電腦運行

### 程式定義

1. 程式是啥：程式最基礎的就是由函數組成，它為了達到某個目的而做事，而達到這個目的則需要透過 **++==數據==++ + ++==算法==++**

    ```c=
    int main() {

        // 數據
        int a = 10;
        int b = 20;

        printf("sum = %d\n", sum(a, b));
    }

    int sum(int a, int b) {
        // 算法
        return a + b;
    }
    ```

2. 程式的目的：關注程式 ^1^ 運行的結果、^2^ 運行的過程

    ```c=
    int main() {

        int a = 10;
        int b = 20;

        printf("sum = %d\n", sum(a, b));    
        
        printInfo(a);
    }

    // 關注結果
    int sum(int a, int b) {
        return a + b;
    }

    // 關注過程
    void printInfo(int a) {
        printf("value = %d\n", a);    
    }

    // 關注結果 & 過程
    int sumWithInfo(int a, int b) {
        
        printInfo(a);
        printInfo(b);
        
        return a + b;
    }
    ```
    
### 記憶體 RAM

* 記憶體就是儲存程式、數據

* 記憶體粗略有分為 **DRAM**(Dynamic RAM) & **SRAM**(Static RAM) 兩種

    | Type | 特性 | 使用地方 |
    | -------- | -------- | -------- |
    | 暫存器 | 高性能、小、貴 | CPU 暫存器 |
    | SRAM | 比 暫存器 慢、中、中 | CPU 的一、二、三 緩存 |
    | DRAM | 比 SRAM 慢、大、便宜 | 外插 RAM，DDR3、4 |

    > ![](https://i.imgur.com/tWl0det.png)

* 這邊我們大略說明一下 DDR，DDR (Doubk Data Rate) 是一種改進型的 RAM，它的 **特性是可以在一個 CPU 時鐘內 ++讀取兩次數據++**

### 程式 & 記憶體的關係

* 記憶體就是用來儲存 程式中 **可變數據** (在 C 語言中就有全局、局部變量)，**當你聲明一個變量時，就會在記憶體中開闢該變量的位子**
    ```c=
    // 全局部量
    int globalValue = 10;        // 存在記憶體中

    int main() {
        // 局部變量
        int localValue = 1;
    }
    ```
    :::success
    * 常量
        GCC 中常量就存在記憶體中 
        但大部分的單晶片機，會將常量存在 Flash 中，也就是 `.data` 中 (這個我們之後會說到)
    :::

* 數據結構 & 演算法簡單來講就是在研究如何將數據組織、排列放入記憶體中，好讓數據的存取有更高的效率
    1. 數據結構：如何組織數據放入記憶體
    2. 演算法：如何加工存入數據，讓其符合你需要的業務邏輯



### 馮‧諾依曼 ＆ 哈佛 - 結構
* 硬體是軟體的基礎，所有軟體功能最終都由 **硬體來決定**，計算機結構，是軟體跟硬體的抽象結構

    | 架構                           | 程式與資料 | 匯流排 | 訪問方式                   | 特點                 |
    | ---------------------------- | ----- | --- | ---------------------- | ------------------ |
    | **Harvard** (8051, PIC, AVR) | 分開    | 兩條  | MOV (Data)、MOVC (Code) | 可同時取指令（程式）與資料，速度快，空間固定 |
    | **Von Neumann** (ARM, x86)   | 共用    | 一條  | 通用 load/store          | 彈性大但取指令（程式）與資料會競爭      |

    這邊我們在指令（程式）與資料 (數據) 的差別

    | 類別                      | CPU 從哪裡拿                          | 實體記憶體                 | 存放內容         | 存取方式                     |
    | ----------------------- | --------------------------------- | --------------------- | ------------ | ------------------------ |
    | 🟣 **指令/程式 (Instruction)** | **程式記憶體 (Program / Code Memory)** | Flash ROM             | 程式碼、常數表、查表資料 | 由 CPU 取指令匯流排（Code Bus）去讀 |
    | 🟢 **資料 (數據 Data)**        | **資料記憶體 (Data Memory)**           | SRAM (DATA、XDATA、SFR) | 變數、狀態值、暫存器內容 | 由資料匯流排（Data Bus）存取       |


* **馮‧諾依曼結構 `Von Neumann`**
    
    1. 採用二進制結構：簡化電子邏輯
    
    2. 程序儲存 （stored-program）：**程序以及數據全部除存在內部儲存器中**，這會導致程序＆　數據共享在同一個地方，一定程度限制了機器 (因為數據、指令共享一條數據線，影響了傳輸速度)
    
    3. 指令（程式）、數據寬度要相同（這點要看使用的指令集，如果是複雜指令集 `CISC` 就會不同）
        
        :::info
        ARM7、MIPS 的 CPU 都是使用 **馮‧諾依曼** 結構
        :::
        > ![](https://i.imgur.com/Y4C49mF.png)

* **哈佛結構 `Harvard`**
    馮‧諾依曼　結構的進化版本，同樣是使用了二進制＆stored-program，差異是在 **指令 ＆ 數據分開儲存** (增強了存取效率)
    
    1. CPU 可以先到指令儲存器讀取指令，解碼後得到記憶體地址，再到對應的數據儲存器中讀取數據
    2. 在執行步驟一時仍可儲存數據到儲存器中（就是一個非同步操作）
    
    3. **指令、數據的寬度也可以不同**
    
        > ![](https://i.imgur.com/RbuZo6j.png)


* **馮‧諾依曼 ＆ 哈佛 - 差異**

    * 哈佛可以在執行操作時同時讀取下一個指令，提高的吞吐量（IO 速度），而 馮‧諾依曼 結構則無法
    
    * 哈佛結構缺點在 ^1^ 架構複雜 ＆ ^2^ 需要兩個儲存器



### 管理記憶體 - OS 作用 
 
* 記憶體就是一種資源，如何高效率的管理記憶體是重要課題 (怎麼申請、申請大小、何時釋放... 等等)

    1. 有操作系統：操作系統會館裡所有的記憶體，並將一大塊記憶體 **分頁管理 (每頁大小一般是 4 KB)，一頁就是一個單位**
        > ![](https://i.imgur.com/IrYnPln.png)

    2. 無操作系統 (eg. MCU)：必須手動管理記憶體，如果沒有管理好記憶體可能會覆蓋到不能覆蓋的資料
        > ![](https://i.imgur.com/vT7X48D.png)


### 記憶體 & 程式語言

* 不同程式語言也有不同的記憶體管理方式

    | 語言 | 特色 | 說明 | 其他 |
    | -------- | -------- | -------- | -------- |
    | 匯編 | 沒有管理、效率高 | 直接操作記憶體 | 較為麻煩 |
    | C | 編譯器會幫我們管理記憶體地址 | 透過變量名訪問記憶體 (eg. int a = 10; a 實際記憶體地址由編譯器指定) | 可透過 API 動態申請記憶體地址 |
    | C++ | 可透過關鍵字快速申請記憶體 | new 分配地址、delete 釋放記憶體 | 可透過 new/delete 動態申請地址 |
    | Java/C# | 不直接操控地址，而是透過虛擬機管理 | 申請、釋放都是虛擬機操作 | 虛擬機也是占用空間、效率的 |


## 深入了解記憶體

以邏輯角度記憶體可以隨機訪問，記憶體對於程式來說是可以存放變量 (讀取 & 寫入)
> ![](https://i.imgur.com/qmP2wwS.png)

:::info
* C 語言並不會直接操作到真正的記憶體地址 (虛擬記憶體的原因)，但申明的變量就是對記憶體的操作
:::

### 記憶體邏輯抽象模型

* 記憶體實際上是無限多個記憶體單元格組成，每個單元格有固定的地址，**該記憶體地址與記憶體單元格式永久綁定的**
    > ![](https://i.imgur.com/2STpbYK.png)

* 記憶體在邏輯上可以無限大 (無限多個地址)，但其實 **記憶體大小是 ==受到硬體所限制==，以下提到三個有關硬體的設計，三總線：**

    1.**地址**總線：可訪問的記憶體地址範圍
    > 傳輸記憶體 (記憶體) 地址
    :::success
    * 記憶體大小 ＆ 真正可使用
    
        記憶體大小不是越大就可以用得越多，這取決於地址總線的數量 (2 的冪次方)
        
        > e.g: 2 條線就有 2^2^ 個，也就是 00、01、10、11 這 4 個記憶體位置
        > 
        > e.g: 32 條就是 2^32^個，接近於 4G 
 
        就算用了超過 地址總線數量 的記憶體還是只能使用到 地址總線的大小 
    :::

    2.**數據**總線：一次可傳輸的數據量
    > 傳輸要寫入的數據
    :::success
    * 幾位元 CPU，32、64 ?
        通常在說幾位元 CPU 就是指數據總線的數量
    :::
    3.控制總線：控制數據的 讀取 或是 寫入
    > 傳輸 讀 or寫 指令

* 總線概念圖
    > ![](https://i.imgur.com/b9RQyRS.png)

### 記憶體 - 單位
* 基礎使用單位不管是哪一台電腦，或是任何位元都是使用固定單位 (除了特規...)，其基本規定如下

    | 單位 | 記憶體單元 | 其他 |
    | -------- | -------- | -------- |
    | Bit | 1 | 最小單元 |
    | Byte | 8 | 也就是 8 個 Bit |


    | 單位 | 記憶體單元 |
    | -------- | -------- |
    | 1GB | 1024MB |
    | 1MB | 1024KB |
    | 1KB | 1024Byte |
    | 1B | 8Bit |

:::success
* 計算 & 電腦中的 1000 是不同的
    計算的 1000 就是 1000，**電腦的 1000 則是 1024**

:::
    
:::warning
* 有關於 Word ?
    Word 就是指 int，一般來講 32Bit，但是 **這必須要以作業系統的位元為準** (64 位元系統就是 64 Bit)，所以這就不用詳細區分
:::

* 雖然在程式中最小單位為 Bit，**但電腦中，硬體的記憶體地址是以 Byte 為基礎單位來切割**，一次能傳送的數據又跟數據總線有關 (下面會提及)
    1. 能夠儲存數據的是記憶體
    2. 記憶體又被規劃為單位大小為 1 Byte
    3. **每個 Byte 又對應到 一個地址**
   
        > ![](https://i.imgur.com/DxNzz1f.png)



### 記憶體位寬 - 數據總線

* 前有提到數據總線，而 **記憶體位寬就是指，++數據線的總量++**，就是一次能傳遞數據的大小，下圖就是 記憶體位寬 & 數據總線的關係
    1. 左圖，依照硬體特性，一次必須傳送 8Bit 數據
    2. 右圖，依照硬體特性，一次必須傳送 32Bit 數據
        
        > ![](https://i.imgur.com/1DNJHkw.png)

    :::info
    一次能傳送的數據是指，一個 CPU 時鐘週期內能傳送的數據
    :::

* 硬體：記憶體是可以連結的，就算是 8 位元也可以傳送 16、32 位元數據

* 軟體：軟體是可以隨意規定要取用的位元 (0 ~ 100都可以)，但仍需 **依賴硬體限制**，就算規定是 11 位元，但記憶體寬是 32 位，那實際就是傳送 32 位數據


### 記憶體對齊 & 數據類型

* 在不同數據內行做存入時 (char、short、int、long、float、double) 仍是依照 **硬體規定的記憶體位寬做存入的 (數據總線)**，不一定用了 char 類型就一定效率更高 
    > 尚未使用的部分就可以當作是浪費的
    > ![](https://i.imgur.com/IDi3IB6.png)

* 從這裡也可以了解到每次訪問就是以 Word (就是 int) 為單位來訪問，Word 大小又依賴於數據總線


* **對齊訪問不是邏輯的問題，是 ==硬體的問題==**，因為一次訪問一個 Word 效率是最高的
:::info
* 彙編可以使用不對其訪問，而高級語言對於記憶體的分配都是依照自動對齊做訪問
:::


## C 語言操作記憶體


### C 封裝記憶體地址

```c=
// 1. 編譯器申請了一個 int 類型的記憶體 (該地址由編譯自動幫我們分配)
int a;

// 2. 透過數據總線，將 5 賦予給 a，存入 a 的地址內
a = 6;

// 3. 該操作非原子操作
//    3-1. 透過地址分析、讀取的數值
//    3-2. 將該值 +4
//    3-3. 將最終結果存入 a 的地址內
a += 4;
```


### 數據類型的含意
* C 語言中，數據類型的涵義代表了，**^1^ ==記憶體單元的長度==(Byte 為單位)，^2^ ==解析該數據的方法==**

    * 一個地址代表了一個記憶體單元 (Byte)
        1. 地址總線透過透過地址編碼器份配，之後我們取得一個地址

        2. **該地址能儲存的大小為 1 個 Byte** (因為記憶體最小是以一個 Byte 為單位)
    
    
* 所以如果是 32 位元系統，透過 C 語言要申請一個 int 數據類型，那就必須要使用到 4 個 **++連續++** 地址的數據 (也就是 4 個 byte)，並且 **將++首地址跟變量 a 綁定++**
    
    > 這邊數據的存入使用小端 (little-endian 從低未元開始存入)
    > ![](https://i.imgur.com/nVcKQEB.png)

### 指針訪問
* 指針對於 C 來說也是一種 **數據類型**，也就是說指針仍是一種變量，該變量儲存在記憶體中，並且它的也有它獨特的解析方式 (用指針的方式解析)

    ```c=
    void main() {
        int a;

        a = 6;

        int *p = 0;        // 宣告一個 p 指針 (又稱為一級指針)

        p = &a;            // 透過 `&` 將 a 的地址存進 p 中

        printf("%d", *p);    // 透過 `*` 解析 p 中存取的地址中的數值 (也就是 a 的數值)
    }
    ```
    > ![](https://i.imgur.com/aWY8Xfr.png)
    
    上圖 int a 是使用 int 的方式解析 0x00-00-00-00，而 int \*p 則是使用 int \* 的方式來解析 0x03-00-00-00
    
:::success
* 有關於函數指針：
    **函數名的實質就是一個記憶體地址**，透過訪問該記憶體地址來訪問函數
    
    ```c=
    // test_function 就是指針
    // void* 類型指針
    void test_function() {    // 透過編譯器會將 test_function 賦予指針
        printf("TEST");
    }
    ```
:::


### Array 記憶體

* **Array 數組也是一種變量類型**。這裡要再次強調一下，C語言中普通變量、數組、指針的本質都是一樣的，只是 **==解析方式不同==**

* Array 就是定義一塊 **連續** 的記憶體空間，而 **數組名就是該記憶體空間的首地址** (跟前面我們所說的 int 相同，差異在解析方式)，**==數組名就相當於一個指針==**

    ```c=
    int a;            // 編譯器分配 4 Byte 記憶體給 a，並把 a 與 記憶體的首地址綁定

    int a[10];        // 編譯器分配 40 Byte 記憶體給 a，並把 a 與 記憶體的首地址綁定 (但解析方式與 int a 不同)
    ```
    > ![](https://i.imgur.com/517TJ72.png)

    
* 定義一個數組，並輸出該數組的地址，^1^ 查看 Array 個元素地址是否是連續、^2^ 宣告的符號是否可以當作指針操作、^3^ 測試 Array 指針類型
    從測試結果可以看到
    1. 每個 int 元素都是相連的，都相差 4 個地址 (一個地址佔一個 Byte)
    2. 宣告的符號 a 是數組的首地址，並且 a 可以當作 int(因為這邊宣告是 int[] 數組) 指針操作
    3. 透過 `&` 就可以取得該數組的地址，並且該 **地址解析的方式也是數組**
    ```c=
    #include <stdio.h>

    int main() {

        int a[5] = {0, 1, 2, 3, 4};

        int len = sizeof(a) / sizeof(int);    // 計算數組長度

        printf("array length: %d\n", len);

        for(int i = 0; i < len; i++) {
            // 1. 空間相連
            printf("a[%d] addr = %p\n", i, &a[i]);
        }
        
        // 2. a 作為指針
        printf("a + 1, addr: %p, value: %d\n", a + 1, *(a + 1));
        
        // 3. &a 是取 int[5] 這種結構的地址，所以 + 1 就會往下再給予 int[5] 空間的地址
        printf("&a + 1, addr: %p\n", &a + 1);  
        
        return 0;
    }
    ```
    > ![](https://i.imgur.com/RhfgSsG.png)

### struct 結構
* struct 可以將各種不同類的變量存在一起（聚合數據類型），並用一個名稱描述它
    ```c=
    // 定義一個 訊息 結構，方便之後不斷使用
    struct information {
        char *name;
        long id;
        int weight;
        int height;
    };

    void main() {
        // 可以輕鬆定義 一整塊相關資料，不用分開變量
        struct information A;
        struct information B;
    }
    ```

* 作為 function 入參時，它也是傳遞結構中的數值，所以它會將要傳入函數內的 struct 中所有的數值都壓入 stack 內
    :::danger
    * struct 作為參數 ?
        不建議太長使 struct 作為 function 參數，**建議使用 struct ++pointer++ 來傳送**，原因是因為參數是一個堆疊，它會消耗堆疊的大小（之後介紹）
    :::
    ```c=
    #include <stdio.h>

    struct information {
        char *name;
        long id;
        int weight;
        int height;
    };

    static void set(struct information* info) {
        info->name = "Alien";
    }

    void main() {

        struct information A;
        set(&A);

        printf("name: %s\n", A.name);
    }
    ```
    > ![](https://i.imgur.com/6I8Gd9y.png)


### C 語言的物件導向
* C 語言是注重順序的語言（面向過程），它與其他物件導向 Java、C#、C++、Python 不同，但 C 仍可寫出物件導向的特性 （eg. 像是 Linux 系統就是 C 語言建構的系統）
    ```c=
    struct {
        int count;            // 普通變數
        
        // 包含函數指針的結構體 就類似於一個 class
        int (*pFunc)(void);    // 函數指針
    }
    ```

## 記憶體管理
C 語言簡單來說就是在玩記憶體的操控，而 C 語言也有自己管理記憶體的特性（如下），所以我們一定要了解其特性
1. 堆 Heap
2. 棧 Stack

:::warning
* 堆棧是相同的？
    首先我們要知道 堆就是堆、棧就是棧，兩者是完全不同的數據結構，常說的堆棧，是指棧
:::

### 堆 - Stack
* 堆主要是 C 語言用來保存局部變量、函數調用（控制記憶體的一種方式），Stack 的特性是 **先進後出**，對於棧的理解就可以想成是指針的移動

    1. 局部變量
        * 在 Function 中定義一個局部變量時 (int a)，邊一起會在 Stack 中分配 4Byte 大小的空間給局部變量 a (並將 4 Byte 的地址與 a 產生關聯)，對應的操作就是 **==入棧==，將數據存入 a 中**

            > Stack 中的指針、記憶體分配是自動完成
            ```c=
            void myFunction() {
                int a;    // 將 a 入棧，並讓 a 與 stack 指針產生關聯
                a = 10;    // 將數據 10 存入區域變量中
            }
            ```
        * 當 Function 執行完畢後，區域變數會從 Stack **==出棧==**，我們也盡量不要去控制 Stack 中地址，因為該地址的內容可能是舊 or 新

    2. 函數調用
        **Stack 內有保有函數調用所需的所有維護信息**，這樣才知道該函數調用完畢後要回到的位置、還有調用該函數之前的數據
        ```c=
        void myFunction() {
            int a = 1;    // 分配 4Byte 的 Stack 空間
            
            a = funcA();    // 在呼叫 funcA 之前，儲存 a 的地址 ＆ 數據 ＆ 下一個要執行的函數（funcB） 並 ＋＋入棧 ＋＋
            
            // funcA 結束後原先入棧的數據出棧
            
            a = funcB(a);    // 再次除存數據 ＆ 入棧
        }

        int funcA() {
            return 10;
        }

        int funcB(int value) {
            printf("Hello World: %d", value);
            return value * 10;
        }
        ```

:::success
* 棧的重點如下
    1. 透過指針移動來操控
    2. **棧中的數據不會清理**（髒），這也就是為何我們在局部變量時 IDE 老是提醒我們要初始話的原因
:::

:::danger
* 棧超出？
    這是棧的一個缺點，棧是固定大小的，不可以隨意調整，所以 **區域變數不建議分配過大的容量**，像是 
    1. int a[10000] 之類的就不適合
    2. 遞歸函數（遞歸函數時也是棧的堆疊 ！）
:::

### 堆 - Heap

* Heap 是用來動態配置記憶體時會使用到的管理記憶體方式，其特點是自由，但是要 **手動申請釋放**，而 C、C++ 語言也有提供我們 API 操控
    1. 申請 malloc（C 語言）、new (C++)
    2. 釋放 free （C、C++ 語言）

* Heap 的特點
    1. 容量不限、動態分配
    2. 手動申請、釋放
        
        :::danger
        * 記憶體洩漏？
            就是屬於該進程的記憶體、但該進程並不使用（一直放置...忘記！），這會導致記憶體空間一直被佔據，最後可能會導致 OOM 程式 Crush
        :::

* C 語言提供的動態分配 Heap 函數 (提出幾個常用的)

    | 函數 | 說明 | 原型 | 其他 |
    | -------- | -------- | -------- | - |
    | malloc | 申請記憶體空間 | void \*malloc(size_t size) | - |
    | calloc | 申請記憶體空間 + 初始化清理空間為 0 | void \*calloc(size_t nmemb, size_t size) | - |
    | realloc | 修改已經分配的記憶體空間 | void \*realloc(void \*ptr, size_t size) | 其實是建立一個新空間，並將舊有資料複製進新空間 |
    | free | 釋放 | void free(void \*ptr) | - |

    在使用 C Heap 函數時，**請務必 ==檢查返回值==，若分配記憶體失敗則會返回 0**

### 靜態儲存區塊

* 如其名，它是負責儲存靜態變量的區塊（不管是局部、還是全局），編譯器會在編譯時就確定好靜態存區的大小，在 **靜態儲存區的記憶體生命週期就是如同整個程式**

    ```c=
    static int gTest = 123;

    void myFunction() {
        // 局部靜態
        static int test = 444;
    }
    ```

## 更多的 C 語言相關文章

關於 C 語言的應用、研究其實涉及的層面也很廣闊，但主要是有關於到系統層面的應用（所以 C 語言又稱之為系統語言），為了避免文章過長導致混淆重點，所以將文章係分成如下章節來幫助讀者更好地從不同的層面去學習 C 語言

### C 語言基礎

* **C 語言基礎**：有關於到 C 語言的「語言基礎、細節」

    :::info
    * [**理解C語言中的位元操作：位元運算基礎與宏定義**](https://devtechascendancy.com/bitwise-operations-and-macros-in-c/)

    * [**C 語言解析：void 意義、NULL 意義 | main 函數調用、函數返回值意義 | 臨時變量的產生**](https://devtechascendancy.com/meaning_void_null_return-value_temp-vars/)

    * [**C 語言中的 Struct 定義、初始化 | 對齊、大小端 | Union、Enum**](https://devtechascendancy.com/c-struct_alignment_endianness_union_enum/)

    * [**C 語言儲存類別、作用域 | 修飾語、生命週期 | 連結屬性**](https://devtechascendancy.com/c-storage-scope-modifiers-lifecycle-linkage/)

    * [**指標 & Array & typedef | 指標應用的關鍵 9 點 | 指標應用、細節**](https://devtechascendancy.com/pointers-arrays-const-typedef-sizeof-null/)

    :::

### 編譯器、系統開念

* **編譯器、系統開念**：是學習完 C 語言的基礎（或是有一定的程度）之後，從編譯器以及系統的角度重新檢視 C 語言的一些細節

    :::warning
    * [**理解電腦記憶體管理 | 深入瞭解記憶體 | C 語言程式與記憶體**](https://devtechascendancy.com/computer-memory_manager-c-explained/)

    * [**C 語言記憶體區塊規劃 | Segment 段 | 字符串特性**](https://devtechascendancy.com/c-memory-segmentation-string-properties/)

    * [**編譯器的角度看程式 | 低階與高階、作業系統、編譯器、直譯器、預處理 | C語言函數探討**](https://devtechascendancy.com/compiler-programming-os-c-functions/)
    :::

### C 語言與系統開發

* **C 語言與系統開發**：在這裡會說明 C 語言的實際應用，以及系統為 C 語言所提供的一些函數、庫... 等等工具，看它們是如何實現、應用

    :::danger
    * [**了解 C 語言函式庫 | 靜態、動態函式庫 | 使用與編譯 | Library 庫知識**](https://devtechascendancy.com/understanding-c-library-static-dynamic/)

    * [**Linux 宏拓展 | offsetof、container_of 宏、鏈表 | 使用與分析**](https://devtechascendancy.com/linux-macro_offsetof_containerof_list/)
    :::


## Appendix & FAQ

:::info
:::

###### tags: `C`
