Maison > développement back-end > Golang > le corps du texte

Comment fonctionnent les tableaux Go et deviennent délicats avec For-Range

WBOY
Libérer: 2024-08-20 18:44:00
original
229 Les gens l'ont consulté

How Go Arrays Work and Get Tricky with For-Range

Comment fonctionnent les tableaux Go et deviennent délicats avec For-Range

Ceci est un extrait du post ; l'article complet est disponible ici : Comment fonctionnent les tableaux Go et deviennent délicats avec For-Range.

Le tableau et la tranche Golang classiques sont assez simples. Les tableaux sont de taille fixe et les tranches sont dynamiques. Mais je dois vous dire que Go peut sembler simple en surface, mais il se passe beaucoup de choses sous le capot.

Comme toujours, nous commencerons par les bases, puis creuserons un peu plus. Ne vous inquiétez pas, les tableaux deviennent assez intéressants lorsque vous les regardez sous différents angles.

Nous couvrirons les tranches dans la partie suivante, je les déposerai ici une fois qu'elles seront prêtes.

Qu'est-ce qu'un tableau ?

Les tableaux dans Go ressemblent beaucoup à ceux des autres langages de programmation. Ils ont une taille fixe et stockent des éléments du même type dans des emplacements mémoire contigus.

Cela signifie que Go peut accéder rapidement à chaque élément puisque leurs adresses sont calculées en fonction de l'adresse de départ du tableau et de l'index de l'élément.

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
Copier après la connexion

Il y a quelques choses à remarquer ici :

  • L'adresse du tableau arr est la même que l'adresse du premier élément.
  • L'adresse de chaque élément est à 1 octet l'une de l'autre car notre type d'élément est octet.

How Go Arrays Work and Get Tricky with For-Range

Tableau [5]octet{0, 1, 2, 3, 4} en mémoire

Regardez attentivement l'image.

Notre pile croît vers le bas d'une adresse supérieure à une adresse inférieure, n'est-ce pas ? Cette image montre exactement à quoi ressemble un tableau dans la pile, de arr[4] à arr[0].

Alors, cela signifie-t-il que nous pouvons accéder à n'importe quel élément d'un tableau en connaissant l'adresse du premier élément (ou le tableau) et la taille de l'élément ? Essayons ceci avec un tableau int et un package non sécurisé :

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
Copier après la connexion

Eh bien, nous obtenons le pointeur vers le premier élément, puis calculons les pointeurs vers les éléments suivants en ajoutant des multiples de la taille d'un int, qui fait 8 octets sur une architecture 64 bits. Ensuite, nous utilisons ces pointeurs pour y accéder et les reconvertir en valeurs int.

How Go Arrays Work and Get Tricky with For-Range

Tableau [3]int{99, 100, 101} en mémoire

L'exemple n'est qu'un jeu avec le package non sécurisé pour accéder directement à la mémoire à des fins éducatives. Ne faites pas cela en production sans comprendre les conséquences.

Maintenant, un tableau de type T n'est pas un type en soi, mais un tableau avec une taille et un type T spécifiques est considéré comme un type. Voici ce que je veux dire :

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 
}
Copier après la connexion

Même si a et b sont des tableaux d'octets, le compilateur Go les considère comme des types complètement différents, le format %T clarifie ce point.

Voici comment le compilateur Go le voit en interne (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} }
Copier après la connexion

La longueur du tableau est "codée" dans le type lui-même, donc le compilateur connaît la longueur du tableau à partir de son type. Essayer d'attribuer un tableau d'une taille à une autre, ou de les comparer, entraînera une erreur de type incompatible.

Littéraux de tableau

Il existe de nombreuses façons d'initialiser un tableau dans Go, et certaines d'entre elles peuvent être rarement utilisées dans des projets réels :

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]
Copier après la connexion

Ce que nous faisons ci-dessus (sauf pour le premier) consiste à la fois à définir et à initialiser leurs valeurs, ce que l'on appelle un « littéral composite ». Ce terme est également utilisé pour les tranches, les cartes et les structures.

Maintenant, voici une chose intéressante : lorsque nous créons un tableau avec moins de 4 éléments, Go génère des instructions pour mettre les valeurs dans le tableau une par une.

Donc, quand nous faisons arr := [3]int{1, 2, 3, 4}, ce qui se passe réellement est :

arr := [4]int{}
arr[0] = 1
arr[1] = 2
arr[2] = 3
arr[3] = 4
Copier après la connexion

Cette stratégie est appelée initialisation du code local. Cela signifie que le code d'initialisation est généré et exécuté dans le cadre d'une fonction spécifique, plutôt que de faire partie du code d'initialisation global ou statique.

Cela deviendra plus clair lorsque vous lirez une autre stratégie d'initialisation ci-dessous, où les valeurs ne sont pas placées dans le tableau une par une comme ça.

"Qu'en est-il des tableaux avec plus de 4 éléments ?"

Le compilateur crée une représentation statique du tableau dans le binaire, appelée stratégie « d'initialisation statique ».

This means the values of the array elements are stored in a read-only section of the binary. This static data is created at compile time, so the values are directly embedded into the binary. If you're curious how [5]int{1,2,3,4,5} looks like in Go assembly:

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                          ........
Copier après la connexion

It's not easy to see the value of the array, we can still get some key info from this.

Our data is stored in stmp_1, which is read-only static data with a size of 40 bytes (8 bytes for each element), and the address of this data is hardcoded in the binary.

The compiler generates code to reference this static data. When our application runs, it can directly use this pre-initialized data without needing additional code to set up the array.

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

arr := readonly
Copier après la connexion

"What about an array with 5 elements but only 3 of them initialized?"

Good question, this literal [5]int{1,2,3} falls into the first category, where Go puts the value into the array one by one.

While talking about defining and initializing arrays, we should mention that not every array is allocated on the stack. If it's too big, it gets moved to the heap.

But how big is "too big," you might ask.

As of Go 1.23, if the size of the variable, not just array, exceeds a constant value MaxStackVarSize, which is currently 10 MB, it will be considered too large for stack allocation and will escape to the heap.

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

    b := [10*1024*1024 + 1]byte{}
    println(&b)
}
Copier après la connexion

In this scenario, b will move to the heap while a won't.

Array operations

The length of the array is encoded in the type itself. Even though arrays don't have a cap property, we can still get it:

func main() {
    a := [5]int{1, 2, 3}
    println(len(a)) // 5
    println(cap(a)) // 5
}
Copier après la connexion

The capacity equals the length, no doubt, but the most important thing is that we know this at compile time, right?

So len(a) doesn't make sense to the compiler because it's not a runtime property, Go compiler knows the value at compile time.

...

This is an excerpt of the post; the full post is available here: How Go Arrays Work and Get Tricky with For-Range.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal
À propos de nous Clause de non-responsabilité Sitemap
Site Web PHP chinois:Formation PHP en ligne sur le bien-être public,Aidez les apprenants PHP à grandir rapidement!