---
# System prepended metadata

title: 指標 & Array & typedef
tags: [C]

---

---
title: '指標 & Array & typedef'
disqus: kyleAlien
---

指標 & Array & typedef
===

## OverView of Content

指標對於底層系統開發來說相當重要，而驅動又是透過控制 Register 來控制硬體，在這操控中就常常使用到指標

:::success
* 如果喜歡讀更好看一點的網頁版本，可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/)
    
    本篇文章對應的是 [**指標 & Array & typedef | 指標應用的關鍵 9 點 | 指標應用、細節**](https://devtechascendancy.com/pointers-arrays-const-typedef-sizeof-null/)
:::
[TOC]

## 指標概念

* **普通變數**：首先我們要知道一般的讀寫都不會涉及　**強制轉換**，哪種類型的變數，就會以相對應的格式存在 RAM 中

    ```c=
    int a = 10;

    long b = (long) a;
    ```
    > ![](https://i.imgur.com/Od8sces.png)

* **指標變數**：
    1. 可以把指標當成是另一種類型的宣告
    
    2. 指標的內容：跟普通變數一樣，是儲存某一個數值，只是 **指標是儲存一個地址** (使用關鍵字 `&` 來取得某個變數的地址)

        ```c=
        int a = 10;     // 儲存 10

        int* p = &a;    // 儲存 a 的地址， p 本身也有地址
        ```
        :::info
        指標本身也有地址
        :::
        > ![](https://i.imgur.com/jpg9Pmi.png)

### 指標特性 - 首地址

* 內存的大小：這取決於 **尋址總線數量**，如果尋址總現有 32 條，那地址最大可到 2^32^；相對來說，如果有 64 條，那地址最大可到 2^64^

* 由於 **在電腦中，硬體是以 1 Byte 作為單元切割**，所以當一個指標拿到一個類型的首地址，就會自動順延到符合該類型的長度內容

    ```c=
    int a = 10;

    // 取得 a 的第一個 Byte 的首地址，自動往後推 3 Byte
    // 最終以 4 Byte 作為該變量的空間
    int* p = &a;


    // --------------------------------------------------------
    struct hello_t {
        int a;
        int b;
        int c;
    };

    struct hello_t hello_world = {0};

    // 取得 hello_world 的第一個 Byte 的首地址，自動往後推 11 Byte (符合類型)
    // 最終以 12 Byte 作為該變量空間的地址
    struct hello_t* hw = &hello_world;
    ```

    :::info
    * 指標可以透過類型推導出接下來需要幾個 Byte 的數據
    
    * 一個地址能儲存的大小就為 1 個 Byte (跟硬體有關)

        > ![](https://i.imgur.com/18R4g8Z.png)
    :::



### 指標級量

* 指標可以指向另外一個指標，層層疊加，這就是指標的級量
    
    ```c=
    int a = 10;

    // 一級指標
    int* b = &a;

    // 二級指標
    int** c = &b;

    // 三級指標
    int*** d = &c;
    ```
    > (n + 1 級) = &(n 級)

    :::danger
    建議指標不要超過 3 級，不僅降低了可讀性，效率也會不好
    :::
    
    

### 指標其他作用 - 作用域

* 一般我們在規範函數不讓其它文件訪問時會使用 static，但是只要透過指標就可以取得該函數並執行
    
    `static` 關鍵字在 C 語言中相當於「存取限制符號」，使用 `static` 描述的函數、屬性，只能在該檔案中被訪問，其他檔案不可訪問！

    ```c=
    // 只有該檔案內部可訪問
    static void hello() {
        
    }

    // 只要有 include 的檔案都可以使用
    void world() {
        
    }
    ```

## 指標使用

### 指標符號

* **`*` 符號**： 

    1. **宣告指標變量使用 ，前後都可**
    
        ```c=
        int *p;

        // 同上
        int* p;

        // ------------------------------------------------
        int *p1, *p2;    // 兩個指標變量

        int *p1, p2;    // 一個指標、一個整數變量
        ```
    2. **解引用**

        ```c=
        int a = 10;

        int *p = &a;

        // 解引用，並賦予值
        *a = 20;
        ```

* **`&` 符號**：
    
    取變量的第一個 Byte 的地址
    ```c=
    int a = 10;

    int *p = &a;
    ```
    
### 野指標

* 只要指標可能出現未知性錯誤，它就是一個野指標；可能產生野指標的操作如下

    :::warning
    * `Segmentation Fault`：程式為了防止雪崩性錯誤，會使用 `Segmentation Fault` 停止該應用程式；而錯誤又分為兩種
        * 大段錯誤：地址不存在
        
        * 小段錯誤：地址存在，但訪問受限
    :::

    1. 尚未初始化就直接使用

        ```c=
        void wild_ptr() {
            char* p;

            *p += 1;    // Segment Error
        }
        ```
        
    2. 不清楚空間權限，試圖訪問、修改資料

        ```c=
        void wild_ptr_2() {
            char *p = "hello";    // "hello" 放置在常量區，常量區不許須改
            
            *(p + 1) = 'w';    // Segment Error
        }
        ```
        
    3. 越界訪問

        ```c=
        void wild_ptr_3() {
            int buf[4] = {0};
            
            *(buf + 4) = 10;    // + 4 已越界
        }
        ```
        :::info
        * `*(buf + 4)` 與 `*buf + 4` 是不同的，這有關於符號的優先度
            
            1. `*(buf + 4)` 是 buf 這個地址加上 4 個 Byte
            2. `*buf + 4` 是數組第一個數 buf[0] 再加 4
        :::
        
### What is Null

* Null 在 C/C++ 中是不同定義的存在，C++ 中被定義為 0，但是在 C 中定義為 `(void*) 0`，會被嚴格檢查

    ```c=
    #ifdef _cplusplus
    #define NULL            0
    #else 
    #define NULL            (void*) 0
    #endif
    ```

## const 修飾符

const 也就是 constant 代表不變，用來修飾變量，**希望變量轉為常量**

### const 修飾普通變數
* 修飾變數，不管 const 是在前還是在後都可以，只要保證在變數之前即可

    1. 類型之前
        
        ```c=
        #include <stdio.h>

        int main()
        {    
            const int a = 10;

            a = 30;    // const 不可修改，所以編譯會錯

            printf("Hello: %d\n", a);
            return 0;
        }
        ```
        > ![](https://i.imgur.com/2uttY4B.png)

    2. 類型之後

        ```c=
        #include <stdio.h>

        int main()
        {    
            // 不同之處
            int const a = 10;

            a = 30;    // const 不可修改，所以編譯會錯

            printf("Hello: %d\n", a);
            return 0;
        }
        ```



### const 修飾指標

* **const 修飾指標有三種表現方式**，個代表了不同的限制（注意 `const` 放置的位置）

    1. **const 修飾指標指向的空間**：及說明 **該空間的內容物為常量**；修飾指標指向的空間常量有 2 種表達方式，代表的意思是相同
    
        ```c=
        void const_ptr_1() {
	        
            int tmp = 20;
            
            int const *a = &tmp;
            // const int *a = &tmp;    // 同上

            *a = 100;    // Read-only 編譯器檢查錯誤，不可修改 !

            printf("Hello: %d\n", *a);
        }
        ```

        > ![](https://i.imgur.com/KdR7AHX.png)

    
    2. **const 修飾指標**：使用 const 修飾指標說明 **該指標指向不可在修改**，也就是 **指標無法再指去其他地方**，但其 **值能是可被修改**
        ```c=
        void const_ptr_2() {

            int tmp = 20;
            int * const a = &tmp;

            *a = 100;        // OK
            
            int tmp2 = 30;
            a = &tmp2;            // Read-only 編譯器檢查錯誤，不可修改 !

            printf("Hello: %d\n", *a);
        }
        ```
        > ![](https://i.imgur.com/BYRFhmT.png)


    3. **const 修飾指標 & 修飾指向空間**：代表指標 & 其指向的內容物都不可以修改

        ```c=
        void const_ptr_3() {

            int tmp = 20;
            int const * const a = &tmp;
        //	const int * const a = &tmp;    // 同上

            *a = 100;        // Read-only 編譯器檢查錯誤，不可修改 !

            int tmp2 = 30;
            a = &tmp2;        // Read-only 編譯器檢查錯誤，不可修改 !

            printf("Hello: %d\n", *a);
        }
        ```
        > ![](https://i.imgur.com/773OIQw.png)

### 指標修改 const

* **const 機制是通過編譯器檢查實現**，實際上真正運行的過程中並不關心變數是否被 const 修飾，只要保證編譯通過，程式仍可跳過 const 檢查

    ```c=
    void const_ptr_4() {
        const int a = 100;

        int *p = NULL;    
        p = &a;        // 會有警告而已

        *p = 300;
        printf("Hello: %d\n", a);    // 可修改 a 的值
    }
    ```
    
    :::success
    * 既然可以被修改，那為何要使用 const 修飾？
    
        讓程式更加健壯，提醒使用者某修地方不能被修改，或是保證不會被修改
    :::

### const & 變量 & 常量

* 在程式中我們常會使用 `1`、`2`、`3`、`"HELLO"`... 等等數值，而這些數值在編譯器的處理下會以兩種方式存在 RAM 中
    
    1. **變量**
        
        經過編譯後，會將 **變量放置在 `.data`、`.bss` 中，常出現在 `堆`、`棧` 中**，這些變量都是 **可讀可寫**；經過編譯檢查 const 關鍵字，可將這些數值看做 **偽常量**
        
        > 真正的常量不可修改，而偽常量 其實仍可修改
        
        :::info
        Linux 可使用 `readelf` 來查看編譯出來的執行檔中的 `.data`、`.bss` 區塊
        :::
        
    2. **常量**

        經過編譯後，會將 **變量放置在 `.ro.data` 中，訪問權限為 可讀 (不可改)**
        
        ```c=
        // p 儲存首字 `H` 的地址，而 "Hello const" 則是放置在常量區

        char *p = "Hello const";    // 如果透過指標修改這個變量，則會失敗 (Segmention Fault)
        ```
        
## Array 

深刻了解一維數組，是了解二維（甚至多維）數組的關鍵

```c=
// 格式如下
<類型>    <變量名>[<數量>]

int     a[100];        // 0 ~ 100
long    b[1]           // 0 ~ 1
```

:::info
以內存（記憶體）的角度來看，Array 的物理記憶體是連續的，並不會斷開，所以訪問速度也快
:::

### Array 訪問

1. **變量名訪問**：最基礎的訪問方式就是透過變量名稱來訪問
    ```c=
    void accessByName() {
        int a[10] = {0};

        a[0] = 100;

        printf("a[0]: %d\n", a[0]);

        a[10] = 9;    // 越界，但仍可正常設定值

        printf("a[10]: %d\n", a[10]);
    }
    ```
    > ![](https://i.imgur.com/Y5O4WQ0.png)

2. **指標訪問**：**不受到編譯器的 作用域檢查 規範**

    ```c=
    void accessByPtr() {
        int a[10] = {0};    

        int *p = a;    // a 本身就是一個地址，加上了 `[]` 才能解析其內容

        *p = 100;
        printf("a[0]: %d\n", a[0]);

        *(p + 10) = 9;

        printf("a[10]: %d\n", a[10]);
    }
    ```
    > ![](https://i.imgur.com/CjakUPF.png)

### 一維數組 & 符號

* 數組的符號有 4 種不同的意思（而部分其中還有細分，說明如下）：^1^ `a`、^2^ `a[0]`、^3^ `&a[0]`、^4^ `&a`

    ```c=
    int a[10] = {0};
    ```
    
    1. **Array 符號 `a`**：有兩種含意
        
        * Array 名稱：`sizeof(a)` 時，可以計算出該數組占用幾個 byte 大小
        
        * Array 的第一個地址：等同於 `&a[0]`，是 **數組的首元素的首個字節**，是一個常量值
            :::info
            * 如果是地址，那代表了是常量不可修改，所以永遠不會是左值
            
                ```c=
                int a[10] = {0};

                a = 1000;    //  a 是常量，不可為左值 (被賦予)
                ```
            :::
            
    2. **Array 符號 `a[0]`**：取第一個元素的空間，並可以對其讀寫操作
        ```c=
        int a[10] = {0};

	    printf("a[0]: %d\n", a[0]);    // read

        a[0] = 1000;    // write
        ```

    3. **Array 符號 `&a[0]`**：取締一個元素的首位元空間地址，就等同於符號 `a`
    
    4. **Array 符號 `&a`**：數組首地址，代表一個地址常量，同樣不可為左值

        :::info
        * **符號 `&a`、`a` 的差異 ?**

            兩者的數值皆是 Array 的首地址，但是 **==意義完全不同==**；`&a` 代表該空間的全體，而 `a` 只代表了該空間的 1 個元素
            
            ```c=
            void symbleTest2() {
                int a[5] = {0};

                printf("a: %p, &a: %p\n\n", a, &a);

                printf("a+1: %p, &a+1: %p\n", a+1, &a+1);
            }
            ```
            * `&a+1` 代表地址前進 `int a[5]`
            
            * `a+1` 代表地址前進 1 個 `int` 
            
                > ![](https://i.imgur.com/yzw6Uhm.png)

        :::


### 指標 & Array

* 一般訪問 Array 是透過 index 來指定要訪問 Array 的第幾個元素

    ```c=
    void iterate_array() {
        int array[10] = {0};
        
        for(int i = 0; i < sizeof(array)/sizeof(array[0]); i++) {
            // array[i] 使用 index 取元素
            printf("array[%d]: %d\n", i, array[i]);
        }
    }
    ```
    > ![](https://i.imgur.com/ur5JGek.png)

* **Array 的 ==變數名本身就有指標的意義==**，所以可以透過指標來訪問，這指標也分為兩中 ^1.^ 常量指標 (不可修改)、^2.^ 變量指標 (可修改)
    
    1. **常量指標**：常量指標其實就是代表 Array 宣告的變數名 (Symble)，該變數不可再修改，它是一個常量值 !
        ```c=
        void ptr_with_array_1() {
            int array[10] = {0};

            for(int i = 0; i < sizeof(array)/sizeof(array[0]); i++) {
                // 使用 `array + i` 改變常量指標 array
                printf("array[%d]: %d\n", i, *(array + i));
            }
        }
        ```
        :::danger
        * **可否修改成 `*(array++)` ?**
            
            **不行！因為 Array 宣告出的變數名是一個常量，既然是常量就不可以修改 !**
            > ![](https://i.imgur.com/2wIcJzW.png)

        :::
    
    2. **變量指標**：
        ```c=
        void ptr_with_array_2() {
            int array[10] = {0};
            int *p = array;

            for(int i = 0; i < sizeof(array)/sizeof(array[0]); i++) {
                // 如果是變數，就可以修改為 *(p++)，以下還有幾種方案，都可以達到相同的效果
                // *(p++) 可以寫作 
                //     1. p[i]
                //     2. *(p + 1)
                //     3. *(p + 1 * sizeof(int))
                printf("array[%d]: %d\n", i, *(p++));
            }
        }
        ```
        
        :::danger
        * 以下寫法是錯誤的
            
            ```c=
            int array[10] = {0};

            // 錯誤! array 原本就是首地址的第一個 Byte，語意變成了
            // array 首地址的首地址
            p = &array;     
            ```
        :::

* 使用指標位置 + 1，編譯器會依照指標的大小，新增一個單元

    ```c=
    int array[10] = {0}

    int *p = array;

    int a = *(p + 1);    // (p + 1) 相等於 (p + 1 *sizeof(int))
    ```

## 指標類型 & 強制轉換

對於編譯器來說，數據類型就是告訴編譯器該變數，要已什麼樣的格式儲存 (數據結構)，儲存的空間又是多大 ?

1. **儲存空間**：依照類型、硬體裝置，編譯器會給予不同變量，不同空間大小

    ```c=
    sizeof(char);        // 1 Byte    
    sizeof(short);       // 2 Byte
    sizeof(int);         // 4 Byte
    sizeof(float);       // 4 Byte
    sizeof(double);      // 8 Byte
    ```
    
2. **儲存結構**：即便是相同大小的 **`int`、`float` 儲存格式也不同**
    
    > float 是用科學計數法形式儲存 (其中就包括：小數、指數、符號... 等等)

    ```c=
    int a = 10;

    printf("%d", a);    // 正確
    printf("%f", a);    // 亂碼

    // --------------------------------------------
    float b = 10;

    printf("%d", b);    // 亂碼
    printf("%f", b);    // 正確
    ```

### 一般類型強制轉換 - 顯示

* 強制轉換一般類型，需要注意幾個點：**`空間`、`結構`**

    * **空間大小改變**

        1. **小轉大**：沒啥問題，資料可以正常轉換
            ```c=
            void small_to_big() {
                short a = 10;

                int b = (int)a;

	            printf("b value: %d, size: %d", b, sizeof b);
            }
            ```
            > ![](https://i.imgur.com/hLd5r4j.png)

        2. **大轉小**：**小心數據丟失、改變**！（並非一定會丟失，但是很大機率會將數據解釋錯）
            ```c=
            void big_to_small() {
                int a = 0x0000ffff;

                printf("a value: %d, size: %d\n", a, sizeof a);

                short b = (short)a;

                printf("b value: %d, size: %d", b, sizeof b);
            }
            ```
            > ![](https://i.imgur.com/ZTJGt9g.png)
    
    * **結構改變**：將原儲存資料方式就不同的結構強制轉型，像是浮點數（浮點數的儲存結構很不同）與整數的轉換

        1. 整數轉浮點數，**數據不丟失**

            ```c=
            void change_struct_1() {
                int a = 100;

                // 轉換儲存結構
                float b = (float) a;

                printf("b value: %f, size: %d", b, sizeof b);
            }
            ```

            > ![](https://i.imgur.com/Z5oiMu8.png)

        2. 浮點數轉整數，**數據丟失**

            ```c=
            void change_struct_2() {
                float a = 3.1415926;

                // 丟失小數部分數據
                int b = (int)a;

                printf("b value: %d, size: %d", b, sizeof b);
            }
            ```
            > ![](https://i.imgur.com/kFmEkhJ.png)

### 一般類型強制轉換 - 隱示

* 上面使用 `(<類型>)` 來做強制轉換，而隱式轉換則如下

    1. **使用 `=` 符號的隱示轉換**：
        
        ```c=
        void implict_change_1() {
            char c = 0x11223344;

            printf("c value: %d, size: %d", c, sizeof c);
        }
        ```
        > ![](https://i.imgur.com/KVaKN3j.png)

        :::info
        * 這種隱示轉換不安全，通常會有編譯器發出警告（會警告，但並不代表有問題）

            > ![](https://i.imgur.com/e1j6IPu.png)
        :::
        
    2. **當使用 `return` 關鍵字，在返回之前隱示轉換**：
        ```c=
        int implict_change_2() {
            char c = 0x11223344;

            return c;
        }
        ```

        :::info
        * 這種 `return` 隱示轉換不安全，通常會有編譯器發出警告（會警告，但並不代表有問題）

            > ![](https://i.imgur.com/R8OBcx9.png)
        :::

### 指標強制轉換

* 指標轉換「最好使用」顯示轉換，盡量不要使用隱式轉換；指標轉換也涉及兩個層面：^1.^ 指標類型轉換、^2.^ 指標指向類型

    1. **指標類型轉換**：改變數據的解析方式

        ```c=
        void ptr_value_change_1() {
            int a = 100;
            int *pa = &a;

            printf("a ptr value: %d, size: %d\n", *pa, sizeof(*pa));

            float *pb = NULL;
            
            // 指標類型轉換
            pb = (float*) pa;

            printf("b ptr value: %f, size: %d", *pb, sizeof(*pb));
        }
        ```
        > ![](https://i.imgur.com/TWaa9aY.png)

        :::warning
        * **結構的解析方式不同，導致數據解析異常**
            
            從這邊可以看出，改變指標的類型，使用 `*` 也會改變對於該結構的解析方式；上面改變指標為 `float*` 導致解析時不以 `int` 的結構來解析數據內容
        :::

    
    2. **指標指向類型**（不改變指標，改變解指標後的數據）

        ```c=
        void ptr_value_change_2() {
            int a = 0;
            float b = 3.1415926;

            int *pa = &a;
            float *pb = &b;

            *pa = (int)*pb;

            printf("a ptr value: %d, size: %d", *pa, sizeof(*pa));
        }
        ```
        > ![](https://i.imgur.com/ryhoQRy.png)

## sizeof

`sizeof` 看似 Function，但其實它是 C 語言中的 **運算符號 !**


### sizeof vs. array

```c=
char array[] = "Hello";
```

| sizeof 計算 | 結果 | 說明 | 注意 |
| -------- | -------- | -------- | - |
| sizeof(array) | 6 | 前面有說過 array 符號用在 **`sizeof` 會計算該 Array 所有的空間** | 包括 `\0` (字符算的結尾) |
| sizeof(array[0]) | 1 | 一個 array 元素大小 |  |
| strlen(array) | 5 | 使用 C 標準函式庫 | 會解析到 `\0` 為止 (不包含)，所以結果會少 1 |

* 測試 1：測試 Array 首指標、Array 元素、C 標準庫的 `strlen` 測試
    ```c=
    #include <stdio.h>
    #include <stdlib.h>

    void sizeof_vs_array() {
        char array[] = "Hello";

        printf("sizeof(array): %d\n", sizeof(array));
	    printf("sizeof(array[0]): %d\n", sizeof(array[0]));
        printf("strlen: %d\n", strlen(array));
    }
    ```

    > ![](https://i.imgur.com/VRviSfv.png)

* 測試 2：`sizeof` 配合 Array 首元素、首地址、C 標準庫的 `strlen` 測試

    ```c=
    #include <stdio.h>
    #include <stdlib.h>

    void test_ptr_strlen() {
        char str[] = "Hello";
        char* p = str;

        printf("sizeof(*p): %d, sizeof(p): %d, strlen(p): %d\n", 
                sizeof(*p), 
                sizeof(p), 
                strlen(p));    // 會計算 *p 到 '\0' 之間的位元數
    }
    ```
    > ![](https://i.imgur.com/IeHCn4Z.png)


### sizeof vs. 指標

```c=
char array[] = "Hello";
```

| sizeof 計算 | 結果 | 說明 | 注意 |
| -------- | -------- | -------- | - |
| sizeof\(p) | 4 | **指標 p 的大小** | sizeof 會根據類型判別大小，array 才會計算整體 |
| sizeof(\*p) | 1 | 一個 char 大小 |  |
| strlen(p\) | 5 | 使用 C 標準函式庫 | 會解析到 `\0` 為止 (不包含) |

* 測試：指標、指標取得的元素、C 標準庫的 `strlen` 測試

    ```c=
    void sizeof_vs_ptr() {
        char array[] = "Hello";
        char *p = array;

        printf("sizeof(p): %d\n", sizeof(p));
        printf("sizeof(*p): %d\n", sizeof(*p));
        printf("strlen(p): %d\n", strlen(p));
    }
    ```
    > ![](https://i.imgur.com/Insfmgb.png)

### array 作為參數傳遞

* C 語言由於 **考慮到 `Stack` 大小的關係** (入參會導致棧溢出)，設計在 **傳遞 Array 時，傳入的入參是 `Array address` 而不是整體**
    
    ```c=
    void transfer_array(int array[20]) {
	    printf("inner sizeof(array): %d\n", sizeof(array));
    }

    void template_array() {
        int array[20] = {0};

	    printf("outsize sizeof(array): %d\n", sizeof(array));

        transfer_array(array);
    }
    ```
    > ![](https://i.imgur.com/y5iZXJ5.png)

* 知道 Function 傳入的是 `Array address` 後，其實可以修改如下（將接收的類型改為 `pointers`），也會有一樣的功能

    ```c=
    void transfer_array_2(int *array) {
        for(int i = 0; i < 20; i++) {
            printf("Value: %d\n", *(array + i));
        }
    }

    void template_array() {
        int array[20] = {0};

        printf("outsize sizeof(array): %d\n", sizeof(array));

        transfer_array_2(array);
    }
    ```
    > ![](https://i.imgur.com/mbJ2KJc.png)

    :::success
    * 那要決定用哪一種，要選宣告接收 Pointer 還是 Array?
        
        對於程式的「**可讀性**」來說，我們還是會 **選擇使用 `a[10]` 這種寫法**，因為 **這能明確標示請使用者傳入的是一個數組，而不是一個普通數值的 Ptr**
    :::

* 使用 Array 類型的另類寫法：使用 Array 前，宣告大小變數，這個變數就可以讓 Array 變數使用

    ```c=
    void transfer_array_3(int size, int array[size]) {
        for(int i = 0; i < size; i++) {
            printf("Value: %d\n", *(array + i));
        }
    }

    void template_array() {
        int array[20] = {0};

        printf("outsize sizeof(array): %d\n", sizeof(array));

        transfer_array_3(5, array);
    }
    ```
    :::warning
    * **`size` 參數必須定義在 array 之前**! 否則編譯檢查不能通過
        
        > ![](https://i.imgur.com/URN3eX7.png)
    :::
    > ![](https://i.imgur.com/b2OSUwY.png)

## 高級指標

其實沒啥高級指標，應該說是指標比較高級的用法

### 指標數組 & 數組指標

* C 語言語法的重點是：**++前面是修飾詞++，++後面才是主語++** (使用中文來看時)
    
    * 變數 - **指標數組**：代表該變數，主語是 Array，並且修飾（類型）是 Ptr

    * 變數 - **數組指標**：代表該變數，主語是 Ptr，並且修飾（類型）是 Array

    | 類型 | 主語 (本質) | 修飾 | 舉例 |
    | -------- | -------- | -------- | - |
    | 指標 數組 | 數組 (Array) | 指標 (Ptr) | `int *p[5];` (本質是 Array，每個元素都為 `(int*)`，代表 5 個指標) |
    | 數組 指標 | 指標 (Ptr) | 數組 (Array) | `int (*p)[5]` (本質是 1 指標，指向 `int[5]` 的空間) |
    
    :::info
    * 有趣的是，英文會把變數名稱的修飾語放置在後面，前面是名稱（主語），跟這裡的概念相反過來
    
        像是 `revenueAverage`：在英文中可以拆解為 `revenue` 是名稱、`Average` 修飾
    :::
    

* **`*`、`[]` 在符號的前後，指明了該變量是指標還是數組 !（這還有關於到優先級）** 定義一個符號時思考的步驟如下
    
    1. **找到定義的符號，誰是「核心」**

        ```c=
        // `p` 是核心
        // `int`、`*`、`[]` 都是為了定義 p

        int *p[6];
        ```

    2. **看誰跟核心最近，誰的結合優先度 (優先級) 高，就先與之結合**；以下嘗試讓核心與不同的符號結合

        ```c=
        // 以下分號 `;` 不結合

        // 核心 a，跟 [] 結合，所以是數組
        int a[5];

        // 核心 p，跟 * 結合，所以是數組
        int *p;

        // 核心 function，跟 `()` 結合，所以是函數
        int function();
        ```

        * 以下列出幾個常見的優先度，從上到下代表優先度的高到低

            | 運算符號 | 描述 | 結合性 |
            | -------- | -------- | -------- |
            | () | 函數呼叫 |  |
            | [] | Array 引用 | 先於 `*` 符號結合 |
            | -> | 指標指向成員 | 左到右 |
            | . | Struct 成員引用 |  |
            | -/+ | 負號、正號 |  |
            | ++/-- | 遞增、遞減 |  |
            | ! | 邏輯否 |  |
            | ~ | 1 的補數 | 右到左 |
            | * | 指標引用 |  |
            | & | 記憶體位置 |  |
            | sizeof | 計算物件 Byte 大小 |  |
            | (type) | 強制轉型 |  |
            | * | 乘法 |  |
            | / | 除法 | 左到右 |
            | % | 取模 |  |
            | +/- | 加、減 | 左到右 |


            :::success
            符號的建立、分析，都先從基礎去分析，沒有無緣無故的規則 !
            :::
    
* 以下範例是「指標函數」、「函數指標」正確的使用方式

    ```c=
    // 指標數組 
    void major_array() {
        int *p[5];       // 主體是 Array（5 個指標）
        
        // int array[5] = {0}; 
        // p = &array;     // Error

        *(p + 0) = 1;    // 可用指標的方式訪問 Array
        *(p + 1) = 2;
        *(p + 2) = 3;
        *(p + 3) = 4;
        *(p + 4) = 5;

        printf("p: %p, p+1: %p\n", p, (p+1));
    }

    // 數組指標       (主要用在二維數組)
    void major_ptr() {
        int (*p)[5];        // 主體是指標（1 個指標）
        int array[5] = {0};

        p = &array;
        
        (*p)[0] = 1;    // 可用 Array 的方式訪問訪問指標指向的元素
        (*p)[1] = 2;
        (*p)[2] = 3;
        (*p)[3] = 4;
        (*p)[4] = 5;

        printf("p: %p, p+1: %p\n", p, (p+1));
    }
    ```
    > ![](https://i.imgur.com/2QlR1tP.png)

### 函數指標 - function pointer

* 首先要知道，**函數指標也是一個 ==指標==**，與其它指標並無不同；

    :::info
    * **函數（`Function`）到底是什麼**？
        
        **函數本值一段程式**，在經過編譯器編譯成匯編碼後，**載入到記憶體 (RAM) 中，是一段連續記憶體**，**而 ==函數指標就是該記憶體的第一個地址==**！

        ```c=
        // 偽程式，以下記憶體地址都是假的

        // 下面這段程式載入 RAM 中佔用了 0x11221100 ~ 0x11221140 的連續空間
        // 函數指標則是 0x11221100
        int function_hello() {        // 0x11221100

            char str[] = "Hello";    // 0x11221110
            char* p = str;            // 0x11221120

            printf("sizeof(*p): %d, sizeof(p): %d, strlen(p): %d\n",    // 0x11221130
                    sizeof(*p),
                    sizeof(p),
                    strlen(p));
        }    // 0x11221140
        ```
    :::

* 我們可以看到 `數組指標` 的類型是 `<數組類型> (*)[]`；而 `函數指標` 也是，主要是指標所以它的類型是 `<回傳類>(*)(接收參數)`

    ```c=
    #include <stdio.h>

    void test_hello(void) {
        printf("Hello Function\n");
    }

    int main(void) {
        // pFunc 是一個指標
        void (*pFunc)(void);

    	// pFunc = &test_hello;        // 同上
        pFunc = test_hello;

        pFunc();    // 加上 `()` 代表調用函數

        return 0;
    }
    ```
    > ![](https://i.imgur.com/j5xRmnw.png)

### typedef & 函數指標

* **typedef 這個關鍵字是用來定義新類型**，其實我們上面看到的都是自定義類型
    * `數組指標`：像是 `int (*p)[5]`

        ```c=
        #include <stdio.h>

        typedef int (*IntArrayPointer)[5];

        int main() {
            int arr[5] = {1, 2, 3, 4, 5};
            IntArrayPointer p = &arr;

            for (int i = 0; i < 5; ++i) {
                printf("%d ", (*p)[i]);
            }

            return 0;
        }
        ```
        
    * `指標數組`：像是 `int *p[5]`

        ```c=
        #include <stdio.h>

        typedef int* IntPointerArray[5];

        int main() {
            int arr[5] = {1, 2, 3, 4, 5};
            IntPointerArray p;

            for (int i = 0; i < 5; ++i) {
                p[i] = &arr[i];
                printf("%d ", *p[i]);
            }

            return 0;
        }
        ```
    
    * `函數指標`：像是 `void (*p)(int)`

        ```c=
        #include <stdio.h>

        typedef void (*FunctionPointer)(int);

        void printNumber(int num) {
            printf("Number: %d\n", num);
        }

        int main() {
            FunctionPointer p = printNumber;

            p(42);  // 調用函數指標

            return 0;
        }
        ```

        :::info
        * typedef 定義出的新類型並不占用 RAM
        :::

### 二重指標

* `二重指標` 可以存放 `一重指標` 的地址

    ```c=
    #include <stdio.h>

    int main() {
        int num = 42;
        int *ptr = &num;    // 一重指標指向變數 num
        int **doublePtr = &ptr;  // 二重指標指向一重指標 ptr

        printf("Value of num: %d\n", num);
        printf("Value through ptr: %d\n", *ptr);
        printf("Value through doublePtr: %d\n", **doublePtr);

        return 0;
    }
    ```

* `二重指標` 可以指向 指標數組 (`*p[]`)：二重指標也就是 **用來儲存 `指標數組` 的第一個元素的指標變量**

    | 指標 | 儲存 |
    | -------- | -------- |
    | 一重 int* | `int` 的 addr |
    | 二重 int** | `int*` 的 addr |

    ```c=
    #include <stdio.h>

    int main() {
        int num1 = 1, num2 = 2, num3 = 3;
        int *arr[] = {&num1, &num2, &num3};  // 指標數組

        int **doublePtr = arr;  // 二重指標指向指標數組的第一個元素

        for (int i = 0; i < 3; ++i) {
            printf("Value through doublePtr[%d]: %d\n", i, *doublePtr[i]);
        }

        return 0;
    }
    ```
    > ![image](https://hackmd.io/_uploads/SyWC5xWjp.png)



## typedef
 
1. **typedef 用來定義新類型，形式越複雜 typedef 的優勢則越明顯** 

2. **typedef 的另一個優點是 ==方便移植==**
    :::danger
    * **typedef 是一個儲存類的關鍵字**，而 **變量只能被一種儲存類的關鍵字修飾**

        > 其他儲存類的關鍵字：`auto`、`extern`、`static`、`register`

        ```c=
        typedef static int ClzNum[10];    // 編譯錯誤
        ```
        錯誤如下
        > ![](https://i.imgur.com/rVv18Y5.png)

    :::
    

### typedef 看法解析

1. typedef 是給類型取別名，所以 typedef 定義的東西都是類型；所以 **typedef 定義中一定會有一個 `類型`**

2. typedef 定義出的類型：**移除定義中 typedef 關鍵字**，再將 **類型看作變量**，就能知道它的類型
    
    :::info
    * 從這裡可以發現將 typedef 關鍵字 移除後，它就只是一個普通的變量語句
    :::

    * 數組類型

        ```c=
        // MyClass 就是類型
        typedef int MyClass[5];

        // 去除 typedef 關鍵字
        int MyClass[5];

        // 再將 MyClass 看成變量
        int <變量>[];    // 數組類型
        ```

    * 函數指標類型

        ```c=
        // MyFunc 就是類型
        typedef int* (MyFunc*)(int);

        // 去除 typedef 關鍵字
        int* (MyFunc*)(int);

        // 再將 MyFunc 看成變量
        //    MyFunc 變量是一個指標
        //    MyFunc 是函數指標(因為後面是 `()`)
        //    MyFunc 該函數指標回傳一個 int*
        int* (<變量>*)(int);
        ```


### define & typedef

| 使用 | 功能 | 編譯時機 |
| -------- | -------- | -------- |
| define | 簡單 **宏替換** | **==預處理==** |
| typedef | **重新定義類型** | **==編譯期==** |

* **`define` & `typedef` 的區別**：
    
    1. **typedef 不是簡單替換，而是 typedef 對類型重新定義**

        ```c=
        #define		dpInt		int*        // 不可加 `;` 號

        typedef 	int*		tpInt;

        void typedef_define_diff_1() {
            // int* dp1, dp2;     // 同下
            dpInt dp1, dp2;
            
            // int* tp1, *tp2;    // 同下
            tpInt tp1, tp2;

            int a = 20;

            tp1 = tp2 = dp1 = &a;
            dp2 = a;

            printf("tp1: %d\n", *tp1);
            printf("tp2: %d\n", *tp2);
            printf("dp1: %d\n", *dp1);
            printf("dp2: %d\n", dp2);        // 這是一個 int 類型

        }
        ```
        
    2. **#define 可以實現類型組合，而 typedef 不行**：define 過的仍可再使用其他關鍵字修飾，而 typedef 不行

        ```c=
        #define		dInt		int

        typedef 	int		tInt;

        void typedef_define_diff_2() {
            unsigned dInt c1;
        //	unsigned tInt c2;    // 不可再組合
        }
        ```
        > ![](https://i.imgur.com/OqYQ8vk.png)

    3. **define 無法創建新類型**

        ```c=
        // 定義新類型 (新類型為 Class)
        typedef		char	Class[10];

        void typdef_create_new_definition() {
            Class clz;

            for(int i = 0; i < sizeof(clz)/sizeof(clz[0]); i++) {
                *(clz + i) = i * 3;
                printf("index: %d, value: %d\n", i, *(clz + i));
            }
        }
        ```

### typedef & struct 

* struct 結構最簡單的定義如下

    ```c=
    struct Node {};
    ```
    
* `struct` 配合使用上 `typedef` 有以下幾種情況

    1. 串上 `typedef` 可省去 `struct` 關鍵字

        ```c=
        typedef struct MyNode {} Node_T;

        void my_node() {
            Node_T node;
        }
        ```
    2. **定義兩個類型**：一個結構類型，另一個結構指標類型

        ```c=
        // 定義等同於
        //    typedef <類型> Node_T2;
        //    typedef <類型> *pNode_T2;
        typedef struct MyNode_2 {} Node_T2, *pNode_T2;

        void my_node_2() {
            Node_T2 node;

            pNode_T2 pNode;
        }
        ```
        
### typedef & const

* 我們知道 `const` 是如何修飾指標的，有分為 3 個種類

    1. `const int* p`、`const int* p`：修飾指標指向內容不可改
    
    2. `int* const p`：修飾指標指向不可改
    
    3. `const int* const p`：內容、指向都不可改

* 如果以上功能要配合 `typedef` 使用

    1. **`const` 修飾新類型變量**：指向不可修改

        ```c=
        typedef int* pInt;

        void const_typedef() {
            int apply = 10;
            int bannana = 5;

            const pInt p = &apply;    // 等同於 `int* const p`
	        // pInt const p = &apply;    // 同上

        //    p = &bannana;    // read-only, Error 編譯錯誤

            printf("p value: %d\n", *p);
        }
        ```
        > ![](https://i.imgur.com/vTpMgpA.png)

    2. **`const` 修飾新類型宣告**：修飾內容不可修改

        ```c=
        void const_typedef_2() {
            short apply = 10;
            pShort p = &apply;
            printf("initialize p value: %d\n", *p);

            short bannana = 5;
            p = &bannana;
        //	*p = bannana;    // read-only, Error 編譯錯誤
            printf("After change p value: %d\n", *p);
        }
        ```
        > ![](https://i.imgur.com/gETpQqR.png)

    3. **`const` 修飾內容、指向皆不可改**

        ```c=
        typedef const long*	pLong;

        void const_typedef_3() {
            long apply = 200;
            const pLong p = &apply;
            printf("initialize p value: %d\n", *p);


            short bannana = 103;
            p = &bannana;
            p = bannana;
            printf("After change p value: %d\n", *p);
        }
        ```
        > ![](https://i.imgur.com/VRCoRqH.png)

### typedef & 函數指標

* 我們就分析一個比較複雜的函數指標，以下兩個是相等意思

    1. **函數指標原型**
    
        ```c=
        void printTest(int count) {
            for(int i = 0; i < count; i++) {
                printf("Hello: %d\n", i);
            }
        }

        // ---------------------------------------------------------
        // 1. 首先知道 a[10] 是一個指標數組 (主體是數組
        // 2. 之後接上 `()` 代表是一個函數，得知外層是一個函數指標
        // 3. 該函數指標返回 void、接收 `void(*)(int)` 函數指標
        void (*a[10]) (void(*)(int));

        void func_ptr_1() {
            a[0] = printTest;    // 指定函數指標

            a[0](5);    // 呼叫函數
        }
        ```

    2. **`typedef` 改寫上面的範例**
    
        ```c=
        // 功能完全同上
        void printTest(int count) {
            for(int i = 0; i < count; i++) {
                printf("Hello: %d\n", i);
            }
        }

        // 宣告一個新類型 pFunc (函數指標)
        typedef void (*pFunc)(void(*)(int));
        
        // 定義一個 Array 的 pFunc
        pFunc pFuncArray[10];

        void func_ptr_2() {
            pFuncArray[0] = printTest;

            pFuncArray[0](10);
        }
        ```
        > ![](https://i.imgur.com/lwbAIOr.png)



### typedef & sizeof

* `typedef` 在使用 `sizeof` 要注意**一定要括號**，否則會報錯誤
    
    1. 正常可以測量出 size 是 8 byte
    
    2. 兩者配合使用沒有括號，會拋出錯誤 `error: expected expression`
    
    ```c=
    typedef struct {
        char a;
        short b;
        int c;
    } Test_T;

    int main()
    {
        //"1. "
        printf("%d\n", sizeof (Test_T) );
        //"2. "
        printf("%d\n", sizeof Test_T);

        return 0;
    }
    ```

## 二維 Array

```c=
// 二維 Array

int a[2][5];    // [2] 代表一維，[5] 代表二維，可解釋成 2 個 [5] 的空間
```
> ![](https://i.imgur.com/iez7j5q.png)

### 二維 Array 的首地址

* 在一維 Array 中我們可以知道，一維 Array 的符號，等價於 `&a[0]` 的地址

    ```c=
    void One_dimen_array_head() {
        
        int a[6];

        if(a == &a[0]) {
            printf("Same");
        } else {
            printf("Different");
        }
    }
    ```
    > ![](https://i.imgur.com/XDB06ec.png)

* 推斷可得知二維的符號，等價於 `&(&a[0])[0]` 的地址
    
    ```c=
    void Two_dimen_array_head() {

        int a[6][6];

        if(a == &(&a[0])[0]) {
            printf("Same on a == &&a[0][0]");
        } else {
            printf("Different");
        }
    }
    ```
    > ![](https://i.imgur.com/gh1HCIT.png)

### 訪問 二維 Array

* 使用普通 Pointer 訪問

    ```c=
    void visit_by_ptr() {
        int array[6][6] = {0};
        array[0][0] = -1;
        array[0][1] = 10;
        array[0][2] = 7;
	    array[1][0] = -3;
        array[1][1] = 100;
        array[1][2] = 97;


        int *p1 = array[0];    // 指向第一行的第一個元素
        int *p2 = array[1];    // 指向第二行的第一個元素

        printf("array[0][0]: %d\n", *p1);

        printf("array[0][1]: %d\n", *(p1 + 1));
        printf("array[0][2]: %d\n", *(p1 + 2));

        // 證明 Array 是連續空間，其實可以用一個 ptr 訪問全部二維 Array
	    printf("array[1][0]: %d\n", *(p1 + 6));    

        printf("array[1][1]: %d\n", *(p2 + 1));
        printf("array[1][2]: %d\n", *(p2 + 2));
    }

    ```
    > ![](https://i.imgur.com/An97YyK.png)


* 使用 **陣列指標** 訪問

    ```c=
    void visit_by_ptr_array() {
        int array[6][6] = {0};
        array[0][0] = -1;
        array[0][1] = 10;
        array[0][2] = 7;
        array[1][1] = 100;
        array[1][2] = 97;

        int (*p)[6] = array;    // 指向第一個元素

        printf("array[0][0]: %d\n", *(*p));

        printf("array[0][1]: %d\n", *(*p + 1));    // *p 是第一個地址
        printf("array[0][2]: %d\n", *(*p + 2));    

        printf("array[1][1]: %d\n", *(*(p + 1) + 1));
        printf("array[1][2]: %d\n", *(*(p + 1) + 2));
    }
    ```
    > ![](https://i.imgur.com/SWVm3hP.png)

    :::info
    1. `a[i][j]` 對於陣列指標來說，等同於 `*( *(p + i) + j)`
    
    2. 上面宣告的陣列指標 `int (*p)[6]`，其中的 **`[6]` 並不能亂定義，必須要與二維數組的數量相同才可以** !
    :::
    

## 更多的 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`