工程101:理解指標

工程101:理解指標

如果你從 Python 或 JavaScript 開始寫程式,指標聽起來像是白鬍子系統工程師才懂的祕密魔法。如果你在大學學過 C++,你記得和 segmentation fault 還有記憶體洩漏搏鬥的日子。

秘密是這樣。指標只是個變數。它的值是一個數字。那個數字存著某個東西的位址。

要理解指標,得先知道你的資料住在哪裡。

兩個領域:堆疊與堆積

程式執行時,資料總得有個去處。它會落在堆疊(stack)或堆積(heap)其中一方。

堆疊:又快又有條理

把堆疊想成桌上的一疊便利貼。

如果有個變數 int a = 10;,它通常會放在這裡。又快又安全。

堆積:西部荒野

堆積是一座巨大的倉庫。

所以,指標是什麼?

指標就是一個路標。

如果 score = 99 這個變數在堆積上(位址 0x1234),指標就是堆疊上一個小小的變數,上面寫著「資料在 0x1234」。

int score = 99;  // 實際的值
int *p = &score; // 指標 'p' 持有 'score' 的位址

p 是位址。它告訴你資料在哪裡。*p 是值,也就是那個位址裡存的東西。

為什麼要這樣折磨自己?

為什麼不直接把值傳過去,讓語言收拾善後就好?

1. 速度(不用複製)

想像一張 10MB 的圖片。你把它傳給 process(image) 函式,而你的語言把它複製一份,你就白白浪費了 10MB 的 RAM 和複製像素的 CPU 時間。用指標的話,你只傳位址(8 bytes)。函式知道圖片在哪裡,自己去看。

2. 共享狀態

你把變數用值傳給函式,函式收到的是副本。改副本不會動到原版。用指標的話,函式知道原版住在哪裡,就能修改真正的資料。

3. 動態結構

鏈結串列、樹、圖,都是一塊資料握著指向其他資料的指標。沒有參照,很難做出這些結構。

要付出的代價

指標不是免費的。

  1. 記憶體開銷: 指標本身就要佔空間(64 位元系統上通常是 8 bytes)。如果你有一個指向小整數的指標陣列,花在指標上的記憶體可能比資料本身還多。
  2. 效能損失: 「解參考」(沿著指標找到資料)要花一個 CPU cycle。也可能造成「快取失誤」。如果資料離得很遠,CPU 得等它被取出來。堆疊對快取很友善。鏈結串列這種指標很多的結構,通常不是。
  3. 腦力負擔: Null pointer exception。Dangling pointer。記憶體洩漏。手動管理指標需要紀律。這就是為什麼現代的語言(Rust、Go)或執行環境(Java、V8)會想辦法把指標藏起來或替你管理。

結論

指標是你程式碼與硬體之間的橋樑。就算你寫的是高階 JavaScript 或 Python,你碰到的物件底層都是參照(指標)。所以 a = b 會讓 a 指向和 b 相同的資料。

© Melvin Laplanche - All rights reserved.