Heim  >  Artikel  >  Backend-Entwicklung  >  Wie Go-Arrays funktionieren und wie es mit For-Range knifflig wird

Wie Go-Arrays funktionieren und wie es mit For-Range knifflig wird

WBOY
WBOYOriginal
2024-08-20 18:44:00226Durchsuche

How Go Arrays Work and Get Tricky with For-Range

Wie Go-Arrays funktionieren und wie es mit For-Range knifflig wird

Dies ist ein Auszug aus dem Beitrag; Der vollständige Beitrag ist hier verfügbar: How Go Arrays Work and Get Tricky with For-Range.

Das klassische Golang-Array und -Slice sind ziemlich einfach. Arrays haben eine feste Größe und Slices sind dynamisch. Aber ich muss Ihnen sagen: Go mag oberflächlich betrachtet einfach erscheinen, aber unter der Haube steckt eine Menge dahinter.

Wie immer beginnen wir mit den Grundlagen und gehen dann etwas tiefer. Keine Sorge, Arrays werden ziemlich interessant, wenn man sie aus verschiedenen Blickwinkeln betrachtet.

Wir werden uns im nächsten Teil mit den Scheiben befassen, ich werde sie hier einfügen, sobald sie fertig sind.

Was ist ein Array?

Arrays in Go ähneln denen in anderen Programmiersprachen sehr. Sie haben eine feste Größe und speichern Elemente des gleichen Typs an zusammenhängenden Speicherorten.

Das bedeutet, dass Go schnell auf jedes Element zugreifen kann, da seine Adressen basierend auf der Startadresse des Arrays und dem Index des Elements berechnet werden.

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

Hier gibt es ein paar Dinge zu beachten:

  • Die Adresse des Arrays arr ist dieselbe wie die Adresse des ersten Elements.
  • Die Adresse jedes Elements ist 1 Byte voneinander entfernt, da unser Elementtyp Byte ist.

How Go Arrays Work and Get Tricky with For-Range

Array [5]Byte{0, 1, 2, 3, 4} im Speicher

Schauen Sie sich das Bild genau an.

Unser Stack wächst von einer höheren zu einer niedrigeren Adresse nach unten, oder? Dieses Bild zeigt genau, wie ein Array im Stapel aussieht, von arr[4] bis arr[0].

Bedeutet das also, dass wir auf jedes Element eines Arrays zugreifen können, indem wir die Adresse des ersten Elements (oder des Arrays) und die Größe des Elements kennen? Versuchen wir es mit einem int-Array und einem unsicheren Paket:

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

Nun, wir erhalten den Zeiger auf das erste Element und berechnen dann die Zeiger auf die nächsten Elemente, indem wir Vielfache der Größe eines int addieren, was bei einer 64-Bit-Architektur 8 Bytes entspricht. Dann verwenden wir diese Zeiger, um auf sie zuzugreifen und sie wieder in die int-Werte umzuwandeln.

How Go Arrays Work and Get Tricky with For-Range

Array [3]int{99, 100, 101} im Speicher

Das Beispiel ist nur ein Herumspielen mit dem unsicheren Paket, um zu Bildungszwecken direkt auf den Speicher zuzugreifen. Tun Sie dies nicht in der Produktion, ohne die Konsequenzen zu verstehen.

Nun ist ein Array vom Typ T kein Typ für sich, aber ein Array mit einer bestimmten Größe und Typ T wird als Typ betrachtet. Folgendes meine ich:

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 
}

Obwohl sowohl a als auch b Byte-Arrays sind, sieht der Go-Compiler sie als völlig unterschiedliche Typen. Das %T-Format macht diesen Punkt deutlich.

So sieht es der Go-Compiler intern (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} }

Die Länge des Arrays ist im Typ selbst „kodiert“, sodass der Compiler die Länge des Arrays anhand seines Typs kennt. Der Versuch, ein Array einer Größe einem anderen zuzuordnen oder sie zu vergleichen, führt zu einem Fehler aufgrund eines nicht übereinstimmenden Typs.

Array-Literale

Es gibt viele Möglichkeiten, ein Array in Go zu initialisieren, und einige davon werden in echten Projekten möglicherweise selten verwendet:

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]

Was wir oben tun (mit Ausnahme des ersten), ist die Definition und Initialisierung ihrer Werte, was als „zusammengesetztes Literal“ bezeichnet wird. Dieser Begriff wird auch für Slices, Maps und Strukturen verwendet.

Hier ist etwas Interessantes: Wenn wir ein Array mit weniger als 4 Elementen erstellen, generiert Go Anweisungen, um die Werte einzeln in das Array einzufügen.

Wenn wir also arr := [3]int{1, 2, 3, 4} machen, passiert tatsächlich Folgendes:

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

Diese Strategie wird als lokale Code-Initialisierung bezeichnet. Dies bedeutet, dass der Initialisierungscode im Rahmen einer bestimmten Funktion generiert und ausgeführt wird und nicht Teil des globalen oder statischen Initialisierungscodes ist.

Es wird klarer, wenn Sie unten eine andere Initialisierungsstrategie lesen, bei der die Werte nicht einzeln in das Array eingefügt werden.

"Was ist mit Arrays mit mehr als 4 Elementen?"

Der Compiler erstellt eine statische Darstellung des Arrays in der Binärdatei, was als „statische Initialisierungsstrategie“ bekannt ist.

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                          ........

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

"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)
}

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
}

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.

Das obige ist der detaillierte Inhalt vonWie Go-Arrays funktionieren und wie es mit For-Range knifflig wird. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn