"Se um trabalhador quiser fazer bem o seu trabalho, ele deve primeiro afiar suas ferramentas." - Confúcio, "Os Analectos de Confúcio. Lu Linggong"
Primeira página > Programação > Como os Go Arrays funcionam e são complicados com o For-Range

Como os Go Arrays funcionam e são complicados com o For-Range

Publicado em 2024-08-22
Navegar:976

Este é um trecho da postagem; a postagem completa está disponível aqui: How Go Arrays Work and Get Tricky with For-Range.

O array e a fatia clássicos de Golang são bastante diretos. As matrizes têm tamanho fixo e as fatias são dinâmicas. Mas devo dizer que Go pode parecer simples superficialmente, mas tem muita coisa acontecendo nos bastidores.

Como sempre, começaremos com o básico e depois nos aprofundaremos um pouco mais. Não se preocupe, os arrays ficam muito interessantes quando você os olha de diferentes ângulos.

Cobriremos as fatias na próxima parte, deixarei isso aqui quando estiver pronto.

O que é uma matriz?

Arrays em Go são muito parecidos com aqueles em outras linguagens de programação. Eles têm um tamanho fixo e armazenam elementos do mesmo tipo em locais de memória contíguos.

Isso significa que Go pode acessar cada elemento rapidamente, pois seus endereços são calculados com base no endereço inicial do array e no índice do elemento.

func main() {
    arr := [5]byte{0, 1, 2, 3, 4}
    println("arr", &arr)

    for i := range arr {
        println(i, &arr[i])
    }
}

// Output:
// arr 0x1400005072b
// 0 0x1400005072b
// 1 0x1400005072c
// 2 0x1400005072d
// 3 0x1400005072e
// 4 0x1400005072f

Há algumas coisas a serem observadas aqui:

  • O endereço do array arr é igual ao endereço do primeiro elemento.
  • O endereço de cada elemento está separado por 1 byte um do outro porque nosso tipo de elemento é byte.

How Go Arrays Work and Get Tricky with For-Range

Array [5]byte{0, 1, 2, 3, 4} na memória

Observe a imagem com atenção.

Nossa pilha está crescendo de um endereço mais alto para um mais baixo, certo? Esta imagem mostra exatamente a aparência de um array na pilha, de arr[4] a arr[0].

Então, isso significa que podemos acessar qualquer elemento de um array sabendo o endereço do primeiro elemento (ou array) e o tamanho do elemento? Vamos tentar isso com um array int e um pacote inseguro:

func main() {
    a := [3]int{99, 100, 101}

    p := unsafe.Pointer(&a[0])

    a1 := unsafe.Pointer(uintptr(p)   8)
    a2 := unsafe.Pointer(uintptr(p)   16)

    fmt.Println(*(*int)(p))
    fmt.Println(*(*int)(a1))
    fmt.Println(*(*int)(a2))
}

// Output:
// 99
// 100
// 101

Bem, obtemos o ponteiro para o primeiro elemento e depois calculamos os ponteiros para os próximos elementos adicionando múltiplos do tamanho de um int, que é de 8 bytes em uma arquitetura de 64 bits. Em seguida, usamos esses ponteiros para acessá-los e convertê-los de volta aos valores int.

How Go Arrays Work and Get Tricky with For-Range

Array [3]int{99, 100, 101} na memória

O exemplo é apenas uma brincadeira com o pacote inseguro para acessar a memória diretamente para fins educacionais. Não faça isso na produção sem entender as consequências.

Agora, um array do tipo T não é um tipo por si só, mas um array com um tamanho e tipo específico T, é considerado um tipo. Aqui está o que quero dizer:

func main() {
    a := [5]byte{}
    b := [4]byte{}

    fmt.Printf("%T\n", a) // [5]uint8
    fmt.Printf("%T\n", b) // [4]uint8

    // cannot use b (variable of type [4]byte) as [5]byte value in assignment
    a = b 
}

Embora aeb sejam matrizes de bytes, o compilador Go os vê como tipos completamente diferentes, o formato %T deixa esse ponto claro.

Aqui está como o compilador Go o vê internamente (src/cmd/compile/internal/types2/array.go):

// An Array represents an array type.
type Array struct {
    len  int64
    elem Type
}

// NewArray returns a new array type for the given element type and length.
// A negative length indicates an unknown length.
func NewArray(elem Type, len int64) *Array { return &Array{len: len, elem: elem} }

O comprimento do array é "codificado" no próprio tipo, então o compilador conhece o comprimento do array a partir de seu tipo. Tentar atribuir um array de um tamanho a outro, ou compará-los, resultará em um erro de tipo incompatível.

Literais de matriz

Existem muitas maneiras de inicializar um array em Go, e algumas delas podem ser raramente usadas em projetos reais:

var arr1 [10]int // [0 0 0 0 0 0 0 0 0 0]

// With value, infer-length
arr2 := [...]int{1, 2, 3, 4, 5} // [1 2 3 4 5]

// With index, infer-length
arr3 := [...]int{11: 3} // [0 0 0 0 0 0 0 0 0 0 0 3]

// Combined index and value
arr4 := [5]int{1, 4: 5} // [1 0 0 0 5]
arr5 := [5]int{2: 3, 4, 4: 5} // [0 0 3 4 5]

O que estamos fazendo acima (exceto o primeiro) é definir e inicializar seus valores, o que é chamado de "literal composto". Este termo também é usado para fatias, mapas e estruturas.

Agora, aqui está uma coisa interessante: quando criamos um array com menos de 4 elementos, Go gera instruções para colocar os valores no array um por um.

Então, quando fazemos arr := [3]int{1, 2, 3, 4}, o que realmente está acontecendo é:

arr := [4]int{}
arr[0] = 1
arr[1] = 2
arr[2] = 3
arr[3] = 4

Essa estratégia é chamada de inicialização de código local. Isso significa que o código de inicialização é gerado e executado dentro do escopo de uma função específica, em vez de fazer parte do código de inicialização global ou estático.

Ficará mais claro quando você ler outra estratégia de inicialização abaixo, onde os valores não são colocados no array um por um assim.

"E quanto a arrays com mais de 4 elementos?"

O compilador cria uma representação estática do array no binário, que é conhecida como estratégia de 'inicialização estática'.

Isso significa que os valores dos elementos da matriz são armazenados em uma seção somente leitura do binário. Esses dados estáticos são criados em tempo de compilação, portanto os valores são incorporados diretamente no binário. Se você está curioso para saber como é a aparência de [5]int{1,2,3,4,5} no assembly Go:

main..stmp_1 SRODATA static size=40
    0x0000 01 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00  ................
    0x0010 03 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00  ................
    0x0020 05 00 00 00 00 00 00 00                          ........

Não é fácil ver o valor do array, ainda podemos obter algumas informações importantes disso.

Nossos dados são armazenados em stmp_1, que são dados estáticos somente leitura com tamanho de 40 bytes (8 bytes para cada elemento), e o endereço desses dados é codificado no binário.

O compilador gera código para fazer referência a esses dados estáticos. Quando nosso aplicativo é executado, ele pode usar diretamente esses dados pré-inicializados sem a necessidade de código adicional para configurar o array.

const readonly = [5]int{1, 2, 3, 4, 5}

arr := readonly

"E um array com 5 elementos, mas apenas 3 deles inicializados?"

Boa pergunta, este [5]int{1,2,3} literal se enquadra na primeira categoria, onde Go coloca o valor no array um por um.

Ao falar sobre definição e inicialização de arrays, devemos mencionar que nem todo array é alocado na pilha. Se for muito grande, ele será movido para a pilha.

Mas quão grande é "muito grande", você pode perguntar.

A partir do Go 1.23, se o tamanho da variável, e não apenas do array, exceder um valor constante MaxStackVarSize, que atualmente é de 10 MB, ela será considerada muito grande para alocação de pilha e escapará para o heap.

func main() {
    a := [10 * 1024 * 1024]byte{}
    println(&a)

    b := [10*1024*1024   1]byte{}
    println(&b)
}

Neste cenário, b irá para o heap enquanto a não irá.

Operações de matriz

O comprimento do array é codificado no próprio tipo. Mesmo que os arrays não tenham uma propriedade cap, ainda podemos obtê-la:

func main() {
    a := [5]int{1, 2, 3}
    println(len(a)) // 5
    println(cap(a)) // 5
}

A capacidade é igual ao comprimento, sem dúvida, mas o mais importante é que sabemos disso em tempo de compilação, certo?

Portanto, len(a) não faz sentido para o compilador porque não é uma propriedade de tempo de execução, o compilador Go conhece o valor em tempo de compilação.

...

Este é um trecho da postagem; a postagem completa está disponível aqui: How Go Arrays Work and Get Tricky with For-Range.

Declaração de lançamento Este artigo foi reproduzido em: https://dev.to/func25/how-go-arrays-work-and-get-tricky-with-for-range-3i9i?1 Se houver alguma violação, entre em contato com study_golang@163 .com para excluí-lo
Tutorial mais recente Mais>

Isenção de responsabilidade: Todos os recursos fornecidos são parcialmente provenientes da Internet. Se houver qualquer violação de seus direitos autorais ou outros direitos e interesses, explique os motivos detalhados e forneça prova de direitos autorais ou direitos e interesses e envie-a para o e-mail: [email protected]. Nós cuidaremos disso para você o mais rápido possível.

Copyright© 2022 湘ICP备2022001581号-3