L32 — unsafe & CGo: thoát khỏi Go safety

Hai cánh cửa hậu của Go: unsafe (đo & bẻ memory layout) và cgo (gọi C). Ba module tương tác bên dưới giúp bạn thấy những thứ thường vô hình: padding trong struct, cùng địa chỉ memory đọc dưới nhiều kiểu, và overhead khi vượt biên Go↔C.

Module 1 — Struct memory layout (Sizeof / Offsetof / padding)

Chọn một struct ở dropdown để xem byte-by-byte memory layout: màu là field thật, cam (pad) là padding compiler chèn để giữ alignment. So sánh với phiên bản "sắp xếp lại" để thấy bớt được bao nhiêu byte.

bool int32 int16 int64 float64 string (16B header) padding

Module 2 — Pointer conversion: cùng memory, kiểu khác

Cùng 8 byte trong RAM, ba cách diễn giải. Nhập một int64, xem nó "biến" thành float64[8]byte khi cast qua unsafe.Pointer.

8 byte trong RAM (hex, little-endian):

*int64
unsafe.Pointer
*T2

Cast về *int64

// trở lại kiểu gốc

Cast về *float64

// đọc 8 byte theo IEEE 754

Cast về *[8]byte

// đọc bằng byte rời

Lưu ý: đây là 6 pattern hợp lệ #1. Nếu cast giữa hai kiểu khác size (vd *int32 → *int64) thì đọc/ghi sẽ vượt bộ nhớ hợp lệ — undefined behavior.

Module 3 — Go ↔ C boundary: cgo call overhead

Animation mô phỏng các bước khi Go gọi C qua C.strlen(...): pin goroutine vào OS thread, switch register ABI, copy string, gọi C, copy ngược, unpin. Mỗi bước thêm vài ns; tổng cộng ~100–200 ns mỗi lần — gấp 100× một Go function call thường.

GO goroutine G7 đang chạy trên thread M3
GO gọi C.strlen(C.CString("hello"))
PIN pin G7 vào M3 — scheduler không thể swap G khác lên M3 nữa
GO save Go registers, switch sang C ABI (System V)
C malloc + copy "hello\0" sang C heap (CString)
C gọi strlen — duyệt đến byte zero
GO restore Go registers, return value 5 về Go
UNPIN unpin G7 — M3 lại sẵn sàng chạy G khác
GO cs vẫn ở C heap — phải C.free tay (chưa làm = leak!)
⏱ Đồng hồ tích lũy:
0 ns
Go call (inlined)
~0.3ns
Go call (no-inline)
~1.5ns
cgo call
~150ns
Ratio
~100×

💡 Hệ quả thực tế: hot loop gọi C.something() 1 triệu lần/giây → 150 ms CPU/giây = 15% một core, chỉ riêng overhead vượt biên. Đó là lý do Go std lib viết MD5, SHA, JSON, regex bằng pure Go — không wrap OpenSSL/PCRE qua CGo.