>  기사  >  백엔드 개발  >  ​Golang 인터뷰 질문: 기억탈출에 대해 간단히 이야기해볼까요?

​Golang 인터뷰 질문: 기억탈출에 대해 간단히 이야기해볼까요?

藏色散人
藏色散人앞으로
2021-04-16 10:02:411831검색

다음은 golang튜토리얼 칼럼에 나온 golang 인터뷰 질문 소개입니다. 기억탈출에 대해 간략히 이야기해볼까요? , 도움이 필요한 친구들에게 도움이 되길 바랍니다!

​Golang 인터뷰 질문: 기억탈출에 대해 간단히 이야기해볼까요?

질문

골랑의 기억탈출에 대해 아시나요? 어떤 상황에서 메모리 탈출이 발생합니까?

답변

golang 프로그램 변수는 전체 수명 주기가 런타임에 완전히 알려져 있는지 여부를 증명하기 위해 일련의 검증 데이터를 전달합니다. 변수가 이러한 검사를 통과하면 스택에 할당될 수 있습니다. 그렇지 않으면 escape라고 하며 에 할당되어야 합니다. golang程序变量会携带有一组校验数据,用来证明它的整个生命周期是否在运行时完全可知。如果变量通过了这些校验,它就可以在栈上分配。否则就说它 逃逸 了,必须在堆上分配

能引起变量逃逸到堆上的典型情况

  • 在方法内把局部变量指针返回 局部变量原本应该在栈中分配,在栈中回收。但是由于返回时被外部引用,因此其生命周期大于栈,则溢出。
  • 发送指针或带有指针的值到 channel 中。 在编译时,是没有办法知道哪个 goroutine 会在 channel 上接收数据。所以编译器没法知道变量什么时候才会被释放。
  • 在一个切片上存储指针或带指针的值。 一个典型的例子就是 []*string 。这会导致切片的内容逃逸。尽管其后面的数组可能是在栈上分配的,但其引用的值一定是在堆上。
  • slice 的背后数组被重新分配了,因为 append 时可能会超出其容量( cap )。 slice 初始化的地方在编译时是可以知道的,它最开始会在栈上分配。如果切片背后的存储要基于运行时的数据进行扩充,就会在堆上分配。
  • 在 interface 类型上调用方法。 在 interface 类型上调用方法都是动态调度的 —— 方法的真正实现只能在运行时知道。想像一个 io.Reader 类型的变量 r , 调用 r.Read(b) 会使得 r 的值和切片b 的背后存储都逃逸掉,所以会在堆上分配。

举例

  • 通过一个例子加深理解,接下来尝试下怎么通过 go build -gcflags=-m 查看逃逸的情况。
package main
import "fmt"
type A struct {
    s string
}
// 这是上面提到的 "在方法内把局部变量指针返回" 的情况
func foo(s string) *A {
    a := new(A) 
    a.s = s
    return a //返回局部变量a,在C语言中妥妥野指针,但在go则ok,但a会逃逸到堆
}
func main() {
    a := foo("hello")
    b := a.s + " world"
    c := b + "!"
    fmt.Println(c)
}

执行go build -gcflags=-m main.go

go build -gcflags=-m main.go
# command-line-arguments
./main.go:7:6: can inline foo
./main.go:13:10: inlining call to foo
./main.go:16:13: inlining call to fmt.Println
/var/folders/45/qx9lfw2s2zzgvhzg3mtzkwzc0000gn/T/go-build409982591/b001/_gomod_.go:6:6: can inline init.0
./main.go:7:10: leaking param: s
./main.go:8:10: new(A) escapes to heap
./main.go:16:13: io.Writer(os.Stdout) escapes to heap
./main.go:16:13: c escapes to heap
./main.go:15:9: b + "!" escapes to heap
./main.go:13:10: main new(A) does not escape
./main.go:14:11: main a.s + " world" does not escape
./main.go:16:13: main []interface {} literal does not escape
<autogenerated>:1: os.(*File).close .this does not escape
  • ./main.go:8:10: new(A) escapes to heap 说明 new(A) 逃逸了,符合上述提到的常见情况中的第一种。
  • ./main.go:14:11: main a.s + " world" does not escape 说明 b 变量没有逃逸,因为它只在方法内存在,会在方法结束时被回收。
  • ./main.go:15:9: b + "!" escapes to heap 说明 c 变量逃逸,通过fmt.Println(a ...interface{})
  • 변수가 힙으로 이스케이프될 수 있는 일반적인 상황
  • :
      메서드에서 지역 변수 포인터 반환 지역 변수는 원래 스택에 할당되고 스택에서 재활용되어야 합니다. 그러나 반환 시 외부에서 참조되기 때문에 스택보다 수명주기가 길어 오버플로됩니다.
    채널에 포인터나 포인터가 포함된 값을 보냅니다. 🎜 컴파일 타임에는 어떤 고루틴이 채널에서 데이터를 수신할지 알 수 있는 방법이 없습니다. 따라서 컴파일러는 변수가 언제 해제될지 알 수 없습니다. 🎜🎜🎜 슬라이스에 포인터가 있는 포인터 또는 값을 저장합니다. 🎜 일반적인 예는 []*문자열입니다. 이로 인해 슬라이스의 내용이 이스케이프됩니다. 뒤에 있는 배열은 스택에 할당될 수 있지만 참조하는 값은 힙에 있어야 합니다. 추가하는 동안 용량(캡)이 초과될 수 있으므로 🎜🎜🎜슬라이스 뒤의 배열이 재할당됩니다. 🎜 슬라이스가 초기화되는 위치는 컴파일 타임에 알 수 있으며, 처음에는 스택에 할당됩니다. 슬라이스 뒤의 스토리지가 런타임 데이터를 기반으로 확장되는 경우 힙에 할당됩니다. 🎜🎜🎜인터페이스 유형에 대한 호출 메서드. 🎜 인터페이스 유형에 대한 메서드 호출은 동적으로 예약됩니다. 메서드의 실제 구현은 런타임에만 알 수 있습니다. io.Reader 유형의 변수 r을 상상해 보세요. r.Read(b)를 호출하면 r 값과 슬라이스 b 뒤의 저장소가 이스케이프되어 힙에 할당됩니다. 🎜🎜🎜Example🎜
      🎜 예제를 통해 이해를 깊게 해보자. 다음으로 go build -gcflags=-m을 통해 탈출 상황을 확인해 보자. 🎜🎜rrreee🎜Execute go build -gcflags=-m main.go🎜rrreee
        🎜./main.go:8:10: new(A)가 힙으로 이스케이프됨 code> code>는 new(A)가 이스케이프되었음을 나타내며 위에서 언급한 일반적인 상황 중 첫 번째 상황을 따릅니다. 🎜🎜./main.go:14:11: main a.s + " world" does not escape 설명 b 변수는 메소드 내에만 존재하기 때문에 이스케이프하지 않습니다. 메서드가 끝나면 재활용됩니다. 🎜🎜./main.go:15:9: b + "!"는 힙으로 이스케이프됩니다. 설명 c 변수는 fmt.Println(a .. . 인터페이스{})인쇄된 변수는 모두 이스케이프됩니다. 관심 있는 친구는 이유를 확인할 수 있습니다. 🎜🎜위 작업을 실제로는 🎜탈출 분석🎜이라고 합니다. 🎜다음 글에서는 좀 더 까다로운 방법으로 변수 탈출을 방지하는 방법에 대해 말씀드리겠습니다. 모두가 면접관 앞에서 자랑하는 것이 편리합니다🎜. 🎜🎜🎜

    위 내용은 ​Golang 인터뷰 질문: 기억탈출에 대해 간단히 이야기해볼까요?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

    성명:
    이 기사는 segmentfault.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제