--- 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`