Go 스 택 이해

12824 단어
Go 의 스 택 에 대해 이야기 하기 전에 스 택 기초 지식 을 복습 하 세 요.
무엇이 창고 입 니까?컴퓨터 에서 스 택 의 개념 은 데이터 구조의 스 택 과 메모리 배분 에서 스 택 으로 나 뉜 다.
데이터 구조의 스 택:
더미: 더 미 는 나무 로 볼 수 있 습 니 다. 예 를 들 어 쌓 기 정렬.대기 열 에서 스케줄 러 는 대기 열 에서 첫 번 째 작업 을 반복 적 으로 추출 하고 실행 합 니 다. 실제 상황 에서 짧 은 작업 이 오래 기 다 려 야 끝나 거나 짧 지 않 지만 중요 한 작업 도 우선권 을 가 져 야 하기 때 문 입 니 다.더 미 는 이러한 문 제 를 해결 하기 위해 설 계 된 데이터 구조 이다.
스 택: 선진 적 인 데이터 구조 입 니 다.
여기 서 중점적으로 말 하 는 것 은 메모리 분배 중의 더미 와 창고 이다.
메모리 할당 중인 더미 와 스 택
스 택 (운영 체제): 운영 체제 에서 자동 으로 분배 되 고 함수 의 매개 변수 값, 부분 변수의 값 등 을 저장 합 니 다.그 조작 방식 은 데이터 구조의 창고 와 유사 하 다.
더미 (운영 체제): 일반적으로 프로그래머 가 분배 하여 방출 한다. 만약 에 프로그래머 가 방출 하지 않 으 면 프로그램 이 끝 날 때 OS 에서 회수 할 수 있 고 분배 방식 은 체인 테이블 과 유사 하 다.
스 택 캐 시 방식
스 택 은 1 급 캐 시 를 사용 합 니 다. 그들 은 보통 호출 될 때 저장 공간 에 있 고 호출 이 끝나 면 바로 방출 합 니 다.
더 미 는 2 급 캐 시 에 저장 되 고 생명 주 기 는 가상 컴퓨터 의 쓰레기 회수 알고리즘 에 의 해 결정 된다 (고아 대상 이 되면 회수 되 는 것 이 아니다).그래서 이 대상 들 을 호출 하 는 속 도 는 상대 적 으로 낮 아야 한다.
스 택 추적
스 택 추적 정보 와 스 택 에서 함수 가 전달 하 는 인 자 를 식별 하 는 방법 에 대해 논의 합 니 다.
다음 테스트 사례 의 버 전 은 Go 1.11 입 니 다.
예시:
package main

import "runtime/debug"

func main() {
   slice := make([]string, 2, 4)
   Example(slice, "hello", 10)
}
func Example(slice []string, str string, i int) {
   debug.PrintStack()
}

목록 1 은 간단 한 프로그램 입 니 다. main 함 수 는 다섯 번 째 줄 에서 Example 함 수 를 호출 합 니 다.Example 함 수 는 9 줄 에 세 개의 인자, 하나의 문자열 slice, 하나의 문자열 과 하나의 정수 가 있 습 니 다.방법 체 도 간단 합 니 다. 한 줄 만 있 습 니 다. debug. Printstack () 은 스 택 추적 정 보 를 즉시 생 성 합 니 다.
goroutine 1 [running]:
runtime/debug.Stack(0x1, 0x0, 0x0)
    C:/Go/src/runtime/debug/stack.go:24 +0xae
runtime/debug.PrintStack()
    C:/Go/src/runtime/debug/stack.go:16 +0x29
main.Example(0xc000077f48, 0x2, 0x4, 0x4abd9e, 0x5, 0xa)
    D:/gopath/src/example/example/main.go:10 +0x27
main.main()
    D:/gopath/src/example/example/main.go:7 +0x79

스 택 추적 정보:
첫 번 째 줄 에서 실행 중인 goroutine 은 id 가 1 인 goroutine 입 니 다.
두 번 째 줄 debug. Stack () 호출 됨
네 번 째 줄 debug. Printstack () 이 호출 되 었 습 니 다.
여섯 번 째 줄 은 debug. Printstack () 의 코드 위 치 를 호출 하고 main package 에 있 는 Example 함수 입 니 다.코드 가 있 는 파일 과 경로, 그리고 debug. Printstack () 에서 발생 하 는 줄 수 (10 줄) 를 보 여 줍 니 다.
여덟 번 째 줄 도 Example 함수 의 이름 을 호출 합 니 다. main package 의 main 함수 입 니 다.파일 이름과 경로, 그리고 Example 함 수 를 호출 하 는 줄 수 를 보 여 줍 니 다.
다음은 Example 함수 전달 참조 정 보 를 분석 합 니 다.
// Declaration
main.Example(slice []string, str string, i int)
// Call to Example by main.
slice := make([]string, 2, 4)
Example(slice, "hello", 10)
// Stack trace
main.Example(0x2080c3f50, 0x2, 0x4, 0x425c0, 0x5, 0xa)

Example 함수 의 성명, 호출 및 전달 값 에 대한 정 보 를 열거 하 였 습 니 다.함수 의 성명 과 전달 값 을 비교 할 때 일치 하지 않 음 을 발견 합 니 다.함수 성명 은 세 개의 인자 만 받 고 스 택 에는 16 진법 으로 표시 되 는 값 이 6 개 표 시 됩 니 다.이 점 을 이해 하 는 관건 은 모든 매개 변수 유형의 실현 체 제 를 알 아야 한 다 는 것 이다.
첫 번 째 [] string 형식의 인 자 를 보 여 줍 니 다.slice 는 인용 형식 입 니 다. 이 값 은 포인터 의 헤더 정보 (header value) 로 문자열 을 가리 키 는 것 을 의미 합 니 다.slice 에 대해 서 는 머리 가 세 개의 워드 수 이 고 한 배열 을 가리 키 고 있 습 니 다.그래서 앞의 세 값 은 이 slice 를 대표 한다.
// Slice parameter value
slice := make([]string, 2, 4)
// Slice header values
Pointer:  0xc00006df48
Length:   0x2
Capacity: 0x4
// Declaration
main.Example(slice []string, str string, i int)
// Stack trace
main.Example(0xc00006df48, 0x2, 0x4, 0x4abd9e, 0x5, 0xa)
0xc00006df48 첫 번 째 매개 변수 [] string 을 대표 하 는 지침 을 보 여 주 었 습 니 다. 0x2 는 slice 길 이 를 대표 하고 0x4 는 용량 을 대표 합 니 다.이 세 개의 값 은 첫 번 째 매개 변 수 를 대표 한다.
// String parameter value
“hello”
// String header values
Pointer: 0x4abd9e
Length:  0x5
// Declaration
main.Example(slice []string, str string, i int)
// Stack trace
main.Example(0xc00006df48, 0x2, 0x4, 0x4abd9e, 0x5, 0xa) 

스 택 추적 정보 중 네 번 째 와 다섯 번 째 매개 변 수 는 문자열 을 대표 하 는 인 자 를 표시 합 니 다.0x4abd9e 은 이 문자열 의 밑 에 있 는 배열 을 가리 키 는 지침 입 니 다. 0x5 은 'hello' 문자열 의 길이 이 고 두 번 째 매개 변수 입 니 다.
세 번 째 매개 변 수 는 정수 입 니 다. 간단 한 워드 값 입 니 다.
// Integer parameter value
10
// Integer value
Base 16: 0xa
// Declaration
main.Example(slice []string, str string, i int)
// Stack trace
main.Example(0xc00006df48, 0x2, 0x4, 0x4abd9e, 0x5, 0xa) 

스 택 의 마지막 매개 변 수 를 표시 하 는 것 은 Example 성명 의 세 번 째 매개 변수 입 니 다. 그 값 은 0xa, 즉 정수 10 입 니 다.
Methods
다음은 예제 가 방법 이 되도록 프로그램 을 조금 바 꿔 보 자.
package main

import (
   "fmt"
   "runtime/debug"
)

type trace struct{}

func main() {
   slice := make([]string, 2, 4)
   var t trace
   t.Example(slice, "hello", 10)
}
func (t *trace) Example(slice []string, str string, i int) {
   fmt.Printf("Receiver Address: %p
", t) debug.PrintStack() }

상기 8 줄 에 trace 형식 을 새로 추 가 했 습 니 다. 15 번 에 example 를 trace 의 pointer receiver 로 바 꾸 는 방법 입 니 다.12 줄 성명 t 의 유형 은 trace 이 고 13 줄 에서 호출 하 는 방법 입 니 다.
이 방법 은 pointer receiver 방법 으로 설명 되 어 있 기 때문에 Go 는 t 포인터 로 receiver type 을 지원 합 니 다. 코드 에서 값 을 사용 하 더 라 도 이 방법 을 사용 합 니 다.프로그램 이 실 행 될 때 스 택 추적 정 보 는 다음 과 같 습 니 다.
Receiver Address: 0x5781c8
goroutine 1 [running]:
runtime/debug.Stack(0x15, 0xc000071ef0, 0x1)
    C:/Go/src/runtime/debug/stack.go:24 +0xae
runtime/debug.PrintStack()
    C:/Go/src/runtime/debug/stack.go:16 +0x29
main.(*trace).Example(0x5781c8, 0xc000071f48, 0x2, 0x4, 0x4c04bb, 0x5, 0xa)
    D:/gopath/src/example/example/main.go:17 +0x7c
main.main()
    D:/gopath/src/example/example/main.go:13 +0x9a

일곱 번 째 줄 에서 방법 을 분명하게 나타 내 는 receiver 는 pointer type 이다.방법 명 과 가방 이름 사이 에 (* trace) 가 있 습 니 다.두 번 째 주의해 야 할 것 은 스 택 정보 에서 방법의 첫 번 째 매개 변 수 는 receiver 의 값 입 니 다.방법 호출 은 항상 함수 호출 으로 변환 되 고 receiver 의 값 을 함수 의 첫 번 째 매개 변수 로 합 니 다.우 리 는 총 창고 정보 에서 실 현 된 세부 사항 을 볼 수 있다.
Packing
import (
   "runtime/debug"
)

func main() {
   Example(true, false, true, 25)
}
func Example(b1, b2, b3 bool, i uint8) {

   debug.PrintStack()
}

Example 방법 을 다시 바 꾸 어 4 개의 인 자 를 받 도록 합 니 다.앞의 세 번 째 매개 변 수 는 불 유형의 것 이 고 네 번 째 매개 변 수 는 8bit 무 기호 정수 이다.불 유형 도 8bit 로 표시 되 기 때문에 이 네 개의 매개 변 수 는 하나의 워드 로 포장 할 수 있 습 니 다. 32 비트 구조 와 64 비트 구 조 를 포함 합 니 다.프로그램 이 실 행 될 때 재 미 있 는 스 택 이 생 깁 니 다:
goroutine 1 [running]:
runtime/debug.Stack(0x4, 0xc00007a010, 0xc000077f88)
    C:/Go/src/runtime/debug/stack.go:24 +0xae
runtime/debug.PrintStack()
    C:/Go/src/runtime/debug/stack.go:16 +0x29
main.Example(0xc019010001)
    D:/gopath/src/example/example/main.go:12 +0x27
main.main()
    D:/gopath/src/example/example/main.go:8 +0x30

네 개의 값 이 하나의 값 으로 포장 되 어 있 는 것 을 볼 수 있 습 니 다. 0xc 019010001
// Parameter values
true, false, true, 25

// Word value
Bits    Binary      Hex   Value
00-07   0000 0001   01    true
08-15   0000 0000   00    false
16-23   0000 0001   01    true
24-31   0001 1001   19    25

// Declaration
main.Example(b1, b2, b3 bool, i uint8)

// Stack trace
main.Example(0x19010001)

스 택 의 값 이 매개 변수 와 어떻게 일치 하 는 지 보 여 줍 니 다.true 는 1 로 8bit 를 차지 하고 false 는 0 으로 8bit 를 차지 하 며 uint 8 값 25 의 16 진법 은 x19 로 8bit 로 표시 합 니 다.우리 수업 이 야.
Go 가 실 행 될 때 디 버 깅 프로그램 을 도와 주 는 상세 한 정 보 를 제공 합 니 다.스 택 추적 정보 stack trace 를 통 해 스 택 에 있 는 방법 을 디 코딩 하여 전달 하 는 매개 변 수 는 BUG 를 신속하게 찾 는 데 도움 이 됩 니 다.
변 수 는 쌓 기 (hep) 입 니까? 스 택 (stack) 입 니까?
c 언어 를 써 봤 지만 명확 한 창고 와 더미 의 관련 개념 이 있다.한편, Go 성명 문법 은 스 택 이나 더 미 를 언급 하지 않 았 습 니 다. Go 의 FAQ 에 만 다음 과 같은 설명 이 있 습 니 다.
How do I know whether a variable is allocated on the heap or the stack?
From a correctness standpoint, you don't need to know. Each variable in Go exists as long as there are references to it. The storage location chosen by the implementation is irrelevant to the semantics of the language.
The storage location does have an effect on writing efficient programs. When possible, the Go compilers will allocate variables that are local to a function in that function's stack frame. However, if the compiler cannot prove that the variable is not referenced after the function returns, then the compiler must allocate the variable on the garbage-collected heap to avoid dangling pointer errors. Also, if a local variable is very large, it might make more sense to store it on the heap rather than the stack.
In the current compilers, if a variable has its address taken, that variable is a candidate for allocation on the heap. However, a basic escape analysis recognizes some cases when such variables will not live past the return from the function and can reside on the stack.
정확 한 각도 에서 볼 때 당신 은 알 필요 가 없습니다.Go 의 모든 변 수 는 존재 합 니 다. 인용 만 있 으 면 됩 니 다.선택 한 저장 위 치 는 언어의 의미 와 무관 합 니 다.
저장 위 치 는 효율 적 인 프로그램 작성 에 확실히 영향 을 줄 수 있다.가능 하 다 면 Go 컴 파일 러 는 이 함수 의 스 택 프레임 의 함수 에 로 컬 변 수 를 할당 합 니 다.그러나 컴 파일 러 가 함수 가 돌아 온 후에 변수 가 인용 되 지 않 았 다 는 것 을 증명 할 수 없다 면 컴 파일 러 는 반드시 쓰레기 수집 더미 에 변 수 를 분배 하여 허공 포인터 의 오 류 를 피해 야 한다.또한 부분 변수 가 매우 크 면 스 택 대신 쌓 아 올 리 는 것 이 의미 가 있 을 수 있 습 니 다.
현재 컴 파일 러 에서 변수 가 주 소 를 가지 고 있다 면 이 변 수 는 쌓 여 있 는 후보 변수 입 니 다.그러나 기초적인 탈출 분석 은 함수 반환 값 을 초과 하지 않 는 생존 변 수 를 식별 할 수 있 기 때문에 창고 에 분배 할 수 있다.
Go 의 컴 파 일 러 는 프로그램의 정확성 을 보장 하기 위해 메모리 (더미 or 창고) 를 어디 에 분배 할 지 결정 합 니 다.
다음은 어 셈 블 리 를 통 해 구체 적 인 메모리 배분 상황 을 살 펴 보 겠 습 니 다.
새 main. go
package main

import "fmt"

func main() {
   var a [1]int
   c := a[:]
   fmt.Println(c)
}

어 셈 블 리 코드 보기
go tool compile -S main.go

출력:
[root@localhost example]# go tool compile -S main.go 
"".main STEXT size=183 args=0x0 locals=0x60
    0x0000 00000 (main.go:5)    TEXT    "".main(SB), $96-0
    0x0000 00000 (main.go:5)    MOVQ    (TLS), CX
    0x0009 00009 (main.go:5)    CMPQ    SP, 16(CX)
    0x000d 00013 (main.go:5)    JLS    173
    0x0013 00019 (main.go:5)    SUBQ    $96, SP
    0x0017 00023 (main.go:5)    MOVQ    BP, 88(SP)
    0x001c 00028 (main.go:5)    LEAQ    88(SP), BP
    0x0021 00033 (main.go:5)    FUNCDATA    $0, gclocals·f6bd6b3389b872033d462029172c8612(SB)
    0x0021 00033 (main.go:5)    FUNCDATA    $1, gclocals·3ea58e42e2dc6c51a9f33c0d03361a27(SB)
    0x0021 00033 (main.go:5)    FUNCDATA    $3, gclocals·9fb7f0986f647f17cb53dda1484e0f7a(SB)
    0x0021 00033 (main.go:6)    PCDATA    $2, $1
    0x0021 00033 (main.go:6)    PCDATA    $0, $0
    0x0021 00033 (main.go:6)    LEAQ    type.[1]int(SB), AX
    0x0028 00040 (main.go:6)    PCDATA    $2, $0
    0x0028 00040 (main.go:6)    MOVQ    AX, (SP)
    0x002c 00044 (main.go:6)    CALL    runtime.newobject(SB)
    0x0031 00049 (main.go:6)    PCDATA    $2, $1
    0x0031 00049 (main.go:6)    MOVQ    8(SP), AX
    0x0036 00054 (main.go:8)    PCDATA    $2, $0
    0x0036 00054 (main.go:8)    PCDATA    $0, $1
    0x0036 00054 (main.go:8)    MOVQ    AX, ""..autotmp_4+64(SP)
。。。。。

new object 호출 이 있 음 을 알 았 습 니 다!그 중에서 main. go: 6 은 변수 a 의 메모리 가 쌓 여 있 음 을 설명 합 니 다!
main. go 수정
package main

func main() {
   var a [1]int
   c := a[:]
   println(c)
}

어 셈 블 리 코드 다시 보기
[root@localhost example]# go tool compile -S main.go 
\"".main STEXT size=102 args=0x0 locals=0x28
    0x0000 00000 (main.go:3)    TEXT    "".main(SB), $40-0
    0x0000 00000 (main.go:3)    MOVQ    (TLS), CX
    0x0009 00009 (main.go:3)    CMPQ    SP, 16(CX)
    0x000d 00013 (main.go:3)    JLS    95
    0x000f 00015 (main.go:3)    SUBQ    $40, SP
    0x0013 00019 (main.go:3)    MOVQ    BP, 32(SP)
    0x0018 00024 (main.go:3)    LEAQ    32(SP), BP
    0x001d 00029 (main.go:3)    FUNCDATA    $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x001d 00029 (main.go:3)    FUNCDATA    $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x001d 00029 (main.go:3)    FUNCDATA    $3, gclocals·9fb7f0986f647f17cb53dda1484e0f7a(SB)
    0x001d 00029 (main.go:4)    PCDATA    $2, $0
    0x001d 00029 (main.go:4)    PCDATA    $0, $0
    0x001d 00029 (main.go:4)    MOVQ    $0, "".a+24(SP)
    0x0026 00038 (main.go:6)    CALL    runtime.printlock(SB)
    0x002b 00043 (main.go:6)    PCDATA    $2, $1
    0x002b 00043 (main.go:6)    LEAQ    "".a+24(SP), AX
    0x0030 00048 (main.go:6)    PCDATA    $2, $0
    0x0030 00048 (main.go:6)    MOVQ    AX, (SP)
    0x0034 00052 (main.go:6)    MOVQ    $1, 8(SP)
    0x003d 00061 (main.go:6)    MOVQ    $1, 16(SP)
    0x0046 00070 (main.go:6)    CALL    runtime.printslice(SB)
    0x004b 00075 (main.go:6)    CALL    runtime.printnl(SB)
    0x0050 00080 (main.go:6)    CALL    runtime.printunlock(SB)

new object 를 호출 하 는 것 을 발견 하지 못 했 습 니 다. 이 코드 a 는 스 택 에서 분 배 됩 니 다.
결론:
Go 컴 파 일 러 는 프로그램의 정확성 을 확보 하기 위해 스 택 이나 더미 에 변 수 를 할당 할 것 을 스스로 결정 합 니 다.
참고 자료:
https://www.ardanlabs.com/blo...
https://zhuanlan.zhihu.com/p/...
https://golang.org/doc/faq
links
  • 목록
  • 좋은 웹페이지 즐겨찾기