---
# System prepended metadata

title: C 語言筆記
tags: [C 語言]

---

# C 語言筆記

> 好讀版：https://54ming.notion.site/C-15437766f44f8055b3b3e293484367bb

## Outline

- [Memory Layout](https://www.notion.so/C-15437766f44f8055b3b3e293484367bb?pvs=21)
- Keywords
    - [static](https://www.notion.so/C-15437766f44f8055b3b3e293484367bb?pvs=21)
        - [Static Variable](https://www.notion.so/C-15437766f44f8055b3b3e293484367bb?pvs=21)
        - [Static Function](https://www.notion.so/C-15437766f44f8055b3b3e293484367bb?pvs=21)
    - [extern](https://www.notion.so/C-15437766f44f8055b3b3e293484367bb?pvs=21)
    - [volatile](https://www.notion.so/C-15437766f44f8055b3b3e293484367bb?pvs=21)
    - [define](https://www.notion.so/C-15437766f44f8055b3b3e293484367bb?pvs=21)
    - [const](https://www.notion.so/C-15437766f44f8055b3b3e293484367bb?pvs=21)
    - [inline (function)](https://www.notion.so/C-15437766f44f8055b3b3e293484367bb?pvs=21)
- Types
    - [Type Size](https://www.notion.so/C-15437766f44f8055b3b3e293484367bb?pvs=21)
    - [Struct Size and Memory Alignment](https://www.notion.so/C-15437766f44f8055b3b3e293484367bb?pvs=21)
    - [union](https://www.notion.so/C-15437766f44f8055b3b3e293484367bb?pvs=21)
    - [enum](https://www.notion.so/C-15437766f44f8055b3b3e293484367bb?pvs=21)
    - [Pointer](https://www.notion.so/C-15437766f44f8055b3b3e293484367bb?pvs=21)
- [Operator Precedence](https://www.notion.so/C-15437766f44f8055b3b3e293484367bb?pvs=21)
- [__attribute__](https://www.notion.so/C-15437766f44f8055b3b3e293484367bb?pvs=21)
- [Interrupt Service Routine (ISR)](https://www.notion.so/C-15437766f44f8055b3b3e293484367bb?pvs=21)

## Memory Layout

https://blog.gtwang.org/programming/memory-layout-of-c-program/

![截圖 2024-12-13 凌晨1.47.58.png](https://prod-files-secure.s3.us-west-2.amazonaws.com/badf38d2-440d-4d71-b29f-2de6b2a37749/ab7870f4-a92f-4324-a96c-8778bd01038d/%E6%88%AA%E5%9C%96_2024-12-13_%E5%87%8C%E6%99%A81.47.58.png)

https://www.geeksforgeeks.org/memory-layout-of-c-program/

![image.png](attachment:9baa1660-2564-4c1e-8954-a41dd2042547:image.png)

### Text Segment

- 儲存已編譯的程式碼（機器碼），包含函數與指令
- 唯讀（Read-Only），防止程式碼被意外修改，確保執行安全性
- 通常位於記憶體的低位址區域

### Data Segment

- 存放全域變數與靜態變數
- 資料區段可進一步劃分為：
    - **Initialized Data Segment：**
        - 儲存已初始化的全域變數與靜態變數
    - **Uninitialized Data Segment (BSS)：**
        - **BSS（Block Started by Symbol）**：源自早期組合語言的指令
        - 儲存未初始化的全域變數與靜態變數
        - 變數在程式啟動時自動初始化為 0

### Heap Segment

- 動態記憶體配置區域
- 動態記憶體分配（malloc()、calloc()、realloc()）發生在此區段
- 從 BSS 區段的結尾開始，向高位址方向增長
- 與 Stack 方向相反增長，若與 Stack 指標相遇，將導致記憶體資源耗盡（Stack Overflow）
    - 例如：遞迴深度過大或過多區域變數
- 堆積區段是共享的，供程序中的共享函式庫及動態載入模組使用

### Stack Segment

- 儲存區域變數與函數呼叫資訊
- 函數呼叫時，會建立 Stack Frame，用來存放：
    - 函數的區域變數
    - 函數參數
    - 返回地址
- 位於記憶體的高位址，向低位址方向增長

## Static

### Static Variable

- **Static Sariable** 在編譯時必須初始化，因此只能使用常量值進行初始化
    - 如果未明確初始化，默認值為該型別的「零值」，例如：
        - pointer: `NULL`
        - int: `0`
        - double: `0.0`
        - char: `'\0'`
        - bool: `false`
- **Static Variable** 僅初始化一次，並且存活至程式結束
    - 這是因為它們被分配在 C 語言記憶體空間中的 BSS 或 Data 區域，而非函式的 Stack 區域
        - BSS 區域：用於未初始化的靜態變數
        - Data 區域：用於已初始化的靜態變數
- **Static Variable** 不能在 **Struct** 中宣告
    - 這是因為 Struct 要求所有成員變數必須分配在連續的記憶體空間（因為結構體的成員通過偏移量計算來訪問）中，但被 static 修飾的變數會被單獨分配到 BSS 區域 或 Data 區域，因此無法滿足結構體記憶體連續性要求
- 適用場景：
    - **函式內持久化變數值**
        - 適用場景
            - 在函式中需要變數的值在多次調用之間保持不變，但又不希望將其暴露為全域變數
        - 用途
            - 計數、累積結果或追蹤狀態
    - **Singleton Pattern**
        - 適用場景
            - 確保某個物件或資源在整個程式中只被初始化一次，例如 Buffer （限制記憶體分配在有限的大小內）或 DB Connect Pool 等
        - 用途
            - 節省有限的記憶體空間，避免多次初始化
    - **限制全域變數的作用域**
        - 適用場景
            - 當需要變數具有全域變數的生命週期，但希望其作用域僅限於當前檔案，避免其他檔案干擾
        - 用途
            - 提高程式的模組化和可維護性
    - **快取變數**
        - 適用場景
            - 需要重複計算的值，可以使用靜態變數進行快取，以提高效率
        - 用途
            - 減少不必要的重複計算，尤其是在遞迴運算中
        
        ```c
        int fibonacci(int n) {
            static int cache[100] = {0}; // 快取結果
            if (n <= 1) return n;
            if (cache[n] != 0) return cache[n]; // 使用快取
            cache[n] = fibonacci(n - 1) + fibonacci(n - 2); // 計算並存入快取
            return cache[n];
        }
        ```
        

### Static Function

- 使用 Static 將函數的作用域限制為所在檔案，避免其他檔案的命名衝突
- 可允許其他檔案中定義相同名稱的函數，而不會互相干擾

## extern

- extern 用於聲明變數或函式，告訴編譯器該變數或函式的定義在其他地方（可能是其他檔案中）
- 它是跨檔案共享變數或函式的核心工具
- 雖然全域變數默認具有外部鏈接，但明確使用 extern 可以提高程式的可讀性和維護性
- 注意：
    - extern 無法用於初始化變數，只能用於聲明
    - 可有效避免命名衝突，特別是在多檔案專案中

## volatile

- 告訴編譯器該變數的值可能會在程式控制範圍外被修改，要求編譯器不要優化，每次都從記憶體中讀取該變數的值，而不是使用暫存的值
- 適用場景：
    - 多執行緒環境：變數可能由其他執行緒修改，使用 volatile 確保其他執行緒讀取最新值
    - 硬體操作：當變數的值由外部事件（例如硬體中斷或 I/O 暫存器）改變時，需使用 volatile 修飾
        - 例子：
            
            早期硬體沒有 Interrupt 機制的情況下，CPU 通常使用 busy waiting 的方式檢查某 buffer 是否可以使用（作業系統筆記 - Process Concept - Shared Memory 的例子），這時需要 volatile 確保變數的最新值被讀取
            

![code.png](https://prod-files-secure.s3.us-west-2.amazonaws.com/badf38d2-440d-4d71-b29f-2de6b2a37749/5beb857b-7ccb-45af-ac4f-f11085df7dab/code.png)

## define

- `#define` 是 C 語言中的預處理指令，用於定義巨集（macro），在編譯前由預處理器進行文本替換，不會涉及型別檢查或編譯器優化，也因此較難偵錯
- `#define` 的內容在編譯前就會被展開，直接替換至程式碼中，不會佔用執行時的記憶體
- 巨集中的運算應使用括號包住參數與整個運算式，確保運算優先順序正確
- 適合簡單的常數定義與條件編譯，但不適合複雜運算或函數替換，應考慮使用 const 或 inline
- 條件編譯：配合 `#ifdef` 或 `#ifndef`，控制某些程式碼是否需被編譯
    
    ```c
    #define SQUARE(x) ((x) * (x))  // 正確
    #define SQUARE(x) x * x        // 錯誤，可能導致優先順序問題
    int result = SQUARE(1 + 2);   // 替換為 ((1 + 2) * (1 + 2))
    ```
    
- 適用場景：
    - 條件編譯
        
        ```c
        #ifdef _WIN32
        #define PLATFORM "Windows"
        #else
        #define PLATFORM "Unix-like"
        #endif
        ```
        
    - 不需要型別安全的數值或常量
        
        ```c
        #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
        ```
        
    - 編譯器、平台、硬體相關的指令
        
        ```c
        #define INLINE __attribute__((always_inline))
        #define NOP __asm__("nop")
        ```
        

## const

- 不可修改（Read-Only）的屬性，當某個變數或資料被定義為 const 時，這表示其值在初始化後不應該再被改變
- 與 `#define` 相比：
    - `const` 是創建一個只讀的變數，具有型別檢查，且遵循作用域規則
    - `#define` 是文本替換，沒有型別檢查
- 範例：
    - 指向的資料是只讀的：
        
        ```c
        const int *p = &x;
        // 或 int const *p = &x;
        
        *p = 20; // 錯誤：不能修改指向的資料
        p = &y;  // 正確：可以更改指標指向的位置
        ```
        
    - 指標本身是只讀的：
        
        ```c
        int *const p = &x;
        
        *p = 20; // 正確：可以修改指向的資料
        p = &y;  // 錯誤：不能更改指標指向的位置
        ```
        
    - 指標本身和指向的資料都是只讀的：
        
        ```c
        const int *const p = &x;
        
        *p = 20; // 錯誤：不能修改指向的資料
        p = &y;  // 錯誤：不能更改指標指向的位置
        ```
        
    - 字串陣列：
        
        ```c
        // strings1 是一個陣列，陣列的每個元素是指向不可改變內容的字串指標
        const char *strings1[] = {
          "Hello, world!",
          "C programming is fun.",
          "Generic strings are cool!"
        };
        strings1[0] = "New String";  // 合法：指標本身可以改變
        // strings1[0][0] = 'h';    // 錯誤：指向的字串內容不可修改
        
        // strings2 是一個陣列，陣列的每個元素是不可改變指向的字串指標
        char *const strings2[] = {
          "Hello, world!",
          "C programming is fun.",
          "Generic strings are cool!"	
        }
        // strings2[0] = "Hi";   // 錯誤：指標本身不可改變
        strings2[0][0] = 'h';      // 合法：指向的內容可以修改
        ```
        

## inline (function)

- inline 關鍵字提示編譯器將函式展開至呼叫處，避免傳統函式呼叫的開銷，提高執行效率
    - 傳統函式呼叫需要：
        - 堆疊管理（保存與恢復呼叫環境）
        - 參數傳遞（推入堆疊或暫存器）
    - 內聯函式透過編譯器展開，可省略這些開銷
- 不適用於大型函式，因為展開後會造成程式碼膨脹（Code Bloat），影響效能

## Type Size

- 型別決定了資料的儲存方式與運算行為，不同型別的變數會佔據不同大小的記憶體空間
- char 是唯一被標準明確定義大小的型別，固定為 1 byte，其他型別的大小則是 implementation-defined（依據編譯器與平台決定）
- 一般情況下，常見型別的大小如下（以 64-bit 系統為例）：
    
    
    | **型別** | **大小（bytes）** |
    | --- | --- |
    | char | 1 |
    | short int | 2 |
    | int | 4 |
    | long int | 4 或 8 |
    | long long int | 8 |
    | float | 4 |
    | double | 8 |
    | pointer | 8 |

## Struct Size and Memory Alignment

- Memory Alignment 	是將資料存放在符合其對齊需求的記憶體地址上，以最佳化記憶體存取並提升效能
- 不同的編譯器或平台可能會有不同的對齊規則，通常根據成員型別的大小來決定
- 可以透過 `#pragma pack` 或 `__attribute__((packed))` 減少 Padding，但需權衡效能與兼容性
- 範例：
    
    ```c
    typedef struct example_t {
        char a;     // 1 byte
                    // padding: 3 bytes (對齊到 4 bytes 邊界)
        int b;      // 4 bytes
        short c;    // 2 bytes
                    // padding: 6 bytes (對齊到 8 bytes 邊界)
        double d;   // 8 bytes
        char e;     // 1 byte
                    // padding: 7 bytes (滿足結構體總大小為其成員中最大對齊需求的倍數)
    } example_t;
    
    // a: 0x16fbf30c8
    // b: 0x16fbf30cc
    // d: 0x16fbf30d0
    // d: 0x16fbf30d8
    // e: 0x16fbf30e0
    // sizeof example_t is 32 bytes
    ```
    

## Union

- Union 是一種特殊的資料結構，允許不同型態的資料共用同一個記憶體區域
    - Union 的大小等於其最大成員的大小，因為所有成員共用相同的記憶體位址
- 適用場景：
    - 節省記憶體（只需要存放其中一個值）
    - 硬體相關開發（如解析通訊協議）
    - 資料型態轉換（如位元操作、型別別名）

### **Union vs Struct**

|  | **Union** | **Struct** |
| --- | --- | --- |
| **記憶體分配** | 所有成員共用相同記憶體空間，大小取決於最大成員 | 每個成員擁有獨立記憶體空間，大小需經過適配計算 |
| **應用場景** | 需節省記憶體，或做型別轉換 | 存放多個相關變數 |
| **存取限制** | 同一時間只能存取一個成員，否則行為未定義（UB） | 可同時存取所有成員 |
- 範例：
    
    ```c
    union Data {
        int intVal;
        float floatVal;
    };
    
    int main() {
        union Data data;
    
        data.intVal = 0x40490FDB; // IEEE 754 表示的 3.14
        printf("Interpreted as float: %f\n", data.floatVal);
    
        return 0;
    }
    ```
    
    ```c
    #include <stdio.h>
    
    union IP {
        unsigned int address;
        unsigned char bytes[4];  // 4 個 byte 組成的 IP
    };
    
    int main() {
        union IP ip;
        ip.address = 0xC0A80101; // 192.168.1.1
    
        printf("IP Address: %d.%d.%d.%d\n", ip.bytes[0], ip.bytes[1], ip.bytes[2], ip.bytes[3]);
    
        return 0;
    }
    ```
    
- UB 範例：
    
    ```c
    #include <stdio.h>
    
    union Data {
        int intVal;
        float floatVal;
    };
    
    int main() {
        union Data data;
    
        data.intVal = 42;  // 設定 intVal
        printf("intVal: %d\n", data.intVal);
    
        data.floatVal = 3.14;  // 設定 floatVal，覆蓋 intVal
        printf("floatVal: %f\n", data.floatVal);
        
        // 🚨 **未定義行為！因為 intVal 已經被 floatVal 覆蓋**
        printf("intVal (after modifying floatVal): %d\n", data.intVal);
    
        return 0;
    }
    ```
    

## enum

- enum（enumeration）是一種用於定義具名整數常量的資料型態，避免使用 Magic Numbers，使程式更具可讀性
- 特性：
    - 定義一組具名的整數常量
        - 每個 enum 成員預設為 **0** 開始，自動遞增
        - 可指定成員初始值，後續成員的值會在此基礎上遞增
    - 主要用途
        - 取代 Magic Numbers，提升可讀性與維護性
        - 適用於定義 狀態、標誌、選項，例如錯誤代碼、顏色、模式等
    - 底層實現
        - enum 成員的值實際上是整數（int），可以與整數進行比較或運算
- 範例：
    
    ```c
    #include <stdio.h>
    
    enum Color {
        RED,    // 預設值 0
        GREEN = 100, // 指定 100，後續成員會遞增
        BLUE    // 遞增為 101
    };
    
    int main() {
        enum Color myColor = GREEN;
    
        printf("My color is: %d\n", myColor);  // 輸出：100
        return 0;
    }
    ```
    
- 注意事項：
    - 在 **Bit Flags** 場景，`#define`（或 `typedef` 搭配 `const`）比 enum 更適合，因為它更靈活，且更容易進行位元運算
    - 簡單來說：
        - 如果成員之間需要進行運算組合，`#define` 更適合，enum 需要手動指定每個值，相較之下不夠直觀與靈活
    
    ```c
    #define FLAG_READ   (1 << 0)  // 0001
    #define FLAG_WRITE  (1 << 1)  // 0010
    #define FLAG_EXEC   (1 << 2)  // 0100
    #define FLAG_DELETE (1 << 3)  // 1000
    // 使用 #define 靈活擴展
    #define FLAG_ALL   (FLAG_READ | FLAG_WRITE | FLAG_EXEC | FLAG_DELETE)
    #define FLAG_RW    (FLAG_READ | FLAG_WRITE)
    
    // 相較之下，enum 需要手動設置數值，且容易出錯
    enum Permission {
        READ = 1,   // 0001
        WRITE = 2,  // 0010
        EXEC = 4,   // 0100
        DELETE = 8, // 1000
        RW = READ | WRITE, // 0011
        ALL = READ | WRITE | EXEC | DELETE // 1111
    };
    ```
    

## Pointer

### Pointer

- 指標是地址的型別，且指標的型別決定了如何解讀該地址的內容
    - 指標本質上是儲存地址的變數，它的值就是一個記憶體地址
    - 指標的型別決定了解讀該地址的方式，也就是該地址指向的資料應該被視為哪種類型

### Function Pointer

- 函式指標用於儲存函式的記憶體地址，使程式能夠在執行時動態調用函式，提供更高的靈活性與可擴展性
- 工作原理
    1. 函式名稱本質上是一個函式地址
        - 在 C 語言中，函式名稱（如 `print_func`）本質上是一個函式的地址，可以視為函式指標的簡寫形式
        - 函式名稱與函式地址的等價性：
            
            ```c
            void my_function() {
                printf("Hello, Function Pointer!\n");
            }
            
            int main() {
                printf("%p\n", my_function);  // ✅ 輸出函式地址
                printf("%p\n", &my_function); // ✅ 等價，顯式取得函式地址
            }
            ```
            
            - `my_function == &my_function`，因此可以直接將函式名稱賦值給函式指標
    2. 語法的簡化
        - C 語言允許函式指標直接調用函式，不需顯式解引用 `*`
            
            ```c
            func_ptr();     // 直接調用函式
            (*func_ptr)();  // 顯式解引用，與上方等價
            ```
            
            - 函式名稱可隱式轉換為函式指標，因此兩種寫法完全等價
            - 編譯器會自動處理解引用，允許更直觀的語法
    3. 函式指標的宣告
        - 為了讓編譯器識別變數為函式指標（儲存函式地址），需顯式使用 `*` 進行宣告
            
            ```c
            void (*func_ptr)(int) = some_function;
            ```
            

### Dangling Pointers and Wild Pointers

- **Dangling Pointers**
    - 指標指向的記憶體已被釋放或重新分配，但指標仍保留舊地址，繼續使用會導致 UB
    - 如何避免
        - 釋放記憶體後，將指標設置為 NULL
            
            ```c
            free(ptr);
            ptr = NULL;  // ✅ 設為 NULL，避免懸空指標
            ```
            
        - 避免返回局部變數的地址
            
            ```c
            void safe_function(int *out) {
                *out = 10;  // ✅ 使用傳遞指標的方式避免 UB
            }
            ```
            
- **Wild Pointers**
    - 未初始化的指標，可能指向隨機記憶體地址，存取時導致 UB

### Pointers and Arrays

- **Pointers**
    - `char *ptr` 表示一個指標，可以指向不同的記憶體位置
    - 指標可以動態變更所指向的地址
    
    ```c
    char c = 'A';
    char *ptr = &c;  // ptr 指向變數 c 的地址
    ```
    
- **Arrays**
    - `char name[]` 表示一個固定大小的陣列，名稱 `name` 本質上是一個常量指標，指向陣列的第一個元素
    - 陣列名稱不可修改，即 `name = some_other_array;` 是非法的
    
    ```c
    char name[] = "Hello";
    // name 是常量指標，指向 'H'
    name = "World";  // ❌ 錯誤，不能改變 name 的地址
    ```
    
- 指標與陣列的關聯
    - 當陣列名稱作為函式參數傳遞時，會隱式轉換為指向陣列第一個元素的指標
    - 這使得函式無法直接得知陣列的大小

### Pointer Operation

- 確定 `++` 作用的對象，再按照運算順序執行
    - `*p++`  = `*p; p = p + 1;`
        - `++` 作用於 `p`，且為後置處理，所以先進行 `*p` 取值，再執行 `p = p + 1`
    - `*++p` = `p = p + 1; *p;`
        - `++` 作用於 `p`，且為前置處理，所以先進行 `p = p + 1`，再執行 `*p`
    - `++*p` = `*p = *p + 1; *p;`
        - `++` 作用於 `*p`，且為前置處理，所以先進行 `*p = *p + 1`，再執行 `*p`
    - `(*p)++`  = `*p; *p = *p + 1;`
        - `++` 作用於 `*p`，且為後置處理，所以先進行 `*p`，再執行 `*p = *p + 1`

## Operator Precedence

- C 語言運算子的優先級（由高到低）
    
    
    | 優先級 | 運算子 | 描述 | 結合性 |
    | --- | --- | --- | --- |
    | 1 | `()` `[]` `->` `.` | 函數呼叫、陣列下標、結構成員訪問 | 左至右 |
    | 2 | `!` `~` `++` `--` `+` `-` `*` `&` | 一元運算符（邏輯非、位元非、遞增、遞減等） | 右至左 |
    | 3 | `*` `/` `%` | 乘法、除法、取餘 | 左至右 |
    | 4 | `+` `-` | 加法、減法 | 左至右 |
    | 5 | `<<` `>>` | 位元左移、右移 | 左至右 |
    | 6 | `<` `<=` `>` `>=` | 比較運算 | 左至右 |
    | 7 | `==` `!=` | 等於、不等於 | 左至右 |
    | 8 | `&` | 位元 AND | 左至右 |
    | 9 | `^` | 位元 XOR | 左至右 |
    | 10 | `|` | 位元 OR | 左至右 |
    | 11 | `&&` | 邏輯 AND | 左至右 |
    | 12 | `||` | 邏輯 OR | 左至右 |
    | 13 | `? :` | 條件運算符 | 右至左 |
    | 14 | `=` `+=` `-=` `*=` `/=` 等 | 賦值運算符 | 右至左 |
    | 15 | `,` | 逗號運算符（順序求值） | 左至右 |
- **位元運算的優先順序 （位元運算優先度都小於算術運算）**
    1. 位元左移 (`<<`)、右移 (`>>`) 
    2. 位元 AND (`&`) 
    3. 位元 XOR (`^`)
    4. 位元 OR (`|`)
- 範例：
    
    ```c
        int a = 12;   // 0b1100
        int b = 5;    // 0b0101
        int c = 2;    // 0b0010
    
        int result = ~a + (b & c) << 1 ^ (b | c) >> (a > b) * c % 3;
        printf("Result: %d\n", result); // -25
    ```
    
    - **負數的二進位表示方式**
        - 在 C 語言中，負數的二進位通常使用二補數（Two’s Complement） 表示：
            - 取正數的一補數（One’s Complement）（即所有位元取反）
            - 加 1 得到最終的二補數
            - 若要將負數的二進位轉換為十進位，可以先取反（一補數），再加 1，轉換為十進位後再加上負號

## __attribute__

- `__attribute__` 是 GCC 和 Clang 提供的編譯器擴展，非 C 語言標準的一部分，主要用於優化程式、增強可讀性、避免潛在錯誤
- 常見用途
    - **優化提示**
        - **noreturn**：告訴編譯器該函式不會返回（如 `exit()`）
        - **優點**：避免不必要的警告，幫助編譯器進行優化
            
            ```c
            void terminate_program() __attribute__((noreturn));
            void terminate_program() {
                exit(1);
            }
            ```
            
    - **指定屬性**
        - **aligned(N)**：指定變數的對齊要求，可用於 SIMD 優化或特定硬體需求
        - **優點**：確保變數對齊到指定的記憶體邊界，提高存取效能
            
            ```c
            int arr[4] __attribute__((aligned(16)));
            ```
            
    - **避免錯誤**
        - **format(printf, 1, 2)**：告訴編譯器，該函式的第一個參數是格式字串，第二個參數開始是格式對應的變數
        - **優點**：讓編譯器檢查格式字串是否正確，避免類似 printf("%d", "string") 的錯誤
            
            ```c
            void my_printf(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
            ```
            

## Interrupt Service Routine (ISR)

- ISR（Interrupt Service Routine，中斷服務程序） 是專門用來處理硬體中斷的函式，當中斷發生時，處理器會暫停當前執行的程式，跳轉至 ISR 進行處理，然後再返回原程式
- 特點：
    1. **自動執行**：當中斷發生時，ISR 會被處理器自動調用，而不是由主程式直接呼叫
    2. **執行速度快**：ISR 必須儘可能快地執行完畢，避免影響系統效能
    3. **避免阻塞**：ISR 不能進行長時間運行的操作（例如 printf() 或動態記憶體分配）
    4. **ISR 與主程式的通訊**：
        - **共享變數**：ISR 可更新全域變數，讓主程式檢查變數狀態
        - **旗標（Flags）**：ISR 可設置一個旗標（flag），讓主程式知道某個事件已發生
- 用途：
    - ISR 在嵌入式系統、即時控制、作業系統核心（Kernel）中廣泛應用，負責處理：
        - **外部設備中斷**（例如鍵盤、滑鼠、定時器）
        - **I/O 處理**（例如資料傳輸完成）
        - **計時器中斷**（用於精確時間控制）
        - **硬體錯誤**（例如記憶體錯誤或溢出）
- 範例：
    
    ```c
    #include <avr/interrupt.h>
    #include <avr/io.h>
    
    volatile int timer_count = 0;  // 使用 volatile 避免編譯器優化影響 ISR
    
    ISR(TIMER1_COMPA_vect) {  // 定時器中斷函式
        timer_count++;         // 更新計數變數
    }
    
    int main() {
        cli(); // 關閉中斷
        TCCR1B |= (1 << WGM12) | (1 << CS12); // 設定計時器模式
        TIMSK1 |= (1 << OCIE1A);  // 啟用計時器中斷
        sei(); // 啟用中斷
    
        while (1) {
            if (timer_count >= 10) {
                // 每 10 次計時器中斷執行某些操作
                timer_count = 0;
            }
        }
    }
    ```