Sitemap

Entendendo ponteiros em Golang

6 min readOct 13, 2024

Veja nesse post o que seria um ponteiro e algumas dicas de como e quando podemos utilizar ele em Go

Sabemos que os ponteiros permitem manipular diretamente o valor de uma variável na memória, o que pode trazer benefícios significativos em termos de desempenho e eficiência.

Para quem trabalha ou já trabalhou com linguagens que utilizam ponteiros como C, esse tema é bem tranquilo, mas para o pessoal que vem de outras linguagens que abstraem ou não utilizam ponteiros diretamente, como Java ou Python, o conceito pode parecer um pouco intimidador inicialmente.

Por esse motivo, resolvi criar este artigo. Meu objetivo aqui é compartilhar algumas dicas sobre o uso de ponteiros em Go, ajudando tanto aqueles que já têm experiência com o conceito quanto quem está começando a trabalhar com essa linguagem.

Mas antes de pensarmos nos ponteiros, precisamos entender como funciona a declaração de variaveis em Go.

No Go temos o conceito de zero value que, ao definir uma variável sem atribuir um valor para ela, o Go irá atribuir um valor padrão para essa variável. Cada tipo possui o seu valor padrão (0 para int, false para bool...), no caso do nosso ponteiro, o zero value será nil dado que não atribuímos nenhum valor para ele. Ou melhor dizendo: nenhum endereço!

A seguir temos um exemplo de zero value: em Go. Notem os valores default retornando na console:

zero value in Go

Avançando para o tema do nosso artigo, nós já sabemos que um ponteiro é uma referência ao endereço de memória de uma variável e que, em vez de copiar o valor de uma variável, ele “aponta” diretamente para a localização na memória onde o valor está armazenado.

A seguir temos um exemplo onde estamos criando uma variavel chamada nome com um valor “Thiago S Adriano” e depois uma outra variavel chamada nomeCompleto, que através da utilização dos ponteiros, esta apontando para o valor da variavel nome:

Note que estamos utilizando o & para que possamos acessar o endereço em memoria.

package main

import "fmt"

func main() {

var nome string = "Thiago S Adriano"
fmt.Println(&nome)

var nomeCompleto *string = &nome
fmt.Println(nomeCompleto)
}

Executando esse código nós temos o seguinte resultado:

0x14000102020
0x14000102020

Note que o resultado foi o mesmo para as duas variaveis, isso porque nomeCompleto esta apontando para o mesmo endereço de memoria da variavel nome.

E caso você queira ver o valor da variavel nomeCompleto, basta adicionarmos desreferenciamento * antes da variavel.

exemplo de ponteiro em Go

Esta começando a ficar mais simples este conceito né?

Antes de avançarmos neste conceito de ponteiros, vamos nivelar o nosso entendimento em alocação de memoria heap e stack:

  • heap: Quando precisamos determinar que uma variável precisa viver além do escopo onde foi definida (por exemplo, se um ponteiro para essa variável for retornado ou passado para outra função), ela será alocada no heap.
  • Stack (pilha): A memória stack é usada para armazenar variáveis locais que não precisam ser acessadas fora do escopo em que foram criadas. Isso é preferível, pois a alocação e desalocação de memória na stack são rápidas e eficientes, já que seguem uma estrutura de gerenciamento automática durante a execução de funções.

Agora, que já estamos familiarizados com o conceito de alocação de memoria, vamos executar o comando go build -gcflags="-m" no nosso código, para analisar como o compilador do Go trata as otimizações relacionadas ao uso de ponteiros.

Caso você não conheça este parametro -gcflags="-m"ele informa ao compilador para mostrar mensagens sobre a alocação de memória e possíveis otimizações durante a compilação.

Ele ajuda a identificar se o compilador está alocando variáveis na pilha (stack) ou no heap, o que é importante para entender o impacto do uso de ponteiros no desempenho.

Executando este comando no nosso código nós temos o seguinte resultado:

Analisando este retorno nós temos:

moved to heap: nomeO compilador está informando que a variável nome foi movida para o heap. Isso ocorre porque o valor de nome foi referenciado por um ponteiro, o que significa que ele pode viver além do escopo de sua função. Por essa razão, o Go move essa variável para o heap, onde sua alocação de memória pode ser gerenciada corretamente, garantindo que ela persista pelo tempo necessário.

*nomeCompleto escapes to heap

A variável *nomeCompleto foi movida para o heap. Isso ocorre porque o ponteiro que aponta para nomeCompleto escapa do escopo local, então a variável precisa ser alocada no heap para garantir sua persistência e acessibilidade.

Se tirarmos o desreferenciamento de frente da variavel nomeCompleto e executarmos go build -gcflags="-m"teremos o seguinte resultado:

Note que não estamos mais movendo nomeCompleto para heap.

Vejamos um outro exemplo onde nós temos uma função com parametro. Para isso vamos criar duas structs: uma para exemplificar um Usuário e uma outra para exemplificar um Investimento, depois vamos criar uma função chamada aplicarJuros.

package main

type Usuario struct {
Nome string
Idade int
}

type Investimento struct {
Valor float64
Usuario Usuario
}

func AplicarJuros(i Investimento, taxa float64) {
i.Valor += i.Valor * taxa / 100
}

Agora na nossa main, vamos criar um novo usuario e um investimento:

package main

import (
"fmt"
"ponteiros/models"
)

func main() {
nome := "Thiago"
idade := 39
usuario := models.Usuario{Nome: nome, Idade: idade}

valor := 1000.0
investimento := models.Investimento{Valor: valor, Usuario: usuario}

fmt.Printf("Valor inicial do investimento: %.2f\n", investimento.Valor)
fmt.Printf("Usuário responsável: %s\n", investimento.Usuario.Nome)

models.AplicarJuros(investimento, 10)

fmt.Printf("Valor do investimento após aplicar juros para o usuario %s: é de R$: %.2f\n", investimento.Usuario.Nome, investimento.Valor)
}

Executando esse código nós temos o seguinte resultado:

Valor inicial do investimento: 1000.00
Usuário responsável: Thiago
Valor do investimento após aplicar juros para o usuario Thiago: é de R$: 1000.00

Veja que mesmo depois de chamar a função AplicarJuros o valor do investimento continuou o mesmo.

Para conserguimos de fato alterar o valor da variável teremos que mudar o nosso código para que a função AplicarJuros receba um ponteiro como parâmetro, dessa forma faremos com que nossa função trabalhe com passagem por referência e não com passagem por valor.

É importante lembrar que o uso de ponteiros para passagem de argumentos pode tornar o código mais complexo e difícil de entender, especialmente para programadores menos experientes.

//atualizando a nossa models/investimento

func AplicarJuros(i *Investimento, taxa float64) {
i.Valor += i.Valor * taxa / 100
}

Agora atualizando a nossa main:

package main

import (
"fmt"
"ponteiros/models"
)

func main() {
nome := "Thiago"
idade := 39
usuario := models.Usuario{Nome: nome, Idade: idade}

valor := 1000.0
investimento := models.Investimento{Valor: valor, Usuario: usuario}

fmt.Printf("Valor inicial do investimento: %.2f\n", investimento.Valor)
fmt.Printf("Usuário responsável: %s\n", investimento.Usuario.Nome)

models.AplicarJuros(&investimento, 10)

fmt.Printf("Valor do investimento após aplicar juros para o usuario %s: é de R$: %.2f\n", investimento.Usuario.Nome, investimento.Valor)
}

Executando ele:

Valor inicial do investimento: 1000.00
Usuário responsável: Thiago
Valor do investimento após aplicar juros para o usuario Thiago: é de R$: 1100.00

Veja que agora ao chamarmos a função AplicarJuros, o valor contido na variável Investimento.Valor de fato foi alterado de 1000.0 para 1100.00.

Executando novamente o comando go build -gcflags=”-m” neste trecho de código nós temos:

./main.go:16:12: inlining call to fmt.Printf
./main.go:17:12: inlining call to fmt.Printf
./main.go:19:21: inlining call to models.AplicarJuros
./main.go:21:12: inlining call to fmt.Printf
./main.go:16:12: ... argument does not escape
./main.go:16:66: investimento.Valor escapes to heap
./main.go:17:12: ... argument does not escape
./main.go:17:64: investimento.Usuario.Nome escapes to heap
./main.go:21:12: ... argument does not escape
./main.go:21:114: investimento.Usuario.Nome escapes to heap
./main.go:21:133: investimento.Valor escapes to heap

Agora conseguimos ver este fluxo com mais detalhes e informações.

Bom, com isso finalizo mais este artigo, espero que tenham gostado e até o próximo pessoal.

--

--

No responses yet