Rafael

Engenheiro de Software

Olá, Eu sou Rafael. Estou construindo coisas para a web e mobile.

Um desenvolvedor dedicado a criar experiências digitais de alto desempenho, acessíveis e limpas. Eu me especializo em transformar problemas complexos em soluções de engenharia elegantes.

Heap e Stack e suas diferenças em golang

Heap

Região de memória usada para alocação dinâmica de dados com tempo de vida variável.
É mais flexível e, em geral, tem custo maior de alocação/acesso que a stack.
Em Go, objetos no heap são gerenciados pelo Garbage Collector (GC).

Stack

Armazena os frames de chamadas de função (parâmetros, variáveis locais, endereço de retorno etc.).
Segue o modelo LIFO (Last In, First Out): a última chamada a entrar é a primeira a sair.
Quando a função termina, seu frame é removido automaticamente (sem GC).

Stack no Go

A stack no Go funciona de forma dinâmica através de cópia de stack (stack copying), trazendo alta eficiência de memória e bom desempenho.

  • Cada goroutine inicia com apenas 2 KB de stack.
  • Conforme a goroutine vai sendo executada, o runtime verifica se há necessidade de mais espaço na stack
  • Conforme a execução avança e mais espaço é necessário, o runtime aloca uma nova stack maior (geralmente o dobro do tamanho anterior).
  • Todo o conteúdo da stack antiga é copiado para a nova, e todos os ponteiros são atualizados.
  • Quando o uso diminui, a stack pode ser reduzida novamente.

Guard Page e Stack Overflow

Stack Overflow: O runtime do Go faz verificações proativas para impedir que a stack cresça indefinidamente, gerando um erro controlado (runtime error: stack overflow) quando o limite é atingido.

Guard Page: Região de proteção no final da stack que ajuda a detectar a necessidade de crescimento e previne acessos inválidos de memória (segmentation faults).

Heap e Stack e suas diferenças

Stack (Pilha)

A stack é uma região de memória usada para armazenar:

  • chamadas de funções;
  • parâmetros;
  • variáveis locais;
  • endereços de retorno;
  • informações de controle da execução.

Ela segue a estrutura LIFO (Last In, First Out): o último item que entra é o primeiro que sai.

Quando uma função é chamada, um novo frame é empilhado na stack. Quando a função termina, esse frame é removido automaticamente.

Exemplo:

GO
func soma(a int, b int) int {
    resultado := a + b
    return resultado
}

Durante a execução:

  • a, b e resultado ficam na stack;
  • ao finalizar a função, essa memória é liberada automaticamente.

Características da Stack

  • Muito rápida;
  • Gerenciada automaticamente;
  • Possui tamanho mais limitado;
  • Ideal para dados pequenos e temporários;
  • Não precisa do Garbage Collector.

Heap

O heap é uma região de memória usada para alocação dinâmica de objetos e estruturas maiores ou que precisam sobreviver após o término de uma função.

Diferente da stack, o heap:

  • não segue LIFO;
  • cresce e diminui dinamicamente;
  • possui gerenciamento mais complexo;
  • normalmente depende do Garbage Collector.

Exemplo em Go:

GO
type User struct {
    Name string
}

func createUser() *User {
    user := User{Name: "Rafael"}
    return &user
}

Nesse caso:

  • user pode escapar da stack;
  • o compilador coloca o objeto no heap;
  • o Garbage Collector libera a memória quando não houver mais referências.

Características do Heap

  • Mais flexível;
  • Maior capacidade de memória;
  • Mais lento que a stack;
  • Usado para objetos compartilhados ou persistentes;
  • Precisa de gerenciamento de memória (GC no Go).

Diferença entre Stack e Heap

StackHeap
Memória rápidaMemória mais lenta
Estrutura LIFOSem ordem específica
Gerenciamento automáticoGerenciado pelo GC
Variáveis locais e chamadas de funçãoObjetos dinâmicos
Menor capacidadeMaior capacidade
Liberação imediata ao fim da funçãoLiberação depende do GC

Observação importante em Go

Em Go, não é o programador que escolhe diretamente entre stack e heap. O compilador faz isso usando uma análise chamada:

  • escape analysis.

Se o valor não precisa sobreviver fora da função, normalmente fica na stack.
Se precisar sobreviver ou ser compartilhado, vai para o heap.

Exemplo:

GO
go build -gcflags="-m"

Esse comando mostra quais variáveis “escaparam” para o heap.

Ultima dica

GO
package main

import "fmt"

func Total[T float64 | int](a map[string]T) T {
	var sum T
	for _, v := range a {
		sum += v
	}
	return sum
}

func main() {
	a := map[string]float64{
		"apple":  1.5,
		"banana": 2.0,
		"orange": 3.0,
	}
	b := map[string]int{
		"apple":  1,
		"banana": 2,
		"orange": 3,
	}
	fmt.Println(Total(a)) // Output: 6.5
	println(Total(b))     // Output: 6
}

O fmt.Println faz com que o valor passado (no caso, o resultado de Total(a)) escape para a heap, enquanto o println (builtin) geralmente não faz.

Por que isso acontece?

  • Ele usa interface{}
  • Usa abstrações pesadas (reflection)

0 comentários

Deixe seu comentário