>  기사  >  백엔드 개발  >  Go 어레이의 작동 방식과 For-Range의 까다로움

Go 어레이의 작동 방식과 For-Range의 까다로움

WBOY
WBOY원래의
2024-08-20 18:44:00226검색

How Go Arrays Work and Get Tricky with For-Range

Go 어레이의 작동 방식과 For-Range의 까다로움

게시물 일부 발췌입니다. 전체 게시물은 여기에서 볼 수 있습니다: Go Arrays 작동 방법 및 For-Range를 사용하여 까다롭게 만드는 방법.

클래식 Golang 배열과 슬라이스는 매우 간단합니다. 배열은 크기가 고정되어 있고 슬라이스는 동적입니다. 하지만 Go는 표면적으로는 단순해 보일 수 있지만 그 내부에는 많은 일이 벌어지고 있다는 점을 꼭 말씀드리고 싶습니다.

언제나 그렇듯이 기본부터 시작하여 조금 더 자세히 살펴보겠습니다. 걱정하지 마세요. 배열을 다른 각도에서 보면 꽤 흥미로워집니다.

다음 부분에서 해당 부분을 다루겠습니다. 준비가 되면 여기에 놓겠습니다.

배열이란 무엇입니까?

Go의 배열은 다른 프로그래밍 언어의 배열과 매우 유사합니다. 고정된 크기를 가지며 동일한 유형의 요소를 인접한 메모리 위치에 저장합니다.

이는 배열의 시작 주소와 요소의 인덱스를 기반으로 주소가 계산되므로 Go가 각 요소에 빠르게 액세스할 수 있음을 의미합니다.

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

여기서 주목해야 할 몇 가지 사항이 있습니다.

  • 배열 arr의 주소는 첫 번째 요소의 주소와 같습니다.
  • 요소 유형이 바이트이므로 각 요소의 주소는 서로 1바이트 떨어져 있습니다.

How Go Arrays Work and Get Tricky with For-Range

메모리 내 배열 [5]바이트{0, 1, 2, 3, 4}

이미지를 잘 살펴보세요.

저희 스택은 높은 주소에서 낮은 주소로 하향 성장하고 있죠? 이 그림은 arr[4]부터 arr[0]까지 스택에서 배열이 어떻게 보이는지 정확하게 보여줍니다.

그렇다면 첫 번째 요소(또는 배열)의 주소와 요소의 크기를 알면 배열의 모든 요소에 액세스할 수 있다는 뜻인가요? int 배열과 안전하지 않은 패키지를 사용하여 이것을 시도해 보겠습니다.

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

첫 번째 요소에 대한 포인터를 가져온 다음 int 크기(64비트 아키텍처에서 8바이트)의 배수를 더하여 다음 요소에 대한 포인터를 계산합니다. 그런 다음 이 포인터를 사용하여 액세스하고 다시 int 값으로 변환합니다.

How Go Arrays Work and Get Tricky with For-Range

메모리 내 배열 [3]int{99, 100, 101}

이 예는 교육 목적으로 메모리에 직접 액세스하기 위해 안전하지 않은 패키지를 사용해 본 것입니다. 결과를 이해하지 못한 채 프로덕션 환경에서 이 작업을 수행하지 마세요.

이제 T 유형의 배열은 그 자체로는 유형이 아니지만 특정 크기와 T 유형을 갖는 배열은 유형으로 간주됩니다. 제 말은 다음과 같습니다.

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 
}

a와 b가 모두 바이트 배열이더라도 Go 컴파일러는 이를 완전히 다른 유형으로 간주하지만 %T 형식을 사용하면 이 점이 명확해집니다.

Go 컴파일러가 이를 내부적으로 보는 방법은 다음과 같습니다(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} }

배열의 길이는 유형 자체에서 "인코딩"되므로 컴파일러는 해당 유형에서 배열의 길이를 알 수 있습니다. 한 크기의 배열을 다른 크기의 배열에 할당하거나 비교하려고 하면 유형 불일치 오류가 발생합니다.

배열 리터럴

Go에서 배열을 초기화하는 방법은 여러 가지가 있으며 그 중 일부는 실제 프로젝트에서 거의 사용되지 않을 수 있습니다.

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]

위에서 수행하는 작업(첫 번째 작업 제외)은 "복합 리터럴"이라고 하는 해당 값을 정의하고 초기화하는 것입니다. 이 용어는 슬라이스, 맵, 구조체에도 사용됩니다.

흥미로운 점은 4개 미만의 요소로 배열을 만들 때 Go가 값을 배열에 하나씩 입력하라는 명령을 생성한다는 것입니다.

따라서 arr := [3]int{1, 2, 3, 4}를 수행할 때 실제로 일어나는 일은 다음과 같습니다.

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

이 전략을 로컬 코드 초기화라고 합니다. 이는 초기화 코드가 전역 또는 정적 초기화 코드의 일부가 아닌 특정 함수 범위 내에서 생성되고 실행된다는 것을 의미합니다.

값이 배열에 하나씩 배치되지 않는 아래의 또 다른 초기화 전략을 읽어보면 더 명확해질 것입니다.

"요소가 4개 이상인 배열은 어떻습니까?"

컴파일러는 '정적 초기화' 전략이라고 알려진 배열의 정적 표현을 바이너리로 생성합니다.

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.

위 내용은 Go 어레이의 작동 방식과 For-Range의 까다로움의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.