# 2018q3 Homework1
contributed by < `littlepee` >
:::danger
你的提問呢?回顧你過去開發的程式,難道沒想到概念上有衝突或者激發出更多想法嗎?
:notes: jserv
:::
## 為什麼要深入學習 C 語言?
* 第一個 C 語言編譯器如何編寫的?
* 可能是用 B 語言或是混合 B 語言與 PDP 組合語言編寫的
* 先編寫一個C語言的子集的編譯器,在通過這個子及去慢慢完成完整的C語言編譯器
* 先創造出最基本子集 C0,再利用 C~0~ 去創造出 C~1~,再從 C~1~ 創造出 C~2~,設計漸漸複雜,一直開發到足夠強大的 C~N~,便可開發出 C語言編譯器
:::info
關於「可能是用 B 語言或是混合 B 語言與 PDP 組合語言編寫的」,可參閱 Bell Labs 過往的材料,有提及 ancient C compilers 的緣由 (和這猜想有出入)
:notes: jserv
:::
## 指標篇
* " & " 不要都念成 "and",在指標操作的時候,要念成 “address of”
* 沒有「雙指標」只有「指標的指標」
* C 語言中只有 call-by-value
* 「指標的指標」英文是 "a pointer of a pointer"
* Pointers vs. Arrays
* in declaration
* extern, 如 `extern char x[];` => 不能變更為 pointer 的形式
* definition/statement, 如 `char x[10]` => 不能變更為 pointer 的形式
* (如果10拿掉,就可以換成 pointer。但因為有10,所以是有明確空間,連續10個 bytes 的記憶體空間, pointer 是一個 word 長度記錄了記憶體位址,所以不能互換)
* parameter of function, 如 `func(char x[])` => 可變更為 pointer 的形式 => `func(char *x)`
* in expression
* array 與 pointer 可以互換
```clike
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]);
}
```
## 函式呼叫篇
* C 語言中不允許 "nested function"
* Process 和 C 程式的關聯
* instructions : 從 object file (ELF) map 到 process 的 program code
* static data : 靜態初始化的變數
* BSS : 未初始化的變數或資料
* 可用 `size` 指令來觀察
* Heap 或 data segment : 執行時期才動態配置的空間
* sbrk 系統呼叫 (sbrk = set break)
* malloc/free 實際的實做透過 sbrk 系統呼叫
* 藏在 Heap 裡的細節
* free() 釋放的是 pointer 指向位於 heap 的連續記憶體,而非 pointer 本身佔有的記憶體 (*ptr)。
舉例來說:
```clike
#include <stdlib.h>
int main() {
int *p = (int *) malloc(1024);
free(p);
free(p);
return 0;
}
```
編譯不會有錯誤,但運作時會失敗:
```
*** Error in './free': double free or corruption (top):
0x000000000067a010 ***
```
倘若改為以下:
```clike
#include <stdlib.h>
int main() {
int *p = (int *) malloc(1024);
free(p);
p = NULL;
free(p);
return 0;
}
```
則會編譯和執行都成功。
因此,為了防止對同一個 pointer 作 free() 兩次的操作,而導致程式失敗,free() 後應該設定為 NULL。
## 遞迴呼叫篇
* 遞迴程式設計
* 遞迴法 (Recursive)
* 非遞迴法 (Iterative)
* 可縮減原先使用遞迴法的函式呼叫造成之執行時期開銷
* Tail recursion
* 可加速原先之遞迴,可減少一半的資料回傳,而概念上是運用從第一次 call function 後即開始計算值,不像之前的遞迴需 call function 至中止條件後才開始計算值,再依序傳回到最上層,因此可大幅降低時間,減少 stack 使用量。
## 前置處理器應用篇
* `#`: [Stringification/Stringizing(字串化)](https://gcc.gnu.org/onlinedocs/cpp/Stringizing.html)
* `##`: [concatenation(連結,接續)](https://gcc.gnu.org/onlinedocs/cpp/Concatenation.html)
```clike
#define DECLARE_OBJECT(name) \
struct __##name##_node; \
typedef struct __##name##_node *name##_node; \
struct __##name##_node { \
name element; \
name##_node next; \
}; \
void append_##name(const name *X, name##_node *list); \
void delete_##name##_list(name##_node *list);
DECLARE_OBJECT(light)
DECLARE_OBJECT(rectangular)
DECLARE_OBJECT(sphere)
```
light 在 `DECLARE_OBJECT(light)` 中會取代 name,因此會產生以下程式碼:
```clike
struct __light_node;
typedef struct __light_node *light_node;
struct __light_node { light element; light_node next; };
void append_light(const light *X, light_node *list);
void delete_light_list(light_node *list);
```
## linked list 和非連續記憶體操作
* circular linked list
* [龜兔賽跑](http://www.csie.ntnu.edu.tw/~u91029/Function.html#4)
* 一、尋找循環長度的倍數:
* 龜兔從起點同時出發,龜走一步、兔就走兩步。由於兔比龜快,當龜兔皆進入循環之中,兔必然領先數圈、從後方追上龜。
* 當龜兔相遇,龜兔的行走距離差,即是循環長度的倍數。龜一倍速、兔兩倍速,龜兔的行走距離差,剛好是龜的行進距離。龜的行進距離即是循環長度的倍數。
* 二、尋找循環起點:
* 龜退回起點,兔原地待命,龜兔同時出發,龜走一步、兔走一步。龜兔相遇之處即是循環起點。
* 三、尋找循環長度:
* 從循環之中任意一處出發,一次走一步,繞一圈回到原處,即得循環長度。
* 時間複雜度:
* 令 μ 是出發起點到循環起點的距離, λ 是循環長度。龜最多走 μ + λ 步,兔最多走 2μ + 2λ 步,時間複雜度為 3μ + 3λ = O(μ + λ) 。