--- title: 'void、NULL、返回值、臨時變量' disqus: kyleAlien --- void、NULL、返回值、臨時變量 === ## Overview of Content 這裡主要來看看 C 語言的細節部分 :::success * 如果喜歡讀更好看一點的網頁版本,可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/) 本篇文章對應的是 [**C 語言解析:void 意義、NULL 意義 | main 函數調用、函數返回值意義 | 臨時變量的產生**](https://devtechascendancy.com/meaning_void_null_return-value_temp-vars/) ::: [TOC] ## void 意義 void 是一種「類型」,稱為「無類型」,可理解成尚未定義的類型 (類似 Java 的 Object 類) :::success * **在指定類型後,編譯器才會按照該類型的解析方式去讀取該區塊的記憶體** ::: ### void 指標 1. void 指標代表一段尚未確認(尚未定義類型)的數據,可以透過強制轉型,告訴編譯器該如何解析該段記憶體數據 ```c= typedef struct MyClz { int a; short b; char c; } MyClz_t; void test_void() { MyClz_t clz; clz.a = 10000; clz.b = 100; clz.c = 'a'; void *p = &clz; printf("analysis by MyClz_t: %d\n", ((MyClz_t *)p)->a); printf("analysis by MyClz_t: %d\n", ((MyClz_t *)p)->b); printf("analysis by MyClz_t: %c\n", ((MyClz_t *)p)->c); } ``` > ![](https://i.imgur.com/Sw9yiMV.png) 2. 較常見的是使用 `malloc` API(這個 API 來自於 C 語言提供的標準庫,用來動態申請記憶體空間),該 API 就是返回一個 `void *` 指標;測試範例如下 ```c= #include <stdlib.h> typedef struct MyClz { int a; short b; char c; } MyClz_t; void test_void_lib() { void *p = malloc(sizeof(MyClz_t)); MyClz_t *pClz = (MyClz_t *)p; pClz->a = 222333; pClz->b = 111; pClz->c = 'z'; printf("analysis by MyClz_t: %d\n", pClz->a); printf("analysis by MyClz_t: %d\n", pClz->b); printf("analysis by MyClz_t: %c\n", pClz->c); free(pClz); } ``` ## NULL 意義 **NULL 並不是 C 語言的關鍵字**,其定義如下,**NULL 在 C/C++ 是不同的意思** ```c= #ifdef _cplusplus #define NULL 0 // C++ 定義 #else #define NULL (void *)0 // C 定義 #endif ``` 這裡我們來解釋一下 C 語言的 NULL,它是一個 ^1.^ **void\* 類型的指標**、^2.^ 0 是指向位置 `0x00000000` 的記憶體空間 :::info * 大部分 CPU 中的 `0x00000000` 記憶體是特殊段,不可隨意訪問 ::: ### `'\0'`、`'0'`、`0`、`NULL` 差異 | 目標 | 類型 | 說明 | | -------- | -------- | -------- | | '\0' | char | **ASCII 的 0**,在 C 中作為字符串的結尾 | | '0' | char | **ASCII 的 48** | | 0 | 數字 | 就是 0 | | NULL | void* | 也是 0,不過它是地址 `0x00000000` 的意思 | ## 返回值意義 ### main 函數 - 標準寫法 * main 是 C 語言的起始點,它有以下幾種寫法 (C99 版本),這幾種寫法都是可以的 1. **main 入口函數 - 不帶參數** ```c= int main(void) { printf("Hello World"); return 0; } ``` 2. **main 入口函數 - 帶參數**:`argc` 是數量,一個是參數指標;**預設參數是該檔案的路徑** ```c= // 指標數組 int main(int argc, char *argv[]) printf("argument count: %d\n", argc); // 以下兩種寫法相同 printf("argument value: %s\n", *argv); printf("argument value: %s\n", argv[0]); return 0; } ``` 以下另外一種寫法(二重指標)也可以,這兩種寫法都可以得到同樣的結果 ```c= // 二重指標 int main(int argc, char **argv) printf("argument count: %d\n", argc); // 以下兩種寫法相同 printf("argument value: %s\n", *argv); printf("argument value: %s\n", argv[0]); return 0; } ``` > ![](https://i.imgur.com/Gz3Bhgh.png) * 編譯出 `.out` 執行檔後,在呼叫時需要傳參數就可以直接將參數加在後面 > 如果有多個參數,那參數間要使用 `空格` 隔開 ```shell= gcc return_test.c -o return_test.out ./return_test.out test 12345 ``` > ![](https://i.imgur.com/Qed2nlo.png) ### 誰調用 main 函數 - main 返回值 * **誰調用 main 函數**:這分為 **兩種方式** (系統決定) 1. **無系統 MCU 系列**:是由匯編 (組合) 語言來調用,先調用加載函數庫,初始化 Stack、Heap,最後會調用 main 函數 2. **有系統 PC 系列**:由系統來調用,透過 `fork` 創建虛擬記憶體的新分頁、`exec` 覆蓋並執行 main 函數 * **main 函數的返回值**: 會由調用它(創建這個程序,可能是 PC 系統,也可能是 MCU)的 Parent Process 接收,而接收到返回值後如何操作則是 Parent Process 決定 ### C 語言 - 返回數的習慣 * C 語言 - 返回數的習慣有兩種 !(並非強制,建議在使用 API 時還是要詳細看看文件說明) | 函數類型 | 返回 0 的含意 | 返回非 0 的含意 | | - | - | - | | 判斷函數 | 成功 | 失敗 | | 操作函數 | 失敗 | 成功 | 1. **操作函數**:操作類型的函數,返回 **0 代表成功,非 0(像是返回 -1)則是失敗** ```c= void use_string_cmp() { char *p = "Hello"; char *pa = "Hello"; // 呼叫操作類型函數 show_on_console int show_result = show_on_console(p, pa); if(show_result == 0) { printf("Success\n"); } else { printf("Fail\n"); } } ``` 2. **判斷函數**:邏輯判斷類型的函數,返回 **0 代表判斷失敗,非 0(像是返回 1)則是成功** ```c= // 邏輯判類型的函數 int is_litte_endian() { union Test t; t.a = 1; if(t.b == 1) { printf("litte_endian\n"); return 1; // 系統為小端 } else { printf("big_endian\n"); return 0; // 系統為大端 } } ``` ## C 語言產生的臨時變量 臨時變量由 C 語言自己提供,並不會顯示顯現 (匿名),它們的顯示時機如下 ### 強制轉型 * 在強制轉型成會產生一個臨時匿名變量,強制轉型的案例如下: ```c= void force_change() { float f = 10.023; // 這裡會產生臨時變量 `x` int a = f; printf("a is %d\n"); } ``` 1. 產生臨時變量 `x`:就像是上面案例… 將 `10.023` 的整數部分 `10` 存起來 2. 將臨時變量 `x` 賦予 `a` 3. 在離開函數時,銷毀臨時變量 `x` ### 臨時變量運算 * 另外一個產生臨時變量的時機就是在運算時,運算的案例如下: ```c= void cal() { int a = 10; // 這裡會產生臨時變量 `x` float b = a / 3; printf(" is %d\n", b); } ``` 1. 產生臨時變量 `x`:就像是上面案例… 將 `10 / 3` 的結果 `3.33333` 存起來 2. 將臨時變量 `x` 賦予 `b` 3. 在離開函數時,銷毀臨時變量 x ## 更多的 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`