Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

uintptr is a built-in, platform-dependent unsigned integer type in Go designed specifically to be large enough to hold the bit pattern of any memory address (pointer). On a 32-bit architecture, uintptr is 4 bytes; on a 64-bit architecture, it is 8 bytes. Unlike standard typed pointers (e.g., *int), uintptr is strictly an integer value. It represents the raw numeric address of a memory location and supports standard integer arithmetic operations, which Go’s standard pointers strictly prohibit.

The Conversion Bridge and Arithmetic Rules

Go’s type system does not allow direct conversion between typed pointers and uintptr. The unsafe.Pointer type acts as the mandatory intermediary bridge. When performing pointer arithmetic using uintptr, two strict rules apply:
  1. Single Expression: The conversion from unsafe.Pointer to uintptr, the arithmetic operation, and the conversion back to unsafe.Pointer must be executed as a single expression. Storing an intermediate uintptr in a variable is invalid because it allows the garbage collector to run between statements, potentially moving or reclaiming the underlying memory before the arithmetic is complete.
  2. Allocation Boundaries: The result of the arithmetic must point into the same allocated object (e.g., advancing from one index of an array to another). Advancing a pointer past the end of its allocated object results in undefined behavior. Furthermore, offsets must be calculated dynamically using unsafe.Sizeof() or unsafe.Offsetof() to account for platform-dependent type sizes.
package main

import "unsafe"

func main() {
	// Allocate an array to ensure pointer arithmetic stays within the same object
	arr := [2]int{10, 20}
	p := &arr[0]

	// VALID: Conversion and arithmetic performed in a single expression.
	// 1. p (*int) -> unsafe.Pointer
	// 2. unsafe.Pointer -> uintptr
	// 3. Add dynamic byte offset using unsafe.Sizeof to remain platform-safe
	// 4. uintptr -> unsafe.Pointer
	// 5. unsafe.Pointer -> *int
	nextPtr := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + unsafe.Sizeof(arr[0])))
	
	_ = nextPtr
}

Garbage Collection Semantics

The most critical technical characteristic of uintptr is its general invisibility to the Go Garbage Collector (GC). Because the runtime treats uintptr purely as an integer, it does not register it as a live reference to the underlying memory. This introduces two strict memory management implications:
  1. Unreachability: If an allocated object is referenced only by a uintptr variable, the GC considers the object unreachable and will reclaim its memory. The uintptr will then hold a dangling, invalid memory address.
  2. Memory Relocation: Go’s runtime dynamically manages memory, which includes moving goroutine stacks (stack shrinking and growing). When the runtime moves a variable to a new memory location, it automatically updates all typed pointers (*T) and unsafe.Pointer references pointing to that variable. It will not update a uintptr.

Compiler Exceptions for GC Invisibility

There are two critical, built-in compiler exceptions where the Go runtime temporarily protects the memory associated with a uintptr from being moved or reclaimed. Both require strict adherence to inline conversion rules.

1. Syscall and Assembly Arguments

When a uintptr is passed as an argument to an assembly function (most notably syscall.Syscall), the Go compiler specifically recognizes this pattern and implicitly keeps the underlying object alive and pinned in memory for the duration of the call. This exception only applies if the conversion occurs directly inline within the argument list. If the pointer is converted to a uintptr and stored in a variable beforehand, the compiler will not recognize the pattern.
//go:build linux

package main

import (
	"syscall"
	"unsafe"
)

func main() {
	var data [8]byte
	fd := uintptr(0) // stdin

	// VALID: The compiler recognizes the inline conversion and protects 'data'.
	// SYS_READ expects a file descriptor, a buffer memory address, and a length.
	syscall.Syscall(syscall.SYS_READ, fd, uintptr(unsafe.Pointer(&data[0])), uintptr(len(data)))

	// INVALID: The compiler does not recognize this pattern. 
	// The GC may move or reclaim 'data' before the syscall executes.
	u := uintptr(unsafe.Pointer(&data[0]))
	syscall.Syscall(syscall.SYS_READ, fd, u, uintptr(len(data)))
}

2. The reflect Package

Methods in the reflect package, specifically reflect.Value.Pointer() and reflect.Value.UnsafeAddr(), return a uintptr rather than an unsafe.Pointer to prevent callers from mutating memory without explicitly importing the unsafe package. To prevent the GC from reclaiming the memory immediately after the method returns, the compiler mandates that the uintptr result must be converted to unsafe.Pointer immediately within the same expression.
package main

import (
	"reflect"
	"unsafe"
)

func main() {
	var v int
	val := reflect.ValueOf(&v)

	// VALID: Immediate conversion in the same expression protects the memory.
	p := unsafe.Pointer(val.Pointer())

	// INVALID: Storing the uintptr before conversion breaks compiler protection.
	// The GC may reclaim or move 'v' before the conversion to unsafe.Pointer.
	u := val.Pointer()
	p2 := unsafe.Pointer(u)

	_, _ = p, p2
}

Type Hierarchy and Memory Safety

To understand uintptr, it must be placed within Go’s memory reference hierarchy:
  • *T (Typed Pointer): Memory-safe, tracked by the GC, strictly typed, no direct arithmetic allowed.
  • unsafe.Pointer: Memory-unsafe, tracked by the GC, untyped (can cast to/from any *T), arithmetic allowed via the unsafe.Add function (introduced in Go 1.17 as a safer alternative to uintptr math).
  • uintptr: Memory-unsafe, not tracked by the GC (excluding the specific inline compiler exceptions), integer type lacking pointer semantics, standard integer arithmetic allowed.
Converting a pointer to a uintptr strips away all memory-safety guarantees and lifecycle tracking provided by the Go runtime, reducing the reference to a volatile, static integer snapshot of a memory address at a specific point in time.
Master Go with Deep Grasping Methodology!Learn More