---
title: 'Struct、Union、Enum'
disqus: kyleAlien
---
Struct、Union、Enum
===
## OverView of Content
如有引用參考請詳註出處,感謝 :smile:
:::success
* 如果喜歡讀更好看一點的網頁版本,可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/)
本篇文章對應的是 [**C 語言中的 Struct 定義、初始化 | 對齊、大小端 | Union、Enum**](https://devtechascendancy.com/c-struct_alignment_endianness_union_enum/)
:::
[TOC]
## struct 結構概念
> 整合變數的一個關鍵字 struct,用於變數、引數都十分的方便
結構主要的目的是在 **++整合參數++**,以往在全域需告變數時,必須分開去操控,難以記憶其關鍵字,而**使用 struct 就可以整合所有相關的變數**
1. 方便於管理
2. 方便使用
### struct 結構 - 定義
* 通常來說我們會把有相關的變數使用結構一併管理,使用 struct 關鍵字 + 大括號 {}
:::warning
**結構的 ++型態定義++ 並不佔有記憶體空間**
:::
```c=
// 分開宣告
char name[];
int age;
long id;
// 使用 struct 整合相關變數
struct Info_T {
char *name;
int age;
long id;
};
```
### struct 結構 - 訪問
* **struct 結構的訪問方式有兩種**
1. ^1.^ 結構的變數使用符號 `.` 來訪問成員;^2.^ 結構的指標 (pointer)則使用 `->` 訪問
```c=
void visit_struct() {
// 結構的變數
struct Info_T info;
info.name = "Sean";
info.age = 28;
info.id = 123132;
printf("name: %s\n", info.name);
printf("age: %d\n", info.age);
printf("id: %d\n\n", info.id);
// 結構的指標
struct Info_T *pinfo = &info;
printf("Ptr name: %s\n", pinfo->name);
printf("Ptr age: %d\n", pinfo->age);
printf("Ptr id: %d\n", pinfo->id);
}
```
> 
2. 手動使用偏移量計算,並調整指標的位置來訪問
```c=
void visit_struct_by_ptr() {
struct Info_T info;
info.name = "Sean";
info.age = 28;
info.id = 123132;
printf("&name: %p\n", &info.name);
printf("&age: %p\n", &info.age);
printf("&id: %p\n\n", &info.id);
// 轉換為結構的第一個地址
char *p = (char *) &info;
*p = "Alien";
*(p + sizeof(char*)) = 10; // 加上 char* 指針大小的位移(篇一到 age 變數)
// 加上 char* 跟 int 大小的位移(偏移到 id 變數)
// 並同時將指標強制轉型為 int* ,才能賦予 int 類型的數值
*((int*) (p + sizeof(char*) + sizeof(int))) = 998877;
printf("p: %p, name: %s\n", p, info.name);
printf("p: %p, age: %d\n", (p + sizeof(char*)), info.age);
printf("p: %p, id: %ld\n\n", (p + sizeof(char*) + sizeof(int)), info.id);
}
```
> 
### struct 結構 - 宣告、定義
* **宣告出來後就在記憶體內佔有空間**
* 在上方我們宣告了 `Info_T` 這個結構,在**呼叫時就要使用全名呼叫(也就是要包含 `struct` 關鍵字)**
:::info
* 完整的型態為 `struct Info_T`,而要使用該型態時就必須全型態名
```c=
// 型態宣告
struct Info_T {
char *name;
int age;
long id;
};
// 變數定義
struct Info_T info1;
```
:::
```c=
#include <stdio.h>
#include <stdlib.h>
struct Info_T {
char *name;
int age;
long id;
};
// 接收傳值的 Function
void printInfo(struct Info_T info) {
printf("name: %s\n", info.name);
printf("age: %i\n", info.age);
printf("id: %li\n\n", info.id);
}
int main(void) {
// 定義兩個結構變量
struct Info_T info1, info2;
// 使用 '.' 指定定義結構內容
info1.name = "Alien";
info1.age = 20;
info1.id = 9627;
// 傳值呼叫 Function
printInfo(info1);
info2.name = "Pan";
info2.age = 17;
info2.id = 33;
printInfo(info2);
return EXIT_SUCCESS;
}
```
**--實作結果--**
> 
### struct 結構宣告 - 同時定義變數
* 可以在定義的同時一起宣告出結構變數;變數方至於 大括號 `{}` 之後,並可宣告多個變數
```c=
#include <stdio.h>
#include <stdlib.h>
struct Info_T {
char *name;
int age;
long id;
} Default_1, Default_2; // 定義兩個變數
void printInfo(struct Info_T info) {
printf("name: %s\n", info.name);
printf("age: %i\n", info.age);
printf("id: %li\n\n", info.id);
}
int main(void) {
Default_1.name = "Hello";
Default_1.age = 12;
Default_1.id = 123;
printInfo(Default_1);
Default_2.name = "World";
Default_2.age = 21;
Default_2.id = 321;
printInfo(Default_2);
return EXIT_SUCCESS;
}
```
**--實作結果--**
> 
### struct 簡化宣告 - define、typedef
* 以往必須使用 struct 全結構才可進行宣告變數的動作,以下有兩種方法 `#define`、`typedef` 可以簡化該行為
:::warning
* `#define`、`typedef` 兩者是有差別的,`#define` 是在預編譯期間做替換,而 `typedef` 則是在編譯期間定義出新類型
:::
1. **使用宏定義關鍵自 `#define`**:其格式為 `#define <新名稱> <struct 原先名稱>`
:::info
* 如果使用分開定義那要特別注意,C 是順序式編程(命令式設計注重程式的順序),要把簡化定義寫在下方
:::
```c=
#include <stdio.h>
#include <stdlib.h>
// #define Info struct Info_T 不可寫在上方,因為還未定義
struct Info_T{
char *name;
int age;
long id;
};
// 簡化定義寫在下方
// 將結構 `struct Info_T` 定義為 `Info`
#define Info struct Info_T
// 引數使用 Info 簡化
void printInfo(Info info) {
printf("name: %s\n", info.name);
printf("age: %i\n", info.age);
printf("id: %li\n\n", info.id);
}
int main(void) {
// 宣告使用 Info 簡化
Info info;
info.name = "Alien";
info.age = 21;
info.id = 9528;
printInfo(info);
return EXIT_SUCCESS;
}
```
2. **使用類型關鍵字 `typedef` 定義**:其格式與 `#define` 相反,格式如 `#define <struct 原先名稱> <新名稱>` (記得使用分號 `;` 結尾)
```c=
#include <stdio.h>
#include <stdlib.h>
struct Info_T{
char *name;
int age;
long id;
};
// 定義新類型
typedef struct Info_T Info;
// 引數使用 Info 簡化
void printInfo(Info info) {
printf("name: %s\n", info.name);
printf("age: %i\n", info.age);
printf("id: %li\n\n", info.id);
}
int main(void) {
// 宣告使用 Info 簡化
Info info;
info.name = "Alien";
info.age = 21;
info.id = 9528;
printInfo(info);
return EXIT_SUCCESS;
}
```
如果覺得分開宣告很麻煩的話,也可以將 `struct`、`typedef` 兩個關鍵字合併使用,範例如下
```c=
#include <stdio.h>
#include <stdlib.h>
// struct`、`typedef` 兩個關鍵字合併使用
// 定義出一個「新類型 Info」,並且該類型為 struct
typedef struct Info_T {
char *name;
int age;
long id;
} Info;
// 引數使用 Info 簡化
void printInfo(Info info) {
printf("name: %s\n", info.name);
printf("age: %i\n", info.age);
printf("id: %li\n\n", info.id);
}
int main(void) {
// 宣告使用 Info 簡化
Info info;
info.name = "Alien";
info.age = 21;
info.id = 9528;
printInfo(info);
return EXIT_SUCCESS;
}
```
## struct 結構 - 初始化
struct 結構初始化有分為多種方式,接下來要介紹的就是不同的結構初始化的方案(以下是針對靜態初始化的結構做介紹)
### 結構順序初始化 - 預設初始化
* 預設初始化是 **手動 ==按照順序==** 對其結構內的變數進行初始化
```c=
#include <stdio.h>
struct Info {
int age;
int height;
int weight;
};
void printInfo(struct Info *i) {
printf("age: %d\n", i->age);
printf("height: %d\n", i->height);
printf("weight: %d\n", i->weight);
}
int main()
{
// 順序初始化
struct Info myInfo_1 = {
25, 170, 65
};
printInfo(&myInfo_1);
printf("Hello World");
return 0;
}
```
**--實作--**
> 
### 結構 - 指定、複合初始化
:::warning
* 使用 **指定、複合文字初始化時** 時要==注意==,當**沒有被指定到的值會初始化為 0**
* **指定初始化有 C 版本的限制**:**==C99 之後== 才能指定參數初始化**,像是 `C90`、`C85` 等等都不可指定初始化
:::
* **結構指定初始化**:
```c=
#include <stdio.h>
struct Info {
int age;
int height;
int weight;
};
void printInfo(struct Info *i) {
printf("age: %d\n", i->age);
printf("height: %d\n", i->height);
printf("weight: %d\n", i->weight);
}
int main()
{
struct Info myInfo_1 = {
// 不按照順序初始化,也可以選定初始化
.weight = 65,
.height = 170
};
printInfo(&myInfo_1);
return 0;
}
```
**--實作--**
> 
* **結構複合初始化**:
```c=
#include <stdio.h>
struct Info {
int age;
int height;
int weight;
};
void printInfo(struct Info *i) {
printf("age: %d\n", i->age);
printf("height: %d\n", i->height);
printf("weight: %d\n", i->weight);
}
int main()
{
struct Info myInfo_1 = {17, 168, 55};
//"1. "
myInfo_1.age = 20;
myInfo_1.height = 168;
myInfo_1.weight = 58;
//"2. " 結構複合初始化
myInfo_1 = (struct Info) {.weight = 60, .age = 20, .height = 168};
printInfo(&myInfo_1);
//"3. " 結構複合初始化
myInfo_1 = (struct Info) {.weight = 60, .height = 168};
printInfo(&myInfo_1);
return 0;
}
```
1. 一般在指定結構的值實必須分別指定
2. 可以透過==複合文字==一次指定全部,**不必按照順序,可特別指定**
3. 該範例使用 複合文字初始化 但沒有指定到 age,該 age 即初始化為 0
> 
## struct 結構 & 函數
使用結構型態可以**簡化函數的引數數量**
```c=
// 舊方法
void printInfo_1(char* str, int age, int id) {
//... TODO
}
// 使用 struct 類型
void printInfo_2(struct Info_T info) {
//... TODO
}
```
:::info
* **函數的原型宣告**
> 函數的原型宣告中的引數(參數)可以省去
```c=
// 原型宣告 - 保持引數
void printInfo_2(struct Info_T info);
// 原型宣告 - 省去引數
void printInfo_3(struct Info_T);
```
:::
### 函數回傳結構 - 變數、指標
* 函數的返回值假設要回傳結構,可以有兩種方案:^1.^ 用結構的型態回傳整組變數,也可以 ^2.^ 回傳結構的指標
```c=
#include <stdio.h>
#include <stdlib.h>
typedef struct Info_T{
char *name;
int age;
long id;
}Info;
// 引數使用 Info 簡化
Info FixInfo(Info info) {
info.id = 9123;
info.age += 1;
return info;
}
void printInfo(struct Info_T info) {
printf("name: %s\n", info.name);
printf("age: %i\n", info.age);
printf("id: %li\n\n", info.id);
}
int main(void) {
// 宣告使用 Info 簡化
Info info;
info.name = "Alien";
info.age = 21;
info.id = 9528;
info = FixInfo(info);
printInfo(info);
return EXIT_SUCCESS;
}
```
> 
## struct 實現注意
在電腦中 **struct 在記憶體中的排列方式,以及佔用空間** 對於我們了解計算機底層十分重要,以下章節將說明「資料對齊」跟「結構」的關係
### 自動對齊
* 在 struct 中宣告其變數,在**記憶體中的排版式固定的 (這取決於編譯器),這個概念由如 JVM 對象創建時設定的對象頭,其中的 Padding 對齊 (方便於總線讀取數據)**
:::info
* **對齊訪問的特點**?
對齊訪問是「**犧牲空間來換取效率**」,而非對齊則相反,效率低不過空間消耗也相對低
* **對齊的數**?
這會有關到硬體的數據總線的多寡;如果是 32 條,就是一次讀取 32 bit 的數據(對齊 32),64 則知一次 64 bit 數據(對齊 64)
:::
```c=
#include <stdio.h>
struct {
float a;
int b;
char c;
float d;
} Test;
int main()
{
//"1. "
printf("float size %d byte\n", sizeof Test.a);
printf("int size %d byte\n", sizeof Test.b);
printf("char size %d byte\n", sizeof Test.c);
printf("float size %d byte\n", sizeof Test.d);
//"2. "
printf("a address: %p\n", &Test.a);
printf("b address: %p\n", &Test.b);
printf("c address: %p\n", &Test.c);
printf("d address: %p\n", &Test.d);
return 0;
}
```
1. 用 sizeof 算出的大小雖然是正確,char 是 1 Byte
2. 取 Addr 出來看 char 卻占了 4 個 byte(這就是編譯器幫我們自動對齊的展現)
**--實做--**
> 從實際抓取到的參數值,**它的地址是以 ++4 為倍數++ 編排**
>
> 
### 手動對齊 - pragma pack
:::warning
如果不是特殊需求建議少用(`pragma pack`)
:::
* 在前面我們可以看到編譯器自動幫我們實現的對齊;有自動對齊當然就有 **手動對齊**,**透過 `#pragma pack([對齊 Byte 數量])` 關鍵字設定對齊**
:::info
* 定義的格式:**`#pragma pack()` 開頭、`#pragma pack()` 結尾**
:::
1. **一字節 (1 Byte) 對齊** 範例如下
```c=
#pragma pack(1)
struct Class {
int BookCount;
short chairCount;
char neckName[11];
};
void align_by_pragma_pack() {
printf("Class struct size: %d\n", sizeof(struct Class));
}
#pragma pack() // 注意需要結尾
```
> 從結果來看,它可以適時減少空間的消耗
>
> 
2. **兩字節 (2 Byte) 對齊** 範例如下
```c=
#pragma pack(2) // 修改為 2
struct Class {
int BookCount;
short chairCount;
char neckName[11];
};
void align_by_pragma_pack() {
printf("Class struct size: %d\n", sizeof(struct Class));
}
#pragma pack() // 注意需要結尾
```
> 
3. **兩字節 (4 Byte) 對齊**
```c=
#pragma pack(4) // 修改為 4
struct Class {
int BookCount;
short chairCount;
char neckName[11];
};
void align_by_pragma_pack() {
printf("Class struct size: %d\n", sizeof(struct Class));
}
#pragma pack() // 注意需要結尾
```
> 
### GCC 手動對齊 - `__attribute__`
* GCC 手動對齊的方式有兩種:使用時直接放置在結構(`struct`)的後方即可
* **`__attribute__((packed))`**:**其中 `packed` 是代表取消對齊**
* `__attribute__((aligned(n)))`:**`aligned(n)` 代表元素要對齊的大小**
1. **使用 `__attribute__` 取消編譯器的對齊**
```c=
#include <stdio.h>
struct MyClass {
int BookCount;
short chairCount;
char neckName[11];
}__attribute__((packed));
void packed_by_gcc() {
printf("packed struct size: %d\n", sizeof(struct MyClass));
}
```
> 
2. 1024 對齊:這裡我們看兩個情況
* 結構大小 **不超過 1024**:表示編譯時元素須在 1024 內對齊
```c=
#include <stdio.h>
typedef struct {
int BookCount;
short chairCount;
char neckName[11];
}__attribute__((aligned(1024))) MyClass_2;
void aligned_by_gcc() {
printf("packed struct size: %d\n", sizeof(MyClass_2));
}
```
> 
* 結構大小 **超過 1024**:超過 1024 則新增一個對齊大小
```c=
#include <stdio.h>
typedef struct {
int BookCount;
short chairCount;
char neckName[1025]; // 故意超出 1024 的大小
}__attribute__((aligned(1024))) MyClass_2;
void aligned_by_gcc() {
printf("packed struct size: %d\n", sizeof(MyClass_2));
}
```
> 
## struct 數據結構
### struct & Array
* **結構陣列**:可以定義、初始化多個結構形成的陣列,範例如下
```c=
#include <stdio.h>
#include <stdlib.h>
typedef struct Info_T{
char *name;
int age;
long id;
} Info;
void printInfo(struct Info_T info) {
printf("name: %s\n", info.name);
printf("age: %i\n", info.age);
printf("id: %li\n\n", info.id);
}
int main(void) {
// 定義結構陣列,並透過順序初始化!
Info infos[3] = {
{"Alien", 21, 1111},
{"Pan", 13, 2222},
{"Kyle", 15, 3333},
};
printInfo(infos[0]);
printInfo(infos[1]);
printInfo(infos[2]);
return EXIT_SUCCESS;
}
```
> 
### struct & Linked
* 可以使用 struct 來製作一個串列 Linked,這樣就可以達到一種 **數據結構的 `LinkedList`**,以下使用指標來指向下一個位子,範例如下
```c=
#include <stdio.h>
#include <stdlib.h>
typedef struct Info_T {
char name[5];
int age;
long id;
// 下一個
struct Info_T *next;
}Info;
// Reference 引數
void printInfo(struct Info_T* info) {
printf("name: %s\n", info->name);
printf("age: %i\n", info->age);
printf("id: %li\n\n", info->id);
}
int main(void) {
Info info1 = {"Alien", 18, 111, NULL};
Info info2 = {"Pan", 20, 222, NULL};
Info info3 = {"Kyle", 12, 333, NULL};
info1.next = &info2;
info2.next = &info3;
Info* temp = &info1;
while(temp != NULL) {
printInfo(temp);
// 使用指標取值必須使用 '->' 符號(優先級問題),不然就不需使用 (*temp)
temp = temp->next;
}
return EXIT_SUCCESS;
}
```
**--實作--**
> 
## Union
中文又稱為 **聯合體**、**共用體**;簡單來說就是 Union 內部成員是共用空間
### Union 使用、特性
* Union 所有成員 **共享空間**
```c=
union MyUnion_T {
int a;
short b;
char c;
};
void base_use_union() {
union MyUnion_T u;
u.a = 0x0F000F0F;
printf("a value: %d\n", u.a); // 還在 int 範圍內,全部顯示
printf("b value: %d\n", u.b); // 超過 short 範圍,顯示 0x0F0F 的數值
printf("c value: %d\n", u.c); // 超過 char 範圍,顯示 0x0F 的數值
}
```
> 
* **不存在對齊問題**,因為 **Union 就是一個空間**,都是從同一個地址開始
```c=
union MyUnion_T {
int a;
short b;
char c;
};
void union_addr() {
union MyUnion_T u;
printf("a addr: %p\n", &u.a);
printf("b addr: %p\n", &u.b);
printf("c addr: %p\n", &u.c);
}
```
所有成員都是相同的起始地址
> 
* **Union 其實功能跟使用指標強制轉型一樣**,透過強制轉型來讓它使用不同的格式分析指標;接下來 **使用指標的轉型來展現 Union 的功能**
```c=
void ptr_replace_union() {
union MyUnion_T u;
u.a = 0x0F000F01;
char *p = (char*) &u;
printf("u addr: %p, p addr: %p\n", &u, p);
printf("ptr a value: %d\n", *((int*) p));
printf("ptr b value: %d\n", *((short*) p));
printf("ptr c value: %d\n", *((char*) p));
}
```
> 
### 大端小端概念
:::info
大、小端這原本是出現在小說中的說詞,後來才用到電腦中表達序列化規則
:::
* 在串列序列化時會碰到傳送數據的順序問題,回到最基礎的傳送 int 類型,假設傳送一個 int 類型的數據是 4 Byte,那就分為兩種情況 (請看下圖
1. 假設有 Byte 0 ~ 3,「高 Byte」對「高地址」,就稱為 **小端**(對電腦讀取友善)
> 
2. 假設有 Byte 3 ~ 0,「高 Byte」對「低地址」,就稱為 **大端**(對人類讀取友善)
> 
* 以下是 `int i = 0xF0`,也就是 **`0x000000F0` 在大小端中的分布**;**分部是以 Byte 為單位**,低位元為 `0xF0`,高位元為 `0x00`
* **小端的 Byte 分佈**:
> 
* **大端的 Byte 分佈**:
> 
### Union 判斷大小端
* **使用 Union 就可以測定大小端**
:::info
* 數據從記憶體低位置開始存,存的順序才由大小端決定
:::
```c=
union Endian_t {
int iVal;
char cVal;
};
int is_little_endian(void) {
int val = 0x00000001; // 0x00 is height, 0x01 is low
union Endian_t t;
t.iVal = val;
if(t.cVal == 0x01) {
// little_endian
return 1;
}
return 0;
}
```
> 低地址存低位元就可以讀出數據
>
> 
* 同樣可以使用指標判定大小端
```c=
int is_little_endian_ptr(void) {
int val = 0x00000001; // 0x00 is height, 0x01 is low
char* p = (char*) &val;
if(*p == 0x01) {
// little_endian
return 1;
}
return 0;
}
```
> 低地址存高位元就則無法讀出數據
>
## Enum 枚舉
Enum 枚舉,定義一個符號,並且該符號與 **常量綁定**
### Enum 使用
1. **Enum 數值當常量基礎使用**:enum 可指定其數值(可正可負);如果**在中途切換為別的變數,也會從那個變數開始加**(自動會往後加)
```c=
#include <stdio.h>
enum Test_T {
A = 0,
B,
C,
D = 1,
E,
F,
G = -1,
H
};
void base_enum() {
printf("A: %i\n", A);
printf("B: %i\n", B);
printf("C: %i\n", C);
printf("D: %i\n", D);
printf("E: %i\n", E);
printf("F: %i\n", F);
printf("G: %i\n", G);
printf("H: %i\n", H);
}
```
> 
2. **分開定義類型、變量**
```c=
enum week {
SUN,
MON,
TUE,
WEN,
THU,
FRI,
SAT
};
void base_use_2() {
enum week w;
}
```
3. **使用 `typedef` 定義新類型**
```c=
typedef enum {
SUN_2,
MON_2,
TUE_2,
WEN_2,
THU_2,
FRI_2,
SAT_2
} week_t;
void base_use_3() {
week_t w;
}
```
:::danger
* 由於 Enum 是全局常量,所以一個文件中不可以重複定義
> 
:::
## 更多的 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`