目錄

你所不知道的C語言:指標篇筆記

基礎定義

你所不知道的C語言:指標篇

物件 (Object) 在 C 語言中指的不是物件導向的 Class,而是「在執行期間,一段可以儲存數值的記憶體區域」。指標就是用來操作這段區域的工具。

「指標」是連接「記憶體 (Memory)」與「物件 (Object)」的橋樑。

Scalar Type (純量): 指標是一種純量。這解釋了為什麼 ptr++ 不一定只是加 1 (byte),而是加上「一個單位」。

  • **Incomplete Type (不完全型別):**解釋了為什麼我們可以寫 struct Node; (前向宣告) 然後就開始用 struct Node *ptr。因為編譯器只要知道這是一個「指標」(固定大小,例如 8 bytes),它就能工作,不需要知道 struct 裡面的詳細內容。

「衍生宣告型別 (Derived Declarator Types)」

這是 C 語言規格書中的術語。簡單來說,C 語言有一些「基本型別」(如 int, char, float),而我們可以在這些基本型別之上,「衍生」出三種變體:

陣列 (Array)

函式 (Function)

指標 (Pointer)

void *

為什麼這很重要? 因為電腦記憶體最小單位是 Byte (8-bit),而 char 正好代表一個 Byte。 所以 memcpy (記憶體複製) 這種函式,雖然是用來搬運任何東西 (int, float, struct…),但它的參數是用 void 。這意味著在 C 語言眼中,所有資料在底層都可以被視為一連串的 Bytes (char) 來處理。

-> vs

古代觀點 (Compiler 很笨): 在 C 語言剛誕生時(甚至是 C 的前身 B 語言或 BCPL),硬體和編譯器非常原始。

Struct 只是「假象」: 早期的 Struct 不能當作一個完整的物件被搬來搬去(不能直接放在暫存器),它只是一塊記憶體。

p 的困境: 如果你寫p,早期的編譯器會試圖把整個 Struct 讀出來,但硬體做不到(因為 Struct 可能很大,暫存器放不下)。所以 (*p).x 在當年是無法翻譯成機器碼的。

-> 的誕生: 為了讓編譯器知道「我不是要讀整個 Struct,我只是要算地址偏移量 (Offset)」,發明了 ->。

意義: p->x 對早期編譯器來說,意思是:「拿 p 的地址,加上 x 的偏移量,然後存取那個位置」。這是一個一步到位的指令,避開了「讀取整個 Struct」的無理要求。

condition_of

很多現代語言(Java, Python, Go)試圖隱藏指標,覺得它危險。但這段話告訴你:指標不是 C 語言發明的,它是電腦硬體原本的樣子。

極致應用:Linux Kernel 的 list_head 文章最後提到 Linux 核心最經典的設計 struct list_head。

問題: 在 C++ 或 Java 裡,你可以寫 List 或 List。但在 C 語言裡沒有 Template/Generic,要怎麼寫一個「萬用」的 Linked List?

Linux 的解法: 侵入式鏈結串列 (Intrusive Linked List)。 不讓 List 去包住資料,而是讓資料去「包含」List。

container_of 的黑魔法: 既然 List 只有 prev 和 next 指標,當我拿到一個 list_head 指標時,我要怎麼知道它是屬於哪個 student 的? 這就需要用到 指標偏移運算 (Pointer Arithmetic)。

指標 (Pointer) 與 型別系統 (Type System) 的硬核知識

  1. 為什麼 (********puts) 可以運作?
  2. 這是在解釋為什麼 str 和 &str 印出來的位址數值一樣,但它們在編譯器眼中的意義完全不同。

這段在教你如何判斷 const 到底在保護誰。判斷的秘訣是:以 * 為分界線。

宣告寫法,const 在 的哪邊?,誰不能改?,術語 const int p,左邊 (修飾 int),內容 (p = 5 ❌),Pointer to Constant int const p,左邊 (修飾 int),內容 (p = 5 ❌),Pointer to Constant int const p,右邊 (修飾 p),指標本身 (p = &b ❌),Constant Pointer const int * const p,兩邊都有,都不能改,Constant Pointer to Constant

“\0”

字串本質: C 語言沒有真正的字串型別,只有 C-style string (以 \0 結尾的 char 陣列)。

風險: 為了找出字串結尾,程式必須一直掃描記憶體直到遇到 \0。如果駭客惡意傳入一個沒有結尾的字串,就會導致程式讀到不該讀的記憶體,甚至造成 Buffer Overflow 攻擊。

. Lvalue 的真正含義 誤解: 一般人以為 Lvalue 只是「可以放在等號左邊的值」。

正解 (C99): Lvalue = Locator Value。

它代表一個 「有名字的、可以定位的記憶體物件 (Object)」。

例如:int a = 10;

a 是 Lvalue (因為它對應到一塊記憶體)。

10 不是 Lvalue (它只是暫時數值,沒地方定位)。

gcc 錯誤訊息: lvalue required as increment operand。意思是 ++ 需要一個有位址的變數來寫入新值,不能對純數值做 ++。