---
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);
}
```
> 
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;
}
```
> 
* 編譯出 `.out` 執行檔後,在呼叫時需要傳參數就可以直接將參數加在後面
> 如果有多個參數,那參數間要使用 `空格` 隔開
```shell=
gcc return_test.c -o return_test.out
./return_test.out test 12345
```
> 
### 誰調用 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`