--- tags: sysprog --- # [你所不知道的 C 語言 : 指標篇](https://hackmd.io/@sysprog/HyBPr9WGl?type=view) ### Pointer to Pointer Pointer to pointer 有一個很重要的應用 - 更改變數的 life time : 因為C裡面的函式都是 ==call by value==,可以說在 function 裡面操作的 parameter 都是原始 object 的「複本」,出了function後原本object的value還是不會改變。 因此很多時候我們必須寫`func(&var)`傳入 object 的地址,讓 function 內部可以更改原始 object 的內容。 舉例來說,如果是一個linked list要刪除head,有兩種做法 ```clike list = list_del_head(list); ``` ```clike list_del_head(&list); ``` 但是第一種作法很麻煩,因為你需要每次都去更新你的linked list的指標。 而第二種做法就可以很好的==實現ADT(Abstract Data Type)== - 你可以不用管function裡面的實作細節,你只需要知道「list變數中紀錄的head被刪除了」這個結果。 ### 解讀指標 1. 「*」 = value of / dereference(提領) 2. ```clike (*(void(*)())0)(); ``` 等效於 ```clike typedef void (*funcPtr)(); ( * (funcPtr) 0 ) (); ``` 呼叫一個記憶體地址為0x0的function。 注意,雖然這段程式編譯得過,但是有記憶體保護所以真去執行會出現Segmentation fault。 3. ```clike void **(*d) (int &, char **(*)(char *, char **)); ``` 以英文解釋 : - d is a pointer to function that take two parameters: - a reference to an int - a pointer to function that take two parmeters: - a pointer to a char - a pointer to a pointer to a char - and return a pointer to a pointer to a char - and return a pointer to a pointer to a void 4. [[source](https://stackoverflow.com/questions/1591361/understanding-typedefs-for-function-pointers-in-c/1591492#1591492)] ```clike void ( *signal(int sig, void (*handler)(int)) ) (int); ``` 逐步拆解 : ``` signal -- signal signal( ) -- is a function signal( sig ) -- with a parameter named sig signal(int sig, ) -- of type int signal(int sig, handler ) -- and a parameter named handler signal(int sig, *handler ) -- which is a pointer signal(int sig, (*handler)( )) ) -- to a function signal(int sig, (*handler)(int)) ) -- taking an int parameter signal(int sig, void (*handler)(int)) ) -- and returning void *signal(int sig, void (*handler)(int)) ) -- returning a pointer ( *signal(int sig, void (*handler)(int)) )( ) -- to a function ( *signal(int sig, void (*handler)(int)) )(int) -- taking an int parameter void ( *signal(int sig, void (*handler)(int)) )(int); -- and returning void ``` 5. 設定絕對地址為 0x67a9 的 32-bit 整數變數的值為 0xaa6,該如何寫? ```clike *(int32_t * const) (0x67a9) = 0xaa6; ↑ ↑ | 強制轉型成const int32_t的指標,其值為0x67a9 提領(dereference)出0x67a9上所存的值(32-bits),並改成0xaa6 ``` 9. C裡面沒有真正的陣列 (`[]`是語法糖) : ``` x[4] == *(x+4) == *(4+x) == 4[x] ``` 9. 二維array subscripting最終在記憶體裡面還是線性地址 10. +1 遷移的單位,取決於前一個operand被derefernce之後的型態 ``` (gdb) whatis b type = struct {...} [17] ``` 11. GDB的之所以強大 : 他可以變更記憶體內容、改變程式執行流程 - `p`雖然是`print`,但實際上可以拿來做操作,改變記憶體內容,如 : `p (&b[0])->v = {1, 2, 3}` - 純量 -> 地址 : `p (void*) 6296224 => (void*) 0x6012a0` - 由地址取值 : `*(double*) 0x6012a0` 12. 在C99 `char *s[strlen(t)+strlen(r)]`是合法的 ( VLA, variable-length array )。 另外`char *s = malloc(strlen(t)+strlen(r))`當然可以,但是當`t`、`r`都是\0,則malloc會回傳NULL ( 當malloc出錯 或者 `malloc(0)`的時候會回傳 NULL ),所以通常會這樣寫 ```clikelike char *s = malloc(strlen(t)+strlen(r) + 1); if(!s) exit(1); ``` 12. argv裡面不只有程式參數,還有環境變數 ``` (gdb) x/4s ((char **) argv)[0] 0x7fffffffe7c9: "/tmp/x" 0x7fffffffe7d0: "LC_PAPER=zh_TW" 0x7fffffffe7df: "XDG_SESSION_ID=91" 0x7fffffffe7f1: "LC_ADDRESS=zh_TW" ``` argc、argv其實是 shell command interpreter 幫你給的,使用`./a.out hello world`其實就是在呼叫 interpreter 13. Function Pointer : 對於function,不管你是對他做reference / dereference,他的回傳值都是一個==function designator==,一個 Lvalue。這本質上是C語言為了避免你犯錯而做的一個限制規範 (少見C語言不允許你做的事),這也說明了為什麼 C 的 qsort() 有一個參數需要你輸入function pointer,但在調用時卻不用加上`&`,比如 : ```clike void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*)) // qsort聲明 int cmp_int(const void*, const void*); qsort(intList, intSize, sizeof(int), cmp_int); // 照理應是&cmp_int ``` 14. C-style string是很糟糕的設計 : C裡面有 int 、有 char ... 但就是沒有一個 string 型態,當你`strcpy(s, t)`時,如果`t`沒有deliminator (`\0`),那它有可能一直無窮找下去,找到天荒地老。 15. ==parameter==和==argument==中文都翻譯「參數」。差別在於parameter代表一個明確的數值,如 : 一個int 5、一個struct MYSQL結構 ; 相較下argument就是比較抽象的概念,「他會有一些數值,但不知道他是什麼型態/數值」 ### Function designator v.s Function pointer 首先看看規格書所說 : - C99 [6.3.2.1] >A function designator is an expression that has function type. Except when it is the operand of the sizeof operator or the unary & operator, a **function designator** with type "function returning type" **is converted to** an expression that has type "pointer to function returning type". - C99 [6.5.3.2] >The unary * operator denotes indirection. **If the operand points to a function, the result is a function designator** - C99 [6.5.3.4] >**The sizeof operator shall not be applied to an expression that has function type** or an incomplete type, to the parenthesized name of such a type, or to an expression that designates a bit-field member. 根據上述三個規則,我們可以推論出以下幾點規則 : 1. 因為 function pointer 和 function designator 都是儲存某個 function 的記憶體位址 ( points to a function ),所以根據[6.5.3.2]我們可以知道 : - `*fptr`,dereference of function pointer,結果會是 function designator - `*funcName`,dereference of function designator,結果會是 function designator 2. 根據[6.3.2.1],在對 function designator 做任何操作以前,都會先「自動」轉換成 function pointer,除非是遇到 **sizeof** 或 **&** : - 遇到 & : function designator 會「明確」轉換成 function pointer ( 根據[6.5.3.2],`*fptr == funcName`,換句話說,`&funcName == fptr` (function pointer) ) - 遇到 sizeof : 根據[6.5.3.4],`sizeof(funcName)`的操作是不被允許的。 所以可以說,除非遇到 sizeof (非法操作),function designator 都會轉換成 function pointer 我們可以用以下程式實驗看看 : ```clike void (*fptr)(); void funcName(){ ; } int main(){ fptr = funcName; } ``` 以 gdb 測試其數值,可得以下操作結果 : ```gdb (gdb) print funcName $1 = {void ()} 0x4004cd <funcName> # function designator (gdb) print *funcName $2 = {void ()} 0x4004cd <funcName> # function designator (gdb) print fptr $3 = (void (*)()) 0x4004cd <funcName> # function pointer (gdb) print *fptr $4 = {void ()} 0x4004cd <funcName> # function designator (gdb) print **funcName $5 = {void ()} 0x4004cd <funcName> # function designator (gdb) print **fptr $6 = {void ()} 0x4004cd <funcName> # function designator (gdb) print &funcName $7 = (void (*)()) 0x4004cd <funcName> # function pointer (gdb) print &fptr $8 = (void (**)()) 0x601030 <fptr> # address of function pointer (gdb) print sizeof(funcName) $9 = 1 # illegal operation (gdb) print sizeof(&funcName) $10 = 8 # size of function pointer (gdb) print sizeof(fptr) $11 = 8 # size of function pointer ``` :::info 疑問 : 影片中有提到,不管是對 function designator 做 reference / dereference 它的回傳值都是一個 function designator。 <br> 但真是這樣嗎? 就我對規格書的理解 reference of function designator 會是 function pointer。而且上述實驗中 `print funcName` 和 `print &funcName`的結果不一樣,也可以印證我的想法。 ::: :::warning 上面的實驗已經預先轉型 (casting),所以在 C 編譯器傳入 printf 的型態已有出入,自然會得到不同的地址。 注意 function designator 在意的不是「地址」或「指標」的純量,而是「使用」的方式 :notes: jserv ::: :::info 疑問 : 在[6.3.2.1]中有一段話,「a function designator with type "function returning type" is converted to a pointer to function returning type」,其中「function returing type」是什麼意思? 從各個文章中的說法來看,`a pointer to function returing type` 應該就是 `a pointer to function` <br> 但如果是這樣,那規格書中又為什麼要多加一個 "returning type" 描述 ? 它跟 function 的回傳值型態有什麼關聯嗎? ::: :::info 疑問: 到目前為止都是在背誦 function pointer 和 function designator 的各種操作規則,但說到底它為什麼設立這樣的操作規定 ? <br> 它甚至告訴你 : 在對 function designator 做除 sizeof 以外的任何操作以前,它都會變成 function pointer。那這個 function designator 到底是設計來做什麼的? 好樣有 function pointer 也就夠了,為什麼要把 function designator 和 function pointer 兩個概念區分開來? ::: :::warning 考慮到 pointer to pointer to object (通用狀況),若我們要確保能夠 dereference (即 `*`) 要能運作,勢必 "pointer to pointer to object" 和 "pointer to object" 這兩個數值 (地址的純量 [sclar]) 會不同,但 function designator 的概念則想確保 `puts("X")`, `(*puts)("X")`, `(*(*puts))("X")` 這幾個敘述都有一致的行為。 如果沒有規範 function designator,那 function pointer 在產生組合語言時,只是直接對應到 `jmp *addr` 的話,`*addr` 和 `**addr` 就會是不同的地址,這當然就會致使最終執行時期的落差。 :notes: jserv ::: #### 參考資料 1. [c語法問題-結構成員指到函式記憶體位址](http://www.programmer-club.com.tw/ShowSameTitleN/c/39038.html) 2. [Lvalues and rvalues](https://www.ibm.com/support/knowledgecenter/SSGH2K_13.1.0/com.ibm.xlc131.aix.doc/language_ref/lvalue.html)