---
# System prepended metadata

title: C 語言：指標
tags: [NCKU Linux Kernel Internals, ' C語言']

---

---
tags: NCKU Linux Kernel Internals, C語言
---

# C 語言：指標

[你所不知道的C語言：指標篇](https://hackmd.io/@sysprog/c-pointer?type=view)

## ~~指標頭腦體操~~
嘗試解讀
### Q1
```c=
void **(*d) (int &, char **(*)(char *, char **));
```
function pointer d，回傳指向 void 指標的指標，兩個參數(parameters)為: 
* 一個 reference to int
*  function pointer，回傳指向 char 指標的指標，兩個參數為:
    *  char 指標
    *  指向 char 指標的指標
    
### Q2
```c=
void ( *signal(int sig, void (*handler)(int)) ) (int);
```
signal函式會回傳一個function pointer(void * )，其中 signal 的兩個參數為:
* 名為 sig 的 int
* 名為 handler 的 funtion pointer，一個參數為
    * int value

signal 所回傳的 function pointer，一個參數為
* int value

## C 語言規格書中的 object 
[ISO/IEC 9899:201x](http://www.open-std.org/jtc1/sc22/WG14/www/docs/n1570.pdf)


### 3.15 object
>region of data storage in the execution environment, the contents of which can represent values
* 並不是只有物件導向程式才有 object
* C語言中，在執行時期，只要有儲存區域的，都可以被稱為是 object
* object 與 pointer 其實是一體兩面的概念

### 6.2.4 Storage durations of objects
> The lifetime of an object is the portion of program execution during which storage is guaranteed to be reserved for it. 
* object 都有各自的生命週期(lifetime)。object 在自己的生命週期中擁有儲存空間。
> An object exists, has a constant address, and retains its last-stored value throughout its lifetime. 
* 在 object 的生命週期以內，意味著有對應的記憶體位址。
> If an object is referred to outside of its lifetime, the behavior is undefined. The value of a pointer becomes indeterminate when the object it points to (or just past) reaches the end of its lifetime
* 如果 object 在自己的生命週期外被操作，其行為是 undefined 的。

### 6.2.5 Types
> A pointer type may be derived from a function type or an object type, called the referenced type.

> Arithmetic types and pointer types are collectively called scalar types. Array and structure types are collectively called aggregate types.
* 我們可以對指標(Scalar Type) 做如 `++` `--` `+=` `-=` 的操作。對 array 或者 struct(Aggregate Type) 則不行。

> Array, function, and pointer types are collectively called derived declarator types. 

> A declarator type derivation from a type T is the construction of a derived declarator type from T by the application of an array-type, a function-type, or a pointer-type derivation to T.
* array、function，以及 pointer 都稱為 **derived declarator types**
## Pointer to pointer
以下的程式印出結果為?
```c=
int B = 2;
void func(int *p) { p = &B; }
int main() {
    int A = 1;
    int *ptrA = &A;
    func(ptrA);
    printf("%d\n", *ptrA);
    return 0;
}
```
Ans: 1

這是因為 **C 語言只有 call by value**，傳入的指標是一個副本。如果我們想要讓 ptrA 透過 func 指到 B，需把程式改寫成:
```c=
int B = 2;
void func(int **p) { *p = B; }
int main() {
    int A = 1;
    int *ptrA = &A;
    func(&ptrA);
    printf("%d\n", *ptrA);
    return 0;
}
```
:::info
:bell: 可以直接參考老師的講義，有更清楚的圖解。
:::

## Pointers vs. Arrays

* Pointer 跟 array 關係緊密，在某些情形下可以通用，但有時候無法互相轉換
    * 在表達式(expression)中互相通用
    * 在宣告(declaration)下:
        * `extern` 不通用, 如 `extern char x[];` ，不能變更為 pointer 的形式
        * definition/statement, 如 `char x[10]` ，不能變更為 pointer 的形式
        * parameter of function, 如 `func(char x[])`，可變更為 pointer 的形式 `func(char *x)`
        
* array subscripting ( `[]` )並不是真正意義上的陣列/矩陣，它只是用來告訴編譯器**一連串連續空間的大小**，以及**第一項元素的指標**。其他的操作，都是透過指標去達成的。

```c=
int a[3];
int *p;
p = a; /* p 指向 a[0] */
*p = 5; /* 等價於 a[0] = 5 */
*(p+1) = 5; /* 等價於 a[1] = 5 */
```
* 以上述程式為例，指標可以指向 array 的一個元素，然後透過指標來操作 array 上對應的位置
* 但是要注意到 `sizeof(a)` 得到的是 `sizeof(int) * 3`，`sizeof(p)` 得到的是 `sizeof(int*) `
```c=
#include <stdio.h>
void foo(int p[]){
    printf("%ld",sizeof(p));
}

int main()
{
    int a[4];
    foo(a);
    return 0;
}
```
* `void function(char a[])` 與 `void function(char * const a)` 是等價的，而且其真實型態其實是指標! 所以上面的程式會印出的值是 `sizeof(int *)` 而不是 `sizeof(int) * 4`! (警告訊息也會反應出來)
* 取值時，array 的行為與 pointer 幾乎雷同，但 array 會是用兩步取值，而 pointer 是三步。（array 的位址本身加上 offset，共兩步，而使用 pointer時，cpu 需先載入 pointer 位址，再用 pointer 的值當作位址並加上 offset 取值）

## Arrays and function designators
```c=
int main() { return (********puts)("Hello"); }
```
是合法且可運作的函式

C 語言規格書中的 **6.3.2.1 Lvalues, arrays, and function designators** 提到:
> A function designator is an expression that has function type. Except when it is the operand of the **sizeof** operator, the **_Alignof** 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"

* 也就是說，除了 sizeof 或取位址的 & 等，一個型別為 function returning type 的 function designator 在表達式(expression)中會自動轉換為 pointer to function returning type。
* 無論是一個 `*` 或是幾個 `*` ，都是同等意思的程式碼。

> Except when it is the operand of the sizeof operator, the _Alignof operator, or the unary & operator, or is a string literal used to initialize an array, an expression that has type "array of type" is converted to an expression with type "pointer to type", that points to the initial element of the array object and is not an lvalue. 

>If the array object has register storage class, the behavior is undefined
* 類似於 function，除了 sizeof 或取位址的 & 等，一個型別為 array of type 的 array 在表達式(expression)中會自動轉換為 pointer to type。

因此
```c=
return(*puts)("Hello"); 
``` 
和 
``` c=
return(*&puts)("Hello"); 
```
意思相同，前者`puts`因規格所說會先轉成 pointer，再透過 `*` 轉成 function designator;後者 `&puts` 代表 pointer 再透過 `*` 轉成 function designator
## 字串
* `char *p = "hello world"`和 `char p[] = "hello world"` 乍看之下是相同的初始化字串，但背後的行為卻大相逕庭。
* `char *p = "hello world"` 使 p 會是指向 static storage 的一個指標。因此當開發者嘗試修改 string literals 的內容，將會造成 undefined behavior。
```c=
/* Undefined behavior example*/
#include <stdio.h>
int main(){
        char *p = "Hello"; 
        *p = 'K'; 
        printf("%s \n",p);
        return 0;
}
```
```c=
/* This one is OK */
#include <stdio.h>
int main(){
        char p[] = "Hello"; 
        *p = 'K'; 
        printf("%s \n",p);
        return 0;
}
```
* 依據 C99 規範，string literals 必須放在 “static storage” 中，而 `char p[]` 的語意則表示要把資料分配在 stack 內，所以這會造成編譯器 (gcc) 隱性地 (implicitly) 產生額外的程式碼，使得 C 程式在執行時期可把 string literals 從 static storage 複製到 stack 中。雖然字串本身並非存放於 stack 內，但 `char p[]` 卻是分配在 stack 內，這也**造成 return p 是 undefined behavior**。
```c=
#include <stdio.h>
/* Undefined behavior example*/
char* foo(){
        char p[] = "Hello";
        return p;
}
int main(){
        printf("%s \n",foo());
        return 0;
}
```
```c=
/* This one is OK */
#include <stdio.h>

char* foo(){
        char *p = "Hello";
        return p;
}
int main(){
        printf("%s \n",foo());
        return 0;
}
```


