Try   HackMD

為何一個指標實作 add_entry() 會出事

(上) 前情提要

  • 我發現我的程式會崩潰,甚至連第一個 entry 都加不進去,以下是我將 pointer to pointer 改寫為指標的程式片段 :
void add_entry(node_t *h, int new_value) { /*create new_node*/ //printf(Address of h = %p, &h) if(h == NULL) h = new_node; else { node_t *tmp = h; while (tmp) { tmp = tmp->next; } tmp = new_node; } } ... int main() { node_t *head = NULL; add_entry(head, 7); //printf(address of head = %p, &head); add_entry(head, 13); ... return 0; }
  • 我故意將原本老師函式中的 head 改成 h,為了強調與主程式中的 head 是不同的變數。

(下) 釐清問題的本質!

1. 函式的傳值 (pass by value) 與傳址 (pass by reference)

  • 一個變數其實有兩個屬性 : 位址,若透過傳值方式傳遞變數,則僅僅只會把變數的複製一份到函式中給另一個變數,不論函式裡面做了甚麼事,都不會影響原本的變數,如下程式碼:
​​void add(int a) ​​{ ​​ a++; ​​} ​​ ​​int main() ​​{ ​​ int a = 0; ​​ add(a); ​​ printf("%d\n", a); // still 0, right? ​​}
  • 那想在函式裡面能夠改變原本的變數,則透過傳址的方式,函式會透過指標來接住原本變數的位址,如此一來這個指標所指的內容(也就是值)便是真真正正原本變數的,而我就可以在函式裡透過取值運算(也就是 * 符號,和宣告變數的 * 功能不同!)來更改原變數的值 :
​​void add(int *a) ​​{ ​​ (*a)++; ​​} ​​ ​​int main() ​​{ ​​ int a = 0; ​​ add(&a); ​​ printf("%d\n", a); // will be 1, yes! ​​}

2. 儘管函式裡面把 h 指向 new_node,但我的 head 仍然指向最一開始的 NULL

我一個重大的發現是我誤以為上面 add_entry 程式碼中第 5 行,我把h = new_node 之後就以為我的 head 會更新,但實際上並不會!
雖然我已經是傳指標給函式了,看似是傳位址過去了,怎麼還是沒有更新呢?因為我其實把一些簡單的概念搞混了


圖的上區塊是還沒 h = new_node,可以看到 headh 其實是兩個不同的變數(可以觀察程式碼第 4 及第 20 行的註解拿掉後的輸出),他們指向相同位址的確是用傳址的方式,也因此我確實可以更改所指的內容,但 h = new_node 不是改內容,是改 h 指的位址,改 h 指的位址,改 h 指的位址!!!不是改它們共同指到的位址的內容
因此這樣做根本就沒有修改到我的 head 對吧!(搭配圖便有更深理解),所以才衍伸到我必須把 h 丟出來給 head 才會對的結果,就只是先給 h 再給 head 而已罷了!

3. 為何需要 pointer to pointer

  • 到這裡就可以解釋為甚麼需要用到 pointer to pointer,上面的問題是 headh 所指內容不同步所導致。indirect 是一個指標但它的內容(值)還是一個指標(也就是 indirect 指向 head 這個指標):指標 h 則是跟 head 指同樣位址而已。
  • 也就是說我能夠用 indirect 追蹤 head 指到哪裡(對 indirect 取值便能得到 head 的位址,儘管 head 指向不同位址)