Pointers
Pointer คืออะไร?
หัวข้อที่มีชื่อว่า “Pointer คืออะไร?”Pointer คือตัวแปรที่เก็บ memory address ของตัวแปรอื่น type ของ pointer ไปยัง T เขียนว่า *T:
x := 42p := &x // & นำ address ของ x; p มี type *intเพื่ออ่านหรือเขียนผ่าน pointer ให้ dereference ด้วย *:
fmt.Println(*p) // 42 — อ่านผ่าน p*p = 100 // เขียนผ่าน p — x กลายเป็น 100fmt.Println(x) // 100& และ * เป็น inverse กัน
หัวข้อที่มีชื่อว่า “& และ * เป็น inverse กัน”| Operator | อ่านว่า | ผล |
|---|---|---|
&x | ”address of x” | คืน *T ที่ชี้ไป x |
*p | ”value at p” | dereference p อ่านหรือเขียน T |
เมื่อไรควรใช้ pointer
หัวข้อที่มีชื่อว่า “เมื่อไรควรใช้ pointer”1. Mutation ข้ามขอบเขตของ function. Go ส่ง argument ทุกตัวเป็น value หาก function ต้องแก้ไขตัวแปรของ caller ให้ส่ง pointer:
func double(n *int) { *n = *n * 2}x := 5double(&x)fmt.Println(x) // 102. Struct ขนาดใหญ่. การ copy struct ขนาดใหญ่ทุกครั้งที่เรียก function นั้นสิ้นเปลือง ส่ง *Config แทน Config เมื่อ struct มีหลาย field
3. Optional value. *T สามารถเป็น nil เพื่อแทน “ไม่ได้กำหนด” คล้ายกับ null ในภาษาอื่น
Shorthand สำหรับ struct pointer
หัวข้อที่มีชื่อว่า “Shorthand สำหรับ struct pointer”เมื่อมี pointer ไปยัง struct Go จะ dereference ให้อัตโนมัติสำหรับการเข้าถึง field คุณไม่ต้องเขียน (*cfg).Debug:
cfg := &Config{Workers: 4}cfg.Debug = true // shorthand ของ (*cfg).Debug = truenew allocate zero value
หัวข้อที่มีชื่อว่า “new allocate zero value”Built-in new(T) allocate zero value ของ type T และคืน *T:
n := new(int) // *int ที่ชี้ไป 0*n = 7Nil pointer
หัวข้อที่มีชื่อว่า “Nil pointer”Zero value ของ pointer type ทุกชนิดคือ nil การ dereference nil pointer ทำให้เกิด runtime panic:
var p *intfmt.Println(p == nil) // true// *p = 1 จะ panic: nil pointer dereferenceควรตรวจสอบ pointer ว่าเป็น nil ก่อน dereference เสมอ เมื่อ pointer นั้นอาจไม่ได้ถูกกำหนดค่า
ไม่มี pointer arithmetic
หัวข้อที่มีชื่อว่า “ไม่มี pointer arithmetic”Go ออกแบบมาโดยตั้งใจไม่รวม pointer arithmetic คุณไม่สามารถทำ p++ หรือ p + 4 เพื่อเดินผ่าน memory ได้ ทำให้ขจัดบั๊กด้านความปลอดภัยของ memory ทั้งคลาสหนึ่งไปได้ Package unsafe มี pointer arithmetic เป็น escape hatch แต่แทบไม่จำเป็นในโค้ด application
package main
import "fmt"
type Config struct { Debug bool Workers int}
func enableDebug(cfg *Config) { cfg.Debug = true // mutates the caller's Config}
func double(n *int) { *n = *n * 2}
func main() { // & takes the address; * dereferences x := 42 p := &x fmt.Println(*p) // 42 *p = 100 fmt.Println(x) // 100 -- x was mutated through p
// Pointer for mutation across function boundary double(&x) fmt.Println(x) // 200
// Pointer to struct cfg := Config{Debug: false, Workers: 4} enableDebug(&cfg) fmt.Println(cfg.Debug) // true
// new() allocates a zeroed value and returns a pointer n := new(int) *n = 7 fmt.Println(*n) // 7
// nil pointer var ptr *int fmt.Println(ptr == nil) // true}Loading Go runtime (first run only, ~8 MB)…
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”Stack Heap (GC-managed)───────────────────── ────────────────────────x [ 42 ] ←──┐p [ 0xC001 ]─────┘ ┌──────────────────────┐cfg [ 0xC002 ]──────────────→ Config{Workers: 4} │───────────────────── └──────────────────────┘
stack allocation: เร็ว, GC ไม่ต้องแตะheap allocation: GC ต้องจัดการ, เพิ่ม latency เล็กน้อยEscape Analysis ตัดสินใจว่า variable ควรอยู่บน stack หรือ heapข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| Usage | Benefit | Cost |
|---|---|---|
| pointer to large struct | หลีกเลี่ยง copy — ส่งแค่ 8 bytes (pointer size) | อาจ escape to heap → GC pressure เพิ่ม |
| value receiver | ชัดเจนว่าไม่ mutate, cache-friendly | copy struct ทุกครั้งที่เรียก method |
| pointer receiver | mutate ได้, ไม่ต้อง copy | ต้อง nil-check, อาจเกิด aliasing bug |
*T = nil เป็น optional | แทน “ไม่มีค่า” ได้โดยไม่ต้องใช้ sentinel | ต้อง nil-check ก่อน dereference ทุกครั้ง |
ความเข้าใจผิดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ความเข้าใจผิดที่พบบ่อย”- pointer ใน Go อันตรายเหมือน C — Go ไม่มี pointer arithmetic; GC จัดการ lifetime ให้; dangling pointer เป็นไปไม่ได้
- pointer เสมอเร็วกว่า value — pointer อาจทำให้ variable escape to heap → GC ต้องทำงานเพิ่ม → latency สูงขึ้น
- interface value เก็บ value โดยตรง — interface value เก็บ (type metadata, data pointer) pair — เสมอมี indirection อย่างน้อยหนึ่งชั้น
- nil pointer = ปัญหาเหมือน NullPointerException ใน Java — Go panic มี stack trace ชัดเจน และตรวจสอบ nil ได้ง่ายด้วย
if p == nil
💡 ตัวอย่างจากของจริง
Kubernetes ใช้ pointer to struct สำหรับ object ที่แชร์ระหว่าง goroutine เช่น
*corev1.Pod— ลด copy overhead ใน reconciliation loopGo standard library ใช้ pointer receiver สำหรับ stateful type เช่น
(*bytes.Buffer).Write()— เพื่อ maintain internal state ระหว่าง call