---
# System prepended metadata

title: 程式 & 記憶體 & 字串
tags: [C]

---

---
title: '程式 & 記憶體 & 字串'
disqus: kyleAlien
---

程式 & 記憶體 & 字串
===

## Overview of Content

> 記憶體又稱為內存，以下會混用

:::success
* 如果喜歡讀更好看一點的網頁版本，可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/)
    
    本篇文章對應的是 [**C 語言記憶體區塊規劃 | Segment 段 | 字符串特性**](https://devtechascendancy.com/c-memory-segmentation-string-properties/)
:::

[TOC]

## RAM 分配 - 概述

在 PC 作業系統中的軟體 (非 RTOS)，因為有 **MMU (`Memory Manager Unit`) 硬體關係**，所以軟體實際取得的記憶體位置 **都是虛擬記憶體位置**

這樣的好處是可以 **更好的利用 規劃虛擬記憶體 空間**

:::info
有關虛擬記憶體，可以參考 [**Linux 內存管理 & 操作**](https://hackmd.io/ptxTEwCzQBuxv61dGL_lvA?view) 這篇
:::

### 程式記憶體管理方式/規劃區域

* 編譯器對程式編譯後，會將程式碼分析並區分為幾個區域：主要有分為 `棧`、`堆`、`數據區`、`常量區` 幾類，儲存區預如下表
    
    > 每個區域都會有不同的特性

    | 稱呼 | 記憶體區塊規劃名稱 |
    | -------- | -------- |
    | 棧 | `stack` |
    | 堆 | `heap` |
    | 數據區 | `.data`、`.bass` |
    | 常量區 | `.ro.data` |

### 函數使用: 棧 Stack

* 在區域變數中使用，並有以下特點

    1. **自動回收**：進入函數之前自動分配，離開函數時自動釋放
    2. **反覆利用**：Stack 在 RAM 中的一個空間中，而該空間會不斷重複利用
    3. **髒內存**：釋放的內存空間中的舊數據並不會配清除
    4. **臨時性**：函數不能返回區域變數內的地址 (例如某一個指標)，因為該空間在函數離開後自動釋放

        ```c=
        #include <stdio.h>
        #include <stdlib.h>

        int *getTestValue() {
            int value = 100;
            int *p = &value;

            return p;    // 返回區域變數很危險
        }
        ```
        :::warning
        * 雖然上面這段程式可能成功了，但是十分危險
        :::

### 堆 Heap - malloc 使用

* **`malloc`/`free` 是 C 語言提供的標準庫的 API 之一**：可以用來動態申請、釋放記憶體，可在區域、全域變數使用 `malloc` 函數做動態記憶體申請，它的特點如下：

    1. **靈活**：可在運行時臨時申請
    
    2. **內存量大**：進程可以按照需要使用 C stander library 提供的 malloc api 手動申請記憶體空間空間
    
    3. **必須手動申請、釋放記憶體空間**
        > 如果不釋放，則該空間會持續佔據應用中的記憶體空間，最終導致記憶體洩漏
    
    4. **髒內存**：釋放的記憶體空間內的資料並不會被立刻清除，可能會遺留

    5. **臨時性**：生命週期只存在 申請 ~ 釋放之間，釋放後再取相同地址會得到不可預期的結果
        
        :::info
        * 其實 malloc 是由 mmap 而來，並且會由標準庫來負責管理申請的記憶體
        :::

        ```c=
        #include <stdio.h>
        #include <stdlib.h>

        void dynamic_memory() {
            int *p = (int *)malloc(1000 * sizeof(int));

            if(p == NULL) {
                printf("malloc memory fault.");
                return;
            }

            *(p + 1) = 123;
            *(p + 2) = 456;
            printf("Use memory p1: %d, p2: %d\n", *(p + 1), *(p + 2));

            free(p);
            // 會出現無法預期的錯誤
            // 最好是在 free 之後，將 p 置為 NULL
            printf("After free ptr, Use memory p1: %d, p2: %d\n", *(p + 1), *(p + 2));
        }
        ```
        > ![](https://i.imgur.com/QuHh6t1.png)


        :::danger
        * 內存洩漏 ?

            內存在 RAM 中佔有一塊位置，但並無任何程式在使用這塊 RAM 空間，這就是內存洩漏
        :::

### 標準函數 malloc 細節

* `malloc` 函數是 C 語言的標準函是庫提供，在使用時須重義一些特點

    1. **`malloc` 函數 返回值為 `void*` 指標**：該空間存放的數據類型尚未確定，也就是 **可以存放任何類型的數據**，而要存放啥數據等待使用者強制轉型
        
        > 可以解為：類似於 Java 的 Object 類的角色

    2. **動態申請記憶體結果**：如果該函數成功則返回第一個 Byte 的地址；失敗則返回 NULL
    
    3. **釋放記憶體**：使用成對的 `free` 函數就可以釋放，不過一定要記得使用原來給予的地址做釋放 


* 對於使用 `malloc` 函數申請的空間；申請 0 空間 返回結果不確定，**有可能反為 NULL，須看函數庫如何實現**

    ```c=
    void malloc_0_size() {
        int *p = (int *)malloc(0);

        if(p == NULL) {
            printf("malloc size 0 fault.\n");
            return;
        }
        printf("malloc size 0 success: %p, size: %d.\n", p, sizeof(*p));

        *p = 0x7FFFFFFF;
        printf("Value: %d.\n", *p);
    }
    ```
    雖然範例是申請成功的，但建議還是不要這樣使用
    > ![](https://i.imgur.com/XbxX6OF.png)

## 編譯器規劃：Segment 段

編譯器在編譯程式時，**會按照一定的結構、規則拆分程式，組成不同的 ++段++**；常見的段就有 `.text`、`.bss`、`.data`


### .text 程式段

* `.text` 是編譯器分析過後，專門放置程式的位置，關於程式的指令也就放在這

    ```c=
    // .text
    void sample_func(int v) {
        char *s = NULL;

        if(v == 0) {    // 判斷指令放在 .text
            s = "Hello";
        } else {
            s = "World";
        }

        printf("%s\n", s);
    }
    ```

### .data 數據段

* `.text` 是編譯器分析過後，專門放置數據的位置，同時也稱為 `靜態數據區`、`靜態區`；

    ```c=
    // 放置在 .txt 段    

    static char* HELLO = "HELLO~";

    void my_function() {
        static int a = 10;    // 分配在 .data 段

        int b = 100;    // 分配在 Stack
    }
    ```
    
    :::info
    * 與 Heap 幾乎相同，但是 `.data` 段生命週期是一直到程式結束才會結束
    :::
    
### .bss 段 / ZI

* `.bss` 段又稱為 **ZI (`Zero Initial`) 段**，所有為顯示初始化的 **靜態 (全局) 變量** 都放置在此，**這個段會自動將 ++尚未初始化++ 的靜態空間初始化為 0** (或顯示初始化為 0 的也會放置在這)

    ```c=
    // 放置在 .bss 段

    char* WORLD;

    int count = 0;
    ```
    
    :::success
    * 全局變量如果 **已經初始化，則會放置到 `.text` 區塊**
    :::
    
### .rodata 段

* `.rodata` 段是用來放置「常量數據」 (也就是程式中的不可修改的數據)

    :::warning
    * 至於使用關鍵字 `const` 修飾過後是否放置在 `.rodata` 段呢？

        只能說可能，**這要依據每個平台的實現 `const` 的方式來決定**
    :::

### 字符串位置 - .text/.data

* **字符串位置**

    1. **字符串也可能放到 `.text` 段**：單晶片的編譯器較常這樣實現；有另外一種可能是放置到 `.data` 段


    2. **字符串也可能放到 `.data` 段**：GCC 就是這樣實現字符串

        ```c=
        void ptr_str() {
            char *p = "My C";    // 放置在 `.data`

            printf("%s\n", p);
        }
        ```

## C 語言：字符串

其他語言像是 C++、Java、C#... 都有 string 這個關鍵字來描述字符串，而 C 語言中沒有，反而是使用指標提供一連串的字符首地址

```c=
// c 表達字符串的方式
char *c = "Hello String";
```

### 字符串特性

* 上面說了，字符串其實就是指向一連串字符空間的首地址

    :::success
    * C 語言使用 [**ASCII**](https://zh.wikipedia.org/wiki/ASCII) **對字符串便碼**，編碼後就可以使用 `char` 將編碼存起來
        
        > ![](https://i.imgur.com/DhavKAM.png)

    :::

* C 語言的字符串特點如下

    1. 只提供使用者 **第一個字符的地址**
    
    2. **字符串尾部固定是 `\0`**：這個 `\0` 是所謂的 **魔數**，在很多程式中都有，魔數個代表了不同的意義
        > 字符串中就不可以有 `\0` 這個符號，否則會分不清是否是結尾
    
    3. **字符的地址是連續** 的，如同 Array

        :::info
        
        * **字符中的 `\0`、`0` 的差別**，我們把字符中與 ASCII 編碼對照，就可以插出兩者的差別

            | 字符 | ASCII 編碼|
            | -------- | -------- |
            | `\0` | 0 |
            | `0` | 48 |

        :::

### 字符串 - 定義方式

* **字符串定義方式有兩種：**
    
1. **使用指標 `char*`** 定義字符串

    * **字符串的大小**：字符串大小 + '\0' + 指針大小
        * '\0' 上面我們已經得知這是必要的規則，所以占用 1 個 Byte
        * **指標大小**：其實我們所取得的 **並不是第一個 char 的地址，而是取得指向第一個 char 地址的指針**，如下圖

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

            ```c=
            void test_string_ptr() {
                char *p = "Hello";

                printf("p: %p,&p: %p\n", p, &p);
            }
            ```
            > 下圖 `004063EC`  H 字符的地址；
            > `0064FF0C` 是 p 的地址
            > 
            > ![](https://i.imgur.com/gQNnTtY.png)


2. **使用陣列 `Array`** 定義字符串

    * **字符串的大小**：'\0' + 指針大小
        
        > ![](https://i.imgur.com/lizc0rt.png)


    * 我們可以看到 Array、Ptr 所定義的 string 是不同的；Array 所定義的 String，其符號是指向第一個 char 字節地址，所以 **Array 定義的 String 可以修改**

        ```c=
        void test_string_array() {
            char p[] = "Linux";

            printf("p: %p,&p: %p, %s\n", p, &p, p);

            p[0] = 'G';
            printf("p: %p,&p: %p, %s\n", p, &p, p);
        }
        ```
        > ![](https://i.imgur.com/KYsv5X4.png)

    * Ptr & Array 定義字符串差異

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

### sizeof & strlen 差異

* **`sizeof` 是 C 語言的 「運算符」，不是函數**；**而 `strlen` 則是標準函數庫所提供的函數**，專門用來計算字符串長度

    `sizeof` 會計算包括 `\0` 的長度

    ```c=
    void string_len() {
        char p[] = "HelloWorld";

        int len = sizeof(p);
        printf("string length: %d\n", len);

        len = strlen(p);
        printf("string length: %d\n", len);

        char *p2 = "HelloWorld";
        len = strlen(p2);
        printf("string length: %d\n", len);
    }
    ```
    > ![](https://i.imgur.com/yMwEOF3.png)

* `strlen` 函數在使用時，傳入的必須是指標，並且該指標一定要指向一個字符串，否則沒有意義

    ```c=
    void err_use_strlen() {
        int apple = 10;
        int *p = &apple;

        int len = strlen(p);        // 傳入非字串指標，沒意義
        printf("string length: %d\n", len);
    }
    ```

## 更多的 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`
