---
title: '儲存類、作用域、生命週期、鏈結'
disqus: kyleAlien
---
儲存類、作用域、生命週期、鏈結
===
## OverView of Content
:::success
* 如果喜歡讀更好看一點的網頁版本,可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/)
本篇文章對應的是 [**C 語言儲存類別、作用域 | 修飾語、生命週期 | 連結屬性**](https://devtechascendancy.com/c-storage-scope-modifiers-lifecycle-linkage/)
:::
[TOC]
## 儲存類 - 概述
儲存類:變量在 RAM 中會開闢一塊空間來做存取,並且 **記憶體被分為 `Heap 堆`、`Stack 棧`、`.data`、`.bass`、`.text`** ... 等等
```c=
int v1 = 10; // 以初始化,存在 .data 段
// 以下兩個初始化為 0,或是尚未初始化,放在 .bss 段
int v2 = 0;
int v3;
int main() {
int v4 = 5; // Stack
int* v5 = (int *)malloc(sizeof(int)); // Heap
free(v5);
return 0;
}
```
:::success
* 可以參考 [**程式 & 記憶體**](https://hackmd.io/X27YAUmYReydHpUrcsdpbw?view) 篇章
:::
### Linux 記憶體概念
* 基礎段分類概念這裡不說明 (請參考 [**程式 & 記憶體**](https://hackmd.io/X27YAUmYReydHpUrcsdpbw?view)),特別說 Linux 中 C 應用程式的記憶體概念,這邊以常見的「外部裝置(也就是文件)」、「內核」映射為例
1. **文件映射區**
* 文件是外部硬體提供,而 Linux 會將文件先讀取到核心 (Kernel) 中,在透過記憶體映射的方式,映射到目標應域的 RAM 中 (虛擬記憶體上)
> 如此設計的原因是因為加快訪問速度,因為外部裝置的速度是最慢的
>
> 
* 應用程序在操縱文件時,其實就是在操縱 Kernel 映射到當前 Process 的記憶體區塊
:::success
可參考 [**檔案映射 - mmap 動態拓展**](https://hackmd.io/ptxTEwCzQBuxv61dGL_lvA?view#%E6%AA%94%E6%A1%88%E6%98%A0%E5%B0%84---mmap-%E5%8B%95%E6%85%8B%E6%8B%93%E5%B1%95) 文章,該文章有做小實驗
:::
2. **內核映射區**:將內核 (`Kernel`) 映射到應用記憶體中
* 每個應用進程都存活在獨立的進程空間中,每個進程有 0 ~ 3G 的使用者記憶體空間、1G 的內核空間 (這記憶體空間可能並非連續,這使用到了 **虛擬記憶體技術**)
> 這裡的虛擬內存量,是以 32 位元系統為例
>
> 
### auto - 修飾局部變數
* `auto` 關鍵字在 C 語言中,唯一的功能就是 **修飾局部變數**;表示該變量是自動局部變量 (依樣分配在 Stack 上)
> 既然分配在 Stack 上,代表部初始化,其 Value 就是隨機的
平時在定義局部變量時只是省略個 `auto` 關鍵字,並且 **`auto` 不可以使用在全局變量**
> 
* `auto` 也會自動推導,目前使用的區域變數類型 (沒定義類型的話會警告,但仍可編譯成功)
```c=
void auto_test() {
auto int apple = 10;
auto book = 200; // 自動推斷
auto car = "car"; // 自動推斷
printf("Apple: %d\n", apple);
printf("Book: %d\n", book);
printf("Car: %s\n", car);
}
```
> 
### static - 靜態變量
* `static` 關鍵字在 C 語言中有兩種用法,但這兩種用法沒相關,個代表了不同的意思
1. **修飾 ++局部變數++**:**透過 `static` 關鍵字修飾,該變量的儲存位置就不再是 `Stack`,也就是說該變量的數值會一直存在 !**
* 已定義初始化:存在 `.data`
* 未定義初始化:存在 `.bss`
```c=
void static_local_test(int init) {
static int a = 100; // .data
static int b; // .bss
int c; // stack
if(init != 0) {
b = 0;
c = 0;
}
int bookcase[1000] = {0}; // 擾亂 Stack
printf("last time b value: %d\n", b++);
printf("last time c value: %d\n\n", c++);
}
int main(void) {
static_local_test(1);
static_local_test(0);
static_local_test(0);
return 0;
}
```
> 
2. **修飾 ++全域變數++**:這有關於多檔案的鏈結,使用 `static` 描述的變數、函數,都不可以被其他檔案使用,只能在內部使用
> 鏈結時再說明
### register - 提高變數讀寫
* `register` 簡單來說就是要求編譯器將變數放置到 CPU 等級的 Register 做存取(當然,這並不一定全部被的變數都會被放置到 Register 中)
> 一般沒有聲明的變量是規劃在 RAM 記憶體空間
`register` 使用起來跟一般變量一樣
```c=
void register_test() {
register int a = 100; // 規劃到棧存器
int b = 200; // 規劃到 RAM
printf("a: %d\n", a);
printf("b: %d\n\n", b);
}
```
> 
:::warning
* **並非一定安排的到暫存器,畢竟 CPU 棧存器數量有限制,所以需慎用**
:::
### extern - 跨文件訪問
* 我們知道 C 語言是以文件為單位來做編譯,**如果要跨文件使用變量、函數,就需要使用 `extern` 關鍵字來修飾 (只能修飾全局變量)**
* 這裡要注意一件事… **`定義`、`宣告` 兩者個差異**:
* **宣告**:僅是告訴編譯器,有一個 **符號**
* **定義**:同時有宣告的意義,並加上為該符號 **在記憶體中佔(耗費)一個位置**
```c=
// storage.c
int var = 10; // 宣告 + 定義
// ------------------------------------------------------
// extern_test.c
extern int var; // 宣告
void test_extern() {
var += 10;
printf("extern val: %d\n", var);
}
```
:::warning
* 不需要再使用 `#include "storage.c"`,因為 extern 會去全局 (整個應用) 中尋找相對應的符號
:::
> 
### volatile - 易變
* **使用 `volatile` 關鍵字有以下特點**
* **變量由外部修改**:最常見的就是在中斷時改變變量數值,其次還有,在 mutli thread 時修改數值,硬體修改數值... 等等
* **寫入相同數值時**:不會再對記憶體寫入,使用 `volatile` 修飾變量後,就算是同樣的數值也會寫入
```c=
int a = 10;
a = 10; // 由於是相同數值,所以會「被編譯器優化」
a = 10;
a = 10;
a = 10;
a = 10;
```
* `volatile` 就是告訴編譯器不要隨意進行優化,使用方式如下
```c=
void test_volatile() {
volatile int a, b, c;
a = 3;
b = a;
c = b; // 如果沒修飾,可能被編譯器寫成 c = b = a = 3;
printf("a: %d\n", a);
printf("b: %d\n", b);
printf("c: %d\n", c);
}
```
### restrict - 限制
:::warning
**在 C99 之後才出現**
> gcc 編譯可以使用 `-std=c99` 來指定編譯版本
:::
* `restrict` 關鍵字是 **用於限制、約束 ++指標++**,目的是為了讓編譯器更好的優化
簡單來說,**在範圍內,修飾的指標不會被參考**
```c=
void test_restrict() {
int a = 10;
int* restrict aPtr;
int* restrict bPtr;
aPtr = &a;
bPtr = &a;
printf("aPtr: %d\n", *aPtr);
printf("bPtr: %d\n", *bPtr);
// 再次參考被 `restrict` 修飾的指標就會被警告 (仍編得過
int **aPtr2 = &aPtr;
int **bPtr2 = &bPtr;
printf("aPtr2: %d\n", **aPtr2);
printf("bPtr2: %d\n", **bPtr2);
}
```
> 
### typedef - 定義新類型
* typedef 在 C 中是屬於 **儲存類**,所以 **不可用第二個儲存類型關鍵字修飾**
:::success
* 使用請參考另一篇 [**指標 & Array & typedef**](https://hackmd.io/bzOkHGC_TDGN-RF4lK18Pw)
:::
## 作用域 - 概述
作用域的重點在 `{}` 符號 (`if`、`while`、`for`... 都有),只要變量一進入該符號就開始生命,離開就結束生命週期
### 作用域 - 區域變量
* 相同符號是可以內蓋外
```c=
#include <stdio.h>
int main(void) {
int apple = 10;
{
int apple = 1; // 內部 symbol 會覆蓋外部 symbol
apple += 1000;
printf("Inner Apple price: %d\n", apple);
}
printf("Outer Apple price: %d\n", apple);
return 0;
}
```
> 
## 生命週期 - 概述
生命週期
* 開始:運行中分配變量空間,排斥其他變量操控
* 結束:回收變量空間,其他變量可用
### Stack、Heap
1. Stack 跟 **作用域** 相當有關,生在作用域內,一離開作用域就死亡
```c=
void my_stack() {
int a = 10; // a 被規劃
for(int b = 0; b < = 20; b++) { // b 被規劃
a += b;
}
// b 結束生命週期
printf("a: %d\n", a);
} // a 結束生命週期
```
2. Heap 生命週期是使用者手動管理,生命週期從 `malloc` ~ `free` 之間,可以跨 Function,因為它的記憶體分配在 Heap 上,而非 Stack 上
> 操作不慎可能導致 OOM or 記憶體洩漏
```c=
int* alloc_int(int init) {
// 生命週期開始
int *ptr = (int*) malloc(sizeof(int));
*ptr = init;
return ptr;
}
int main(void) {
int *a = NULL;
a = alloc_int(9876);
printf("a: %d\n", *a);
free(a); // 生命週期結束
return 0;
}
```
### .data & .bss - 全局變量
* `.data` & `.bss` 用來描述全局變量,並且它們的 **生命周期是永久** (與應用同進退)
* 至於其他段 `.text` & `.rodata` 也是生命周期永久
### 函數 & Stack
* 函數有幾個特點
1. 入參建議不要超過 4 個,超過建議使用 struct 包裹起來(或是使用指標)
> 否則可能會造成 stack 的負擔
2. 傳入參數大小也不建議過大,避免超過 Stack Size,較大參數建議使用 Pointer 傳遞
3. 編譯完後函數 **會存在 elf 中的 `.text` 段**
:::info
* 可以使用 `readelf` 指令查看
> `readelf -S Hello.out`
> 
:::
### 函數 - 入棧 Stack
* 函數的入棧是按照順序的,如下面程式的入棧順序就是
:::info
* Stack 棧是由核心棧存器 `SP` 來管控,相關的核心棧存器還有 鏈結 `LR`、計數器 `PC`
:::
1. main 函數返回地址
2. main 函數
3. 遇到 subtraction 函數,保存 subtraction 函數完成後的返回地址
4. subtraction 函數入棧,**這裡又細分為,參數由右到左入棧**
```c=
int subtraction(int a, int b) {
return a - b;
}
int main(void) {
int result = subtraction(3, 5); // Error 呼叫不到 subtraction 函數
}
```
Stack 概念圖如下
> 
## 鏈結 - 概述
鏈結:鏈結在 C 語言中主要就是在鏈結個個檔案的匯編結果 (`.o`),也就是將各個獨立的二進為檔鏈結,形成一個可執行檔;
:::success
* **編譯以文件為單位,鏈結以工程應用為單位**
> 
:::
### 鏈結屬性
* C 語言中的鏈結有三種屬性
1. **外鏈結**:**使用 `extern` 修飾的全局變量**、**`函數` 都屬於外鏈結部分**
```c=
extern int a;
void printA() {
printf("A: %d\n", a);
}
```
:::info
* 這在大型專案中容易會有重名的問題
:::
2. **內鏈結**:使用 `static` 修飾的函數、**全局 static 變量** (不包含局部 static)
```c=
static int count = 10;
static void _cal_val(int *const val) {
*val = *val + 100;
}
```
:::success
* `static` 可以解決部分函數、變量重名的問題;`static` 修飾過後即便其他檔案有,也不會相互衝突
:::
3. **無鏈結**:**局部變量、`auto` 修飾、局部靜態變量**
```c=
void my_local() {
int a = 10;
auto b = "Hello";
static c = 200;
}
```
## 更多的 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`