# 你所不知道的 C 語言:指標篇 contributed by < [`ofAlpaca`](https://github.com/ofAlpaca) > ###### tags: `CSIE5006` `Note` ### C 語言規格書 * **Incomplete type** : 已宣告但未給予記憶體空間的 type ,因尚未有空間,故非 object type。 > C99[6.2.5] Types are partitioned into object types (types that fully describe objects), function types (types that describe functions), and incomplete types (types that describe objects but lack information needed to determine their sizes). * 型別可分為三, **object type** 、 **function type** 、 **incomplete type** 。 ```clike= struct Strt ; struct Strt x ; // incomplete type char y[]; // incomplete type ``` * Incomplete type 可以宣告為 pointer type 但不可存取其記憶體空間。 * pointer 的大小取決於架構的 address 。 * Array, function, pointer 都是 **derived declarator type** 。 --- ### `void *` 之謎 * `void *` (pointer to void) 無法確定其大小。 * 對指標進行加減運算([source](http://blog.jobbole.com/44845/)) ```clike= #include <stdio.h> int main() { int intvar = 1; int intarr[] = {1,2,3,4,5}; printf("address of intvar = %p\n", (void *)(&intvar)); printf("address of intvar - 1 = %p\n",(&intvar - 1)); printf("address of intvar + 1 = %p\n", (void *)(&intvar + 1)); printf("address of intarr[0] = %p\n", (void *)(&intarr)); printf("address of intarr[1] = %p\n", (void *)(&intarr + 1)); } ``` * 從結果可以得知,每次加減1的單位是以前面指標的型別來決定的。如果前是 int 則單位是 4 bytes,如果前面是 array of int ,則單位是 4 * 5 = 20 bytes,並非 4 bytes 。 ```clike= $ ./test address of intvar = 0x7fff2a78552c address of intvar - 1 = 0x7fff2a785528 address of intvar + 1 = 0x7fff2a785530 address of intarr[0] = 0x7fff2a785510 address of intarr[1] = 0x7fff2a785524 ``` * `void *` 與 `char *` 可以互換表示法。 * 存在這種用法 `*(int32_t * const) (0x67a9) = 0xaa6;` --- ### Pointer to a pointer * C語言只有**call-by-value** ! * `ptrA` 不會被改變到的原因是參數 `p` 只是個副本,而改變副本的值並不會影響到 `ptrA` 。 ```clike= 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); // print 1 return 0; } ``` * 改為使用 pointer-to-pointer 來達成目的。 ```clike= 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; } ``` * 如果不使用「指標的指標」來改變傳入的變數, `memcpy()` 也是個方法。 ```clike= int B = 2; void func(int * p) { memcpy(p,&B,sizeof(B)); } int main() { int A = 1, C = 3; int *ptrA = &A; func(ptrA); printf("%d\n", *ptrA); // print 2 return 0; } ``` * 詭異的是,如果將 `func(int **p)` 改為 `func(int *p)` 就會發生 core dump,但依照邏輯上來說是沒問題的。 ```clike= 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); // Core dump happens here return 0; } ``` * 後來可以從 gdb 中發現,這是由於 `*p` 的型別是 `int` ,所以在 assign 時,只會 assign 4 個 bytes,進而導致後面要 `*ptrA` 時產生問題。 * 只要傳入的參數是 `int **p` ,那 `*p` 也會是 pointer type。 ``` (gdb) p &B $5 = (int *) 0x555555755010 <B> (gdb) p ptrA $7 = (int *) 0x7fff55755010 ^^^^^^^^ 只有 4 bytes 被 assign ``` --- ### Pointers vs. Arrays 1. C語言中沒有真正陣列/矩陣,只有 array subscripting! 2. ```extern char x[]``` 無法轉為 pointer。 **註:extern意思是指此變數會在其他地方被定義** 3. ```char x[10]``` 不能轉為 pointer ,但 ```char x[]``` 可以,因為它不是 object 而是 incomplete type 。 4. ```func(char x[])```會被轉為```func(char *x)``` ,**但僅限於函式的參數, array argument 是不存在於C語言的**。 * 如果在 function 中使用 `sizeof(x)` 會造成 bug ,因為 `sizeof(x)` 只會回傳 4 bytes (as char *x)。 * 可以使用內建 macro `#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))`。 * `func(char x[][10])` == `func(char (*x)[10]`,所以 `x+1` 會一次增加 40 bytes (as 10 int)。 6. **In expression, array 與 pointer 可以互換。** ```clike= int a[3]; *a = 5; // a[0] = 5 *(a+1) = 5; // a[1] = 5 ``` 6. ```x[i]``` 會被編譯器改寫為 ```*(x + i)``` , ```x[i] == (*((x)+(i)))``` 。 7. `x[i]` 、 `*(x + i)` 、 `*(i + x)` 、 `i[x]` 這四者是等價的。 8. ```int x[i][j]``` 意思是 **x 是有 i 個 object 的陣列,每個 object 都是有 j 個 int 的陣列**。此作法稱為 array subscripting。 9. ```char * newargv[];``` 的結構如下,**提醒自己用**: ![](https://i.imgur.com/WNz1L60.png) * ```char* s[]``` 與 ```char **s``` 是相同的。 * 要注意的是 `char *s[]` 與 `char (*s)[]` 是不同的,前者是 an array of pointer to char ,後者是 a pointer to an array 。 (根據 C99 [A.2.1] , `[]` 是優先於 `*` ) 10. ```int *ptr, value;``` ptr 為 pointer to int , value 為 int ,此手法容易搞混,不宜使用。 11. 使用 ```ptr + 1``` 時,每次 ```+1``` 的單位取決於 ptr 的 type 。 **ex. pointer to int (4 bytes)、pointer to array of int(4 * i bytes)** 12. 可以透過 pointer 來改變存取記憶體的方式。 ``` double : 0x00000000 0x3ff00000 // double 長度為 8 bytes , 但 int 長度為 4 bytes。 (int *): 0x00000000 // 轉型後只能取到前 4 bytes ``` 13. 變數宣告的順序不等於記憶體中的順序。 14. ```char *r = malloc(strlen(s) + strlen(t) + 1); ```最後的+1為NULL的空間。 15. `memcpy()` 、 `malloc()` 並不會自動配上最後的 terminated null byte ,在處理 `char *` 時要記得自己保留空間。 16. `strdup()` 會使用 `malloc()` 配置足夠的記憶體空間來存放字串,也包含了最後的 terminated null byte ('\0')。 --- ### [Function pointer](http://blog.jobbole.com/44639/) * ``` *(uint32 * const) (0x601020) = 0x601080``` 左手邊是 **Lvalue**,指的是 locator value ,並非 left 。 * `E` 可以代表: * object: 儲存 address of object `a` * lvalue: E object 的 address 位置 ```clike= int a = 10; int *E = &a; ``` * **Lvalue** 是指除了 `void` 型別以外,有著 object type 或 incomplete type 的表達式。ex. obj , *ptr , ptr[i] , and ++x。 >C99 [6.3.2.1] An lvalue is an expression with an object type or an incomplete type other than void; * 有著回傳型別的 function designator 會被轉為 pointer to returning type 。 ex. `int test();` 會被轉為 pointer to function return int , 型別為 `int (*)()` 。就像陣列這兩者互換的關係,`char a[]` 與 `char * a`。 > C99 [6.3.2.1] A function designator with type ‘‘function returning type’’ is converted to an expression that has type ‘‘pointer to function returning type’’. * 若在 pointer to function 使用 deference operator(**\***) , 則會被轉為 function designator 。 * 此程式碼可以運作的原因是 `put()` 是個 function designator ,而 function designator 會被轉成 pointer to function ,在經過數次的 dereference 後仍然是 function designator,所以不影響結果。 ```clike int main() { return (********puts)("Hello"); } ``` * `char *s="Hello world"` 此為string literal,和 `char s[]="Hello world"` 不同,前者存於read-only data section ,後者則是 stack 的 local variable。 * func1() 中 p 指向的 Hellow 是存於 static storage 裡,故 return 後 Hellow 還在 static storage 中;但是 func2() 中的 p 指向的 Hellow 是存於 stack 裡, return 後的 Hellow 已經不在 stack 中了。 ```clike= char* func1(){ char *p = "Hellow"; return p; } char* func2(){ char p[] = "Hellow"; return p; } int main() { printf("%s\n",func1()); // print Hellow printf("%s\n",func2()); // NULL return 0; } ``` * `strlen()` 不能接受 null pointer ,會造成未定義的行為。 * **Compound Literal** initialization : ```clike= struct S {int a, b;}; struct S x = {1,2}; struct S y = {2,1}; int i = ++(int){1}; // i is initialized as 2 ``` --- ### Null pointer * NULL 是使用 MACRO 定義的空指標常數( null pointer constant ): `#define NULL ((void *)0) // 一般C編譯器常見的寫法` ```clike= #define **offsetof**(st, m) **((size_t)&(((st *)0)->m))** #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) ``` * **offset** : st 代表的是 struct type ,`((st *)0)->m` 意思是將 0 轉型為 pointer to st ,並指向 m 這個 member 的位址。在此獲得**相對位置**。 * **container_of** : mptr 是 member 的 pointer to type ,並 assign 為 ptr 。記憶體中的**絕對位址 - 相對位址**,可以得到原 struct 的起始位址。 * **typeof** : 和 `sizeof` 一樣都是 operator ,用於回傳資料的型別,但 `typeof` 是 gcc 的 extension。