你所不知道的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
Linux 的解法: 侵入式鏈結串列 (Intrusive Linked List)。 不讓 List 去包住資料,而是讓資料去「包含」List。
container_of 的黑魔法: 既然 List 只有 prev 和 next 指標,當我拿到一個 list_head 指標時,我要怎麼知道它是屬於哪個 student 的? 這就需要用到 指標偏移運算 (Pointer Arithmetic)。
指標 (Pointer) 與 型別系統 (Type System) 的硬核知識
- 為什麼 (********puts) 可以運作?
- 這是在解釋為什麼 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。意思是 ++ 需要一個有位址的變數來寫入新值,不能對純數值做 ++。