# 2018q3 Homework1 contributed by <`letticee`> >固定格式,請注意 >[name=課程助教][color=red] ## 為什麼要深入學習 C 語言? ## 開發工具和規格標準 * c 語言可以自己編譯自己,gnu 的 gcc 是用 c 寫的(一些 c++) * c 跟 c++ 在發展的路上越差越多 e.g. c99 有的 designated initializer,c++沒有、是留給constructor去做 * 可對指標做+ - bitwose operator,不可* / * ~~雙重指標~~ **指標的指標** pointer to pointer --- ## 指標篇 ### typedef - 範例 - 建立 PFI 作為一個指向「一個擁有兩個 char * 當作參數並回傳 int 的函式」的指標的別名 ```c typedef int (*PFI)(char *, char *); ``` - 未使用typedef ```c int do_math(float arg1, int arg2) { return arg2; } int call_a_func(int (*call_this)(float, int)) { int output = call_this(5.5, 7); return output; } int final_result = call_a_func(&do_math); ``` - 改寫: ```c typedef int (*MathFunc)(float, int); int do_math(float arg1, int arg2) { return arg2; } int call_a_func(MathFunc call_this) { int output = call_this(5.5, 7); return output; } int final_result = call_a_func(&do_math); ``` [Wikipedia: typedef](https://zh.wikipedia.org/wiki/Typedef) :::info 除了閱讀 Wikipedia,請務必詳閱 C99/C11 規格書描述 :notes: jserv ::: - 呼叫地址為 0 的函式(? ```c int main() { typedef void (*funcptr)(); (* (funcptr) (void *) 0)(); } ``` - 0 這個地址在使用者層級不能隨意呼叫,所以會發生 segmentation fault 記憶體存取的錯誤 :::info 這限制來自 [MMU](https://en.wikipedia.org/wiki/Memory_management_unit),請思考在 GNU/Linux 上對應的處理機制為何 :notes: jserv ::: * 回傳 function pointer 的 function ```c void ( *signal(int sig, void (*handler)(int)) ) (int); ``` (https://stackoverflow.com/questions/15739500/how-to-read-this-prototype) * Q:signal 的用途? * C99 [7.14.1.1] The signal function * The signal function chooses one of three ways in which receipt of the signal number sig is to be subsequently handled. ### 一些名詞 - C99 [3.14] object - region of data storage in the execution environment, the contents of which can represent values - 有明確的大小 (cf. incomplete type) - 資料:連續的記憶體 - undefined behavior - e.g. i++++++ 丟到 compiler => ? 怎樣是對怎樣是錯 :::info 可參照 [未定義行為篇](https://hackmd.io/s/Skr9vGiQm) :notes: jserv ::: - C99 [6.2.5] imcomplete type - An array type of unknown size is an incomplete type. ### pointer type - 可能衍生自 function / object / incomplete type -- 稱作 referenced type - C99 [6.2.5] - The construction of a pointer type from a referenced type is called ‘‘pointer type derivation’’. - C99 [6.2.5] ### 問題 :::warning :question: > 這是 C 語言只有 call-by-value 的實證,函式的傳遞都涉及到數值 why?? ::: ### 指標的指標 ```C=1 int B = 2; void func(int *p) { p = &B; } int main() { int A = 1, C = 3; int *ptrA = &A; func(ptrA); printf("%d\n", *ptrA); return 0; } ``` ``` 1 ``` 沒改到 A 的值,因為傳給 func 的參數是副本,lifetime 只在函式裡({ p = &B; }),ptrA 內含值其實是沒更動到的 ```clike=1 int B = 2; void func(int **p) { *p = &B; } int main() { int A = 1, C = 3; int *ptrA = &A; func(&ptrA); printf("%d\n", *ptrA); return 0; } ``` ``` 2 ``` 代入的 p 的 lifetime 是整個 main block ### Pointers vs. Arrays - x[4], *(x + 4), *(4 + x), 4[x] 都是指到同一個位址 - C 沒有真正的陣列/矩陣 - C 提供操作多維陣列的機制,但實際上在記憶體的呈現只有一維 > [未完] --- ## 函式呼叫篇 - nested function in C:可以在一個 function 的定義裡宣告另一個function,但他的定義要定義在其他的 function 外面 - C 的 function call 一般狀況是有去有回(特殊情況才會有去無回) - argument 參數 vs. parameter 參數 argument:型態 parameter:數值本身 - function return 的 value 放在暫存器,如果是 struct 就可能很大 => 在暫存器放 structure 的開頭位址 ### malloc / free - malloc() 要指定 size,會回傳指標。往往配置的空間會比預期的再大一點,因為會做 alignment - free() 不用指定 size。若該指標是 null 則 free 不會動作。 - free完某 pointer 後做 pointer = NULL 可以避免 doubly free - doubly free:compile 會過,但執行會出錯 - malloc_trim():強制去 free - free() 為效率考量可能不會馬上 free,但會導致系統可用空間變少 ### 問題 :::warning :question: > [color=#b2050e]1. 為什麼 glibc 可以偵測出上述程式的 “double free or corruption” 呢? > > ~~因為執行完 free() 後,ptr的值會改變,已經不是當初 malloc() 分配記憶體時 ptr 指到的記憶體的開頭?~~ > > 2. > :::warning > ```shell > $ gcc -o infinite infinite.c -g > $ gdb -q infinite > Reading symbols from infinite...Reading symbols from /Users/waiting/infinite.dSYM/Contents/Resources/DWARF/infinite...done. > done. > (gdb) r > Starting program: /Users/waiting/infinite > [New Thread 0x1803 of process 569] > During startup program terminated with signal ?, Unknown signal. > (gdb) list > 1 int func() { > 2 static int count = 0; > 3 return ++count && func(); > 4 } > 5 > 6 int main() { > 7 return func(); > 8 } > (gdb) p count > No symbol "count" in current context. > ``` > ::: > macOS 的訊息長得不太一樣oAO > Ubuntu 上 ok :::info 參照 [Diagnosing Memory Heap Corruption in glibc with MALLOC_CHECK_](https://www.novell.com/support/kb/doc.php?id=3113982) 設定好環境變數,再做實驗 :notes: jserv ::: --- ## 遞迴呼叫篇 ### iterate vs. recurse ### recursion 的類型 - tail recursion: - 直接遞迴 (direct recursion) - 線性遞迴 (linear recursion) - 函式執行的最後一件事只能是遞迴呼叫 ### [tail recursion 之重要性](https://notfalse.net/9/recursion#recursive-int-array-sum) - 遞迴的每次呼叫需要額外的記憶體 (系統堆疊 stack frame),來記錄每個遞迴的呼叫狀態,若遞迴呼叫過量使堆疊爆了,就會造成 Stack Overflow Error - 尾端呼叫消除 (Tail Call Elimination, TCE) / 尾端呼叫最佳化 (Tail Call Optimization, TCO) - 尾端遞迴,不增加新的系統堆疊,而是用每次執行的結果,直接更新函式內容 - 許多語言編譯器不支援尾端呼叫最佳化 (e.g. Java、Python) - 尾端遞迴其實只是把迭代的迴圈變化和區域變數,放在方法的參數中傳遞罷了 - 一個函式,理論上必定存在兩種形式解法: 遞迴與非遞迴。 - 而尾端遞迴則能無痛轉換為非遞迴 - clang / gcc 都有能力對 tail recursion 的程式做最佳化,中間的那些遞迴的呼叫事實上就不存在了 --- ## 前置處理器應用篇 --- ## linked list 和非連續記憶體操作 ---