---
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));
}
```
> 
:::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);
}
```
雖然範例是申請成功的,但建議還是不要這樣使用
> 
## 編譯器規劃: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` 將編碼存起來
> 
:::
* 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 地址的指針**,如下圖
> 
```c=
void test_string_ptr() {
char *p = "Hello";
printf("p: %p,&p: %p\n", p, &p);
}
```
> 下圖 `004063EC` H 字符的地址;
> `0064FF0C` 是 p 的地址
>
> 
2. **使用陣列 `Array`** 定義字符串
* **字符串的大小**:'\0' + 指針大小
> 
* 我們可以看到 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);
}
```
> 
* Ptr & Array 定義字符串差異
> 
### 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);
}
```
> 
* `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`