Ponteiros: endereços de memória sem aritmética

1. O que são ponteiros em Go?

Em Go, um ponteiro é uma variável que armazena o endereço de memória de outra variável. Diferente de linguagens como C, Go não permite aritmética de ponteiros, garantindo segurança de tipo e evitando acessos indevidos à memória.

A diferença fundamental é entre valor (o dado em si) e endereço de memória (onde o dado está armazenado). Enquanto uma variável comum guarda um valor diretamente, um ponteiro guarda uma referência para o local onde o valor reside.

var valor int = 42
var p *int     // declaração de ponteiro para int
p = &valor     // operador & obtém o endereço de 'valor'

fmt.Println(valor) // 42
fmt.Println(p)     // 0xc000018098 (endereço de memória)
fmt.Println(*p)    // 42 (desreferência - obtém o valor no endereço)

O operador * tem dois usos: na declaração (*int indica "ponteiro para int") e na desreferência (*p acessa o valor apontado).

2. Criando e usando ponteiros

Existem duas formas principais de criar ponteiros: usando new() ou o operador & com uma variável existente.

// Usando new() - aloca memória e retorna ponteiro
p1 := new(int)
*p1 = 10

// Usando & com variável
x := 20
p2 := &x

fmt.Println(*p1, *p2) // 10 20

Ponteiros podem ser nil, indicando que não apontam para lugar algum. Tentar desreferenciar um ponteiro nulo causa panic.

var p *int
fmt.Println(p) // <nil>
// fmt.Println(*p) // panic: runtime error: invalid memory address

// Verificação segura
if p != nil {
    fmt.Println(*p)
} else {
    fmt.Println("ponteiro nulo")
}

3. Ponteiros como parâmetros de função

Go passa argumentos por valor, mas ponteiros permitem simular passagem por referência, modificando variáveis externas dentro de funções.

func incrementar(valor int) {
    valor++ // modifica apenas a cópia local
}

func incrementarPonteiro(p *int) {
    *p++ // modifica a variável original
}

func main() {
    x := 10
    incrementar(x)
    fmt.Println(x) // 10 (não modificado)

    incrementarPonteiro(&x)
    fmt.Println(x) // 11 (modificado)
}

Para tipos grandes (structs com muitos campos), usar ponteiros evita cópias desnecessárias, melhorando performance:

type Usuario struct {
    Nome    string
    Email   string
    Dados   [1000]byte
}

func processar(u *Usuario) { // evita copiar 1KB+ a cada chamada
    u.Nome = "Modificado"
}

4. Ponteiros para structs e tipos compostos

Go oferece syntax sugar para acessar campos de struct via ponteiro: p.Campo é equivalente a (*p).Campo.

type Pessoa struct {
    Nome string
    Idade int
}

p := &Pessoa{"Alice", 30}
fmt.Println(p.Nome) // Alice (syntax sugar)
fmt.Println((*p).Nome) // Alice (equivalente explícito)

Com slices, maps e arrays, o comportamento varia:

// Slices e maps já são referências internamente
slice := []int{1, 2, 3}
modificarSlice(slice) // modifica o original

// Ponteiro para array permite modificar elementos
arr := [3]int{1, 2, 3}
pArr := &arr
pArr[0] = 100
fmt.Println(arr) // [100 2 3]

Cuidado com compartilhamento de dados mutáveis: múltiplos ponteiros para o mesmo struct podem causar race conditions em concorrência.

5. Comparação com outras linguagens

Go deliberadamente não permite aritmética de ponteiros por razões de segurança:

// Isto NÃO compila em Go:
// p := &x
// p++ // erro: não é possível fazer aritmética com ponteiros
Aspecto Go C/C++ Rust
Aritmética de ponteiros ❌ Proibida ✅ Permitida ❌ Proibida (em safe code)
Ponteiro nulo nil NULL Option<&T>
Garbage collection ✅ Sim ❌ Manual ❌ Ownership
Segurança de tipos ✅ Alta ⚠️ Média ✅ Alta

Em C, aritmética de ponteiros é comum mas perigosa:

int arr[5] = {1,2,3,4,5};
int *p = arr;
*(p+2) = 10; // modifica arr[2] - possível em C, impossível em Go

6. Armadilhas comuns e boas práticas

Ponteiros pendurados (dangling pointers): Go minimiza esse risco com garbage collection, mas é possível criar situações onde um ponteiro referencia dados que serão coletados:

func criaPonteiro() *int {
    x := 42
    return &x // seguro em Go - x escapa para o heap
}

Escape analysis: O compilador Go decide automaticamente se uma variável vai para heap ou stack:

// Escape analysis do compilador
func f1() *int {
    x := 10
    return &x // x escapa para heap
}

func f2() {
    y := 20
    fmt.Println(&y) // y escapa porque seu endereço é passado
}

Quando evitar ponteiros:
- Tipos pequenos e imutáveis (int, bool, float64)
- Valores que não precisam ser modificados
- Em APIs públicas, prefira valores para simplicidade

// Prefira valor para tipos pequenos
func soma(a, b int) int { return a + b }

// Use ponteiro para objetos grandes ou mutáveis
func atualizarUsuario(u *Usuario) { /* ... */ }

7. Exemplos práticos do mundo real

Contador compartilhado:

type Contador struct {
    valor int
}

func (c *Contador) Incrementar() {
    c.valor++
}

func (c *Contador) Valor() int {
    return c.valor
}

func main() {
    c := &Contador{}
    c.Incrementar()
    c.Incrementar()
    fmt.Println(c.Valor()) // 2
}

Cache com ponteiros para objetos grandes:

type Cache struct {
    dados map[string]*ImagemGrande
}

type ImagemGrande struct {
    Pixels [10000]byte
}

func (c *Cache) Get(chave string) *ImagemGrande {
    return c.dados[chave] // retorna ponteiro, evita cópia
}

Patterns em APIs de bibliotecas Go:

// Padrão comum: função retorna (valor, erro)
func AbrirArquivo(nome string) (*os.File, error) {
    return os.Open(nome) // retorna ponteiro para File
}

// Uso típico
arquivo, err := AbrirArquivo("dados.txt")
if err != nil {
    log.Fatal(err)
}
defer arquivo.Close()

Ponteiros em Go são ferramentas poderosas quando usados corretamente. Eles permitem modificar dados compartilhados, evitar cópias desnecessárias e criar estruturas de dados eficientes, tudo isso com a segurança que a ausência de aritmética de ponteiros proporciona.

Referências