# 2018q3 Homework1
contributed by < [p61402](https://github.com/p61402) >
###### tags: `sysprog`
## 指標篇
## Understanding Declarations
```C
(*(void(*)())0)();
```
在這段程式碼中,`(void((*)())0` 表示將 `0x0` 轉換成一個指標指向「一個沒有引數且回傳值為空的函式」。若將`(void((*)())0` 以 `ptr` 表示,`(*ptr)()`代表呼叫此函式。
```C
typedef void (*funcptr)();
(* (funcptr) 0)();
```
此段程式碼與 `(*(void(*)())0)()` 同義,都會造成 [segmentation fault](https://en.wikipedia.org/wiki/Segmentation_fault) ,根據維基百科上的描述,造成的原因為 Attempting to access a nonexistent memory address (outside process's address space),為了避免存取到自身 process 以外的記憶體位址,作業系統會有 [Memory Protection](https://en.wikipedia.org/wiki/Memory_protection) 的機制。
## 回頭看 C 語言規格書
- C99 [3.14] 對 **Object** 的定義 : region of data storage in the execution environment, the contents of which can represent values.
- **&** 在涉及指標操作時,代表的意思是「取址」,唸作 "address of"
- C99 [6.2.5] 的描述 : pointer type 可由 function type 、object type 或 incomplete type 衍生,reference 到被參照的實體。
## void *
`void*`是一種 incomplete type,在 C99 [6.2.5] 中明確說明了 incomplete type 雖然描述了`object`但卻缺乏關於 size 的資訊,但`void*`究竟有什麼功能? stackoverflow 上的[這篇文章](https://stackoverflow.com/questions/11626786/what-does-void-mean-and-how-to-use-it)的回答舉了 qsort 當做例子。
```C
void qsort(void *base, size_t nmemb, size_t size,
int (*compar)(const void *, const void *));
```
第一個引數`void *base`可以接收任何型態的指標,例如: int, double, char 等等,此種寫法能夠讓函式具有 overloading 的功能。
:::warning
:question:問題:根據 manual page 對於`malloc`的描述,`malloc`回傳值是指向宣告的記憶體位址的指標`void*`。
```C
void *malloc(size_t size);
```
在老師的共筆有說到`void*`的存在是為了讓使用者 explicitly cast,以避免 undefined behavior 產生。
但是我在瀏覽別人的程式碼的過程中經常發現沒有顯式轉型的例子,但依然不影響程式運行的結果,那麼究竟在 C 語言當中使用`malloc`函式,使用implicit conversion 與 explicit conversion 兩種方法究竟會不會影響程式的運行?若不影響的話哪一種方法比較好?或是各有優缺點呢?
```C
int *arr = malloc(sizeof(int *) * length); // implicit conversion
int *arr = (int *) malloc(sizeof(int *) * length); // explicit conversion
```
:::
於是我就去翻 C 語言的規格書:
- C11 [6.3.2.3]
A pointer to void may be converted to or from a pointer to any object type. A pointer to any object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.
- C11 [6.5.16.1]
In simple assignment (=), the value of the right operand is converted to the type of the assignment expression and replaces the value stored in the object designated by the left operand.
根據以上兩點可以知道,在 C 語言中,使用 simple assignment (=),`void*`是可以被 implicit cast 成為其他型別的 pointer,是否會在轉型過程中發生問題則沒有被提及。
然後我在 StackOverflow 找到[這篇熱烈討論的文章](https://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc)關於是否該將 malloc 的結果顯式轉型,許多人對於是否該將結果強制轉型的看法不一,以下整理出幾個論點:
1. 在 C 語言中沒有必要顯式轉型,因為`void*`會自動且安全地被轉型成為其他的 pointer type,強制轉型反而會造成程式碼雜亂難以閱讀。
2. 然而在 C++ 當中必須要顯式轉型,否則會造成編譯錯誤如下,因此將`void*`顯式轉型會使得程式碼的便攜性 (portable) 增加。
```C
error: invalid conversion from ‘void*’ to ‘int*’ [-fpermissive]
int *arr = malloc(sizeof(int) * 10);
```
但對於第二個論點我個人並不支持,因為 C 與 C++ 在本質上已是不同的程式語言,兩者具有截然不同的特性,將兩者特性混用並不是一個好主意。況且 C++ 本身支援`new`這個配置記憶體的方法,甚至在功能上與方便性更甚`malloc`,在 C++ 程式碼中使用`new`會比`malloc`更好一些。
在[維基百科](https://en.wikipedia.org/wiki/C_dynamic_memory_allocation#Type_safety)上也有提及兩者的優缺點。
綜合以上的資料,我認為在 C 語言當中,既然`void*`能夠在使用 simple assignment (=) 時自動隱性轉型,況且不論是否進行顯式轉型得到的結果都相同,那麼使用`malloc`函式可以不必將結果顯式轉型。
## A Pointer to a pointer
指標的指標常用來改變傳入函式的原始數值,可延長變數的 lifetime ,我的經驗是時常利用指標的指標配合`malloc`來進行二維陣列的宣告。
例如以下程式碼透過`arr`這個指標指向一段長度為 3 的`int *`型別記憶體位址,再將這三個指標指向一段長度為 4 的`int`型別記憶體位址,利用此方法就可以建立一個 3\*4 的陣列。
```C
int i;
int **arr = malloc(3 * sizeof(int *));
for (i = 0; i < 3; i++)
arr[i] = malloc(4 * sizeof(int));
```
:::info
你想過這樣如果要 free() 時,是否需要兩層的迴圈呢?倘若有人只是 `free(arr);` 會發生什麼事?又,該如何改善?
Hint: memory leak
:notes: jserv
:::
> 若要 free() 的話,應反向操作,釋放較晚宣告的記憶體,接著才釋放指向此段位址的記憶體,如下列程式碼所示。
> ```C
> for (i = 0; i < 3; i++)
> free(arr[i]);
> free(arr)
> ```
> 針對若只有`free(arr)`的部分,我簡單做了個小測試,我將二維陣列的第一個元素`arr[0][0]`的數值設為 5,再宣告一個指向整數型別的指標`*a`指向`&arr[0][0]`,實驗不同釋放的記憶體方式,判斷是否會造成 memory leak 。
> ```C
> int i = 0;
> int *a;
> int **arr = malloc(3 * sizeof(int*));
> for (i = 0; i < 3; i++)
> arr[i] = malloc(4 * sizeof(int));
>
> arr[0][0] = 5;
> a = &arr[0][0];
>
> printf("address of a: %p\n", &a);
> printf("value of a: %d\n", *a);
>
>
> for (i = 0; i < 3; i++)
> free(arr[i]);
> free(arr);
> printf("after free...\n");
>
> printf("address of a: %p\n", &a);
> printf("value of a: %d\n", *a);
> ```
> 執行結果如下:
> ```
> address of a: 0x7ffd0cfc1638
> value of a: 5
> after free...
> address of a: 0x7ffd0cfc1638
> value of a: 0
> ```
> 可以發現`*a`所指向的位址不變,但數值更新為 0 ,可知已經成功釋放記憶體。
> 但若將釋放記憶體的過程改為只有呼叫`free(arr)`,會產生不同的結果如下:
> ```
> address of a: 0x7ffcbea12d88
> value of a: 5
> after free...
> address of a: 0x7ffcbea12d88
> value of a: 5
> ```
> 同樣的`*a`指向的記憶體不變,但會發現數值仍然為 5 ,由此可知若只有呼叫`free(arr)`這種方式,只能釋放`**arr`這個指標所指向的位址,對於再下一層所指向的記憶體無法進行釋放,長久下來會導致無用的資源持續佔用記憶體,以致於 memory leak ,最後造成記憶體耗盡,程式崩潰。
:::success
有個工具叫做 [cppcheck](http://cppcheck.sourceforge.net/),可進行程式碼靜態分析,也能指出上述 memory leak
```shell
$ sudo apt install cppcheck
```
延伸閱讀: [How to dynamically allocate a 2D array in C?](https://www.geeksforgeeks.org/dynamically-allocate-2d-array-c/)
:notes: jserv
:::
## Pointers vs. Arrays
```C
int main() {
int x[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
printf("%d %d %d %d\n", x[4], *(x + 4), *(4 + x), 4[x]);
}
```
以上程式碼中四種方法的結果都是相同的,array subscripting 只是一種語法糖,實際上存取 array 也就是對一段連續的記憶體進行操作,即便是二維陣列也是如此。
## Function Pointer
function pointer 顧名思義就是指向函式的指標,同樣以`qsort`的宣告來舉例的話,其中 `*compar` 這個指標可指向任意引數及回傳形態與之相同的函式位址,即可實現如前面提到過的 overloading 的功能,以`qsort`為例的話就可以將`*compar` 指向基於 asending order 或是 descending order 的比較。
```C
void qsort(void *base, size_t nmemb, size_t size,
int (*compar)(const void *, const void *));
```
:::info
`*compar` 不是 pointer type,而 `compar` 才是,注意用精準的表達式
:notes: jserv
:::