# C語言 指標
此為觀看Jserv 你所不知道的C語言: 指標篇 的筆記
## 前段
* Object != Object-oriented
在C語言規格書中,物件就是在執行期間,資料儲存空間不為0的東西,就叫做物件
* Lifetime: 生命週期,在c語言的規範下,在object的生命週期內,代表一定有對應的**常數記憶體位址**
* 學習C語言,學的是一種思維跟文化
* Undefined bahavior,在C語言規格書中會發現這東西
The lifetime of an object is the portion of program execution during which storage is guaranteed to be reserved for it. An object exists, has a constant address and retains its last-stored value throughout its lifetime. If an object is referred to outside of its lifetime, the behavior is undefined.
* Pointer是一個reference type,是一個incomplete type(儲存空間不知道),它不是一個物件
可以宣告incomplete type,但不能用它來建立物件
但是卻可以建立pointer type
* pointer type不具有完整的算數,可以加減,但不能乘除
* a pointer to void shall have the same representation and alignment requirements as a pointer to a character type
C當中的qsort,第一個參數是void \*,這代表它可以被換成char \*
pointer to void 和 pointer to character可互換
* void在早期c語言當中不存在,如果函式不宣告型態,會自己變成int
:::info
```c=
*a[] //an array of pointers
(*a)[] //a pointer to an array
*f() //a function returning a pointer
(*f)() //a pointer to a function
void ( *signal(int sig, void (*handler)(int)) ) (int);
```
* signal is a pointer to a function, it has two parameters
* an integer, sig
* a pointer to a function, handler
:::
* 考慮以下程式
```c=
int main(){
char *x=0;
void *y=0;
char c = *x;
char d = *y;
}
```
以上程式碼編譯會錯誤,但錯在哪個? 錯在第5行,因為y是a pointer to void,不能dereference,若更改成以下程式則會通過編譯
```c=
int main(){
char *x=0;
void *y=0;
char c = *x;
char d = * (char *)y;
}
```
其實如果寫成下面這樣也可以
```c=
int main(){
char *x=0;
void *y=0;
char c = *x;
char d = * (double *)y;
}
```
但是double跟char的儲存空間大小不同,這時候就要用到alignment
* void \* 會招致一堆undefined bahavior(UB)
* 指標的用途是在傳遞大量資訊的時候不用全部複製一份過去,只要簡單傳送指向那個東西的指標
* c裡面沒有真正的陣列/矩陣
```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]);
//4 4 4 4
}
```
* 4[x]照樣可以得到x[4], array subscripting,因為C語言當中,x[4]相當於*((x)+(i)) = \*((i)+(x)) = 4[x]
* 有些情況array不能改成pointer type
* in declaration
* extern, `extern char x[]`
* definition, `char x[10]`
* parameter of function, `func(char x[])` 可以改變成`func(char *x)`
規格書有寫到as formal parameters in a function definition,
char s[] and `char *x` are equivalent
* array subscripting在編譯時期只知道兩件事
* 得知size
* obtain a pointer to element 0
:::warning
C語言永遠只有call-by-value
sizeof()在c語言是operator不是function
:::
透過gdb解析以下程式
```c=
int a[3];
struct { double v[3]; double length; } b[17];
int calendar[12][31];
int main(){
return 0;
}
```
編譯程式
```shell=
# g是debugger看得懂的除錯訊息,Og是對debugger做最佳化
$ gcc -o ptr.out -Og -g ptr.c
# 執行他
$ gdb -q ptr.out
```
接著做以下分析
```shell=
(gdb) p sizeof(calendar)
$1 = 1488
(gdb) print 1488 / 12 / 31
$2 = 4
(gdb) p sizeof(int)
$3 = 4
```
發現calendar中每個元素的大小和int相同,是一致的
```shell=
(gdb) p sizeof(b)
$4 = 544
(gdb) p 544 / 17 / 4
$5 = 8
(gdb) p sizeof(double)
$6 = 8
# 驗證struct b當中元素都是double
(gdb) whatis b[0]
type = struct {...}
(gdb) whatis calendar
type = int [12][31]
(gdb) whatis calendar[0]
type = int [31]
(gdb) whatis &b[0]
type = struct {...} *
(gdb) whatis &calendar
type = int (*)[12][31]
(gdb) whatis &calendar[0]
type = int (*)[31]
# 把b印出來看看
(gdb) x/4 b
0x4040 <b>: 0 0 0 0
# 接著印b的地址
(gdb) p &b
$7 = (struct {...} (*)[17]) 0x4040 <b>
(gdb) p &b[0]
$8 = (struct {...} *) 0x4040 <b>
# b和b[0]的地址一樣 太神拉
# 接著把b的內容印出來,目前全部為0
(gdb) p b
$9 = {{v = {0, 0, 0}, length = 0} <repeats 17 times>}
```
接著做個有趣的測試
```shell=
(gdb) p &b
$10 = (struct {...} (*)[17]) 0x4040 <b>
(gdb) p &b + 1
$11 = (struct {...} (*)[17]) 0x4260 <a>
```
加1怎麼不是加1上去0x404呢?
這一次加了$2*16*16+2*16 = 544$
這不就是這個struct的大小嗎? 不信再印一次
```
(gdb) p sizeof(b)
$13 = 544
```
那如果像以下呢?
```
(gdb) p &b
$15 = (struct {...} (*)[17]) 0x4040 <b>
(gdb) p &(b[0]) + 1
$16 = (struct {...} *) 0x4060 <b+32>
(gdb) p 2 * 16
$17 = 32
```
這時候加一就變成加32而已
* +1 到底加多少,取決於它前面那個東西到底多大
* p不只能print,還可以變更記憶體內容,這裡jserv用以下指令可以work,但我的vm會跳以下錯誤,還不知道為甚麼
```
(gdb) p (&b[0])->v = {1,2,3}
Cannot access memory at address 0x4040
```
我改用set variable,還是不行
* 最後居然是因為我沒有下r這個指令讓gdb開始執行,沒有執行居然差這麼多
比對一下執行前後的記憶體內容還有記憶體位址,驚人的發現
```shell=
# 執行之前
(gdb) p b
$1 = {{v = {0, 0, 0}, length = 0} <repeats 17 times>}
(gdb) p &b
$2 = (struct {...} (*)[17]) 0x4040 <b>
(gdb) p (&b[0])
$3 = (struct {...} *) 0x4040 <b>
(gdb) p (&b[0])->v
$4 = {0, 0, 0}
(gdb) p (&b[0])->v = {1,2,3}
Cannot access memory at address 0x4040
# 執行之後
(gdb) r
Starting program: /home/sduser/linux-internals/ptr.out
Breakpoint 1, main () at ptr.c:10
10 int main(){
(gdb) p b
$5 = {{v = {0, 0, 0}, length = 0} <repeats 17 times>}
(gdb) p &b
$6 = (struct {...} (*)[17]) 0x555555558040 <b>
(gdb) p (&b[0])
$7 = (struct {...} *) 0x555555558040 <b>
(gdb) p (&b[0])->v
$8 = {0, 0, 0}
(gdb) p (&b[0])->v = {1,2,3}
$9 = {1, 2, 3}
```
* 連記憶體位址都差好多,c語言執行後才會真正分配記憶體給variable的原因嗎?
那執行之前讀取到的是甚麼?
* 接著進行一些轉型的實驗
```shell=
(gdb) p &(&b[0])->v[0]
$10 = (double *) 0x555555558040 <b>
(gdb) p (int *) &(&b[0])->v[0]
$11 = (int *) 0x555555558040 <b>
(gdb) p *(int *) &(&b[0])->v[0]
$12 = 0
(gdb) p *(double *) &(&b[0])->v[0]
$13 = 1
```
* 轉為int或double居然會造成0跟1.0的差別
答案在jserv的共筆有解釋
* Null 是具備pointer type的0
* argc, argv也會把env variable(envp)帶進去
## Function pointer
:::info
**Function designator**
A function designator is an expression with 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".
when you type func(a,b); to call a function, func is the function designator.
:::
```clike=
int main() {
return (********puts)("Hello");
}
```
* 可以發現不管怎麼改變\*的數量,都能成功編譯並執行
* function type, function designator
* Lvalue, L不是left,是locator
* 對function type不管做dereference,reference等等,都是得到function designator
* `*(puts)` 本身為一個function designator
`*(*(puts))` 解讀為將`*(puts)`再做一次dereference,結果還是一個function designator,所以不管加幾個`*`最後還是function designator
接著編譯以下程式碼(這裡我有改過 用jserv原本列的那三行編不過)
```clike=
#include <stdio.h>
void test(){};
void (*fptr)() = &test;
int main() {
fptr();
return 0;
}
```
這時候用gdb去觀察他們
```
(gdb) p test
$1 = {void ()} 0x555555555129 <test>
(gdb) whatis test
type = void ()
(gdb) p fptr
$2 = (void (*)()) 0x555555555129 <test>
(gdb) whatis fptr
type = void (*)()
(gdb) p *fptr
$3 = {void ()} 0x555555555129 <test>
(gdb) p **fptr
$4 = {void ()} 0x555555555129 <test>
(gdb) p *****fptr
$5 = {void ()} 0x555555555129 <test>
```
可以發現很神奇的事情
test是function designator,型態是void()
fptr是a pointer to a function,內容可以發現和test是一樣的
這時候加上\*,發現不管加上幾個它都還是function designator
接著再做以下有趣的實驗
這我自己做的
```
(gdb) p &fptr
$6 = (void (**)()) 0x555555558010 <fptr>
(gdb) p &test
$7 = (void (*)()) 0x555555555129 <test>
(gdb) p &(*fptr)
$8 = (void (*)()) 0x555555555129 <test>
(gdb) p &(*******fptr)
$9 = (void (*)()) 0x555555555129 <test>
```
首先&fptr跟&test不同,合理
再來可以發現&(\****** fptr),不管幾個\*,結果都一樣
## 字串
* c語言的字串其實不保證連續儲存空間,結尾字符\0,搞不好是從一個莫名其妙的地方讀到的
這造成c語言字串非常複雜,也時常不安全
考慮strcpy, memcpy, strncpy等functions
* 你能保證destination的大小可以容納source嗎?
就算做檢查,你也不能保證範圍的檢查沒有被compiler影響而使實際讀取到的記憶體不是錯的
* strdup
* `char *p = "hello world"` 和 `char p[] = "hello world"` 非常不同
string literals必須放在static storage裡面
p[]表示要把資料放在stack裡面
char \*p 則是指向一個static storage的指標
* c語言函式庫不會做太多null pointer的檢查,讓開發者自己檢查,如果它做太多檢查對程式的效能會造成很大的影響,太多branch