ข้ามไปยังเนื้อหา

Pointers

Pointer คือตัวแปรที่เก็บ memory address ของตัวแปรอื่น type ของ pointer ไปยัง T เขียนว่า *T:

x := 42
p := &x // & นำ address ของ x; p มี type *int

เพื่ออ่านหรือเขียนผ่าน pointer ให้ dereference ด้วย *:

fmt.Println(*p) // 42 — อ่านผ่าน p
*p = 100 // เขียนผ่าน p — x กลายเป็น 100
fmt.Println(x) // 100
Operatorอ่านว่าผล
&x”address of x”คืน *T ที่ชี้ไป x
*p”value at p”dereference p อ่านหรือเขียน T

1. Mutation ข้ามขอบเขตของ function. Go ส่ง argument ทุกตัวเป็น value หาก function ต้องแก้ไขตัวแปรของ caller ให้ส่ง pointer:

func double(n *int) {
*n = *n * 2
}
x := 5
double(&x)
fmt.Println(x) // 10

2. Struct ขนาดใหญ่. การ copy struct ขนาดใหญ่ทุกครั้งที่เรียก function นั้นสิ้นเปลือง ส่ง *Config แทน Config เมื่อ struct มีหลาย field

3. Optional value. *T สามารถเป็น nil เพื่อแทน “ไม่ได้กำหนด” คล้ายกับ null ในภาษาอื่น

เมื่อมี pointer ไปยัง struct Go จะ dereference ให้อัตโนมัติสำหรับการเข้าถึง field คุณไม่ต้องเขียน (*cfg).Debug:

cfg := &Config{Workers: 4}
cfg.Debug = true // shorthand ของ (*cfg).Debug = true

Built-in new(T) allocate zero value ของ type T และคืน *T:

n := new(int) // *int ที่ชี้ไป 0
*n = 7

Zero value ของ pointer type ทุกชนิดคือ nil การ dereference nil pointer ทำให้เกิด runtime panic:

var p *int
fmt.Println(p == nil) // true
// *p = 1 จะ panic: nil pointer dereference

ควรตรวจสอบ pointer ว่าเป็น nil ก่อน dereference เสมอ เมื่อ pointer นั้นอาจไม่ได้ถูกกำหนดค่า

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
}
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
UsageBenefitCost
pointer to large structหลีกเลี่ยง copy — ส่งแค่ 8 bytes (pointer size)อาจ escape to heap → GC pressure เพิ่ม
value receiverชัดเจนว่าไม่ mutate, cache-friendlycopy struct ทุกครั้งที่เรียก method
pointer receivermutate ได้, ไม่ต้อง 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 loop

Go standard library ใช้ pointer receiver สำหรับ stateful type เช่น (*bytes.Buffer).Write() — เพื่อ maintain internal state ระหว่าง call

คุณส่ง `x` (เป็น int) ไปยัง `func add(n int)` ซึ่งกำหนด `n = n + 1` หลังเรียก function แล้ว ค่าของ `x` คืออะไร?
`&x` คืนค่าอะไร?
เกิดอะไรขึ้นถ้าคุณ dereference nil pointer ใน Go?
กำหนด `cfg := &Config{Workers: 4}` จะ set field Debug อย่างไร?