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
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
다양한 언어의 JSONJSON은 Javascript 표기법을 사용하여 데이터 구조를 레이아웃하는 데이터 형식입니다. 그러나 Javascript가 코드에서 이러한 구조를 나타낼 수 있는 유일한 언어는 아닙니다. 저는 일반적으로 '객체'{}...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.