일부 하위 수준 라이브러리에서는 안전하지 않은 패키지를 사용하는 경우를 자주 볼 수 있습니다. 이 기사는 Golang의 안전하지 않은 패키지를 이해하고 안전하지 않은 패키지의 역할과 포인터 사용 방법을 소개하는 데 도움이 되기를 바랍니다.
unsafe 패키지는 go 유형의 안전 검사를 우회할 수 있는 일부 작업을 제공하여 메모리 주소를 직접 작동하고 일부 까다로운 작업을 수행합니다. 샘플 코드의 실행 환경은
go version go1.18 darwin/amd64
Memory Alignment
입니다. unsafe 패키지는 "변수에 대한 포인터가 가리키는 메모리 크기를 제외하고" 변수가 차지하는 메모리 크기를 얻기 위해 Sizeof 메소드를 제공하고 Alignof는 메모리 정렬을 얻습니다.
type demo1 struct { a bool // 1 b int32 // 4 c int64 // 8 } type demo2 struct { a bool // 1 c int64 // 8 b int32 // 4 } type demo3 struct { // 64 位操作系统, 字长 8 a *demo1 // 8 b *demo2 // 8 } func MemAlign() { fmt.Println(unsafe.Sizeof(demo1{}), unsafe.Alignof(demo1{}), unsafe.Alignof(demo1{}.a), unsafe.Alignof(demo1{}.b), unsafe.Alignof(demo1{}.c)) // 16,8,1,4,8 fmt.Println(unsafe.Sizeof(demo2{}), unsafe.Alignof(demo2{}), unsafe.Alignof(demo2{}.a), unsafe.Alignof(demo2{}.b), unsafe.Alignof(demo2{}.c)) // 24,8,1,4,8 fmt.Println(unsafe.Sizeof(demo3{})) // 16 } // 16}复制代码
위의 경우에서 데모1과 데모2는 동일한 속성을 포함하지만 정의된 속성의 순서가 다르기 때문에 메모리 크기가 다른 것을 볼 수 있습니다. 변수. 메모리 정렬이 일어나기 때문이다.
컴퓨터가 작업을 처리할 때 특정 단어 길이 단위로 데이터를 처리합니다. "예: 32비트 운영 체제, 단어 길이는 4, 64비트 운영 체제, 단어 길이는 8"입니다. 그러면 데이터를 읽을 때 단위도 단어 길이를 기준으로 합니다. 예: 64비트 운영 체제의 경우 프로그램이 한 번에 읽는 바이트 수는 8의 배수입니다. 다음은 비메모리 정렬 및 메모리 정렬 하의 데모1 레이아웃입니다.
비메모리 정렬:
변수 c는 읽을 때 서로 다른 단어 길이로 배치됩니다. CPU는 동시에 두 번 읽어야 합니다. 그리고 둘 다 읽습니다. 시간의 결과를 처리해야만 c의 값을 얻을 수 있습니다. 이 방법을 사용하면 메모리 공간이 절약되지만 처리 시간이 늘어납니다.
메모리 정렬:
메모리 정렬은 동일한 비메모리 정렬 상황을 피할 수 있는 방식을 채택하지만 "시간을 위한 공간"을 추가로 차지합니다. 특정 메모리 정렬 규칙에 대해서는 Google에서 검색할 수 있습니다.
Unsafe Pointer
여기서 포인터 유형을 선언할 수 있습니다. 즉, 포인터가 가리키는 유형이 무엇인지 명확히 해야 합니다. 컴파일하는 동안 오류가 발생합니다. 다음 예에서와 같이 컴파일러는 MyString과 string이 서로 다른 유형이므로 할당할 수 없다고 생각합니다.
func main() { type MyString string s := "test" var ms MyString = s // Cannot use 's' (type string) as the type MyString fmt.Println(ms) }
모든 유형의 변수를 가리킬 수 있는 유형이 있나요? 모든 유형의 변수를 가리킬 수 있는 unsfe.Pointer를 사용할 수 있습니다. Pointer 선언을 통해 변수의 주소를 가리키는 포인터형임을 알 수 있습니다. 특정 주소에 해당하는 값은 uinptr을 통해 변환될 수 있습니다. 포인터에는 다음과 같은 네 가지 특수 연산이 있습니다.
- 모든 유형의 포인터를 포인터 유형으로 변환할 수 있습니다.
- 포인터 유형 변수를 모든 유형의 포인터로 변환할 수 있습니다.
- uintptr 유형 변수를 포인터 유형으로 변환할 수 있습니다.
- 포인터 type 변수는 uintprt 유형으로 변환될 수 있습니다.
type Pointer *ArbitraryType // uintptr is an integer type that is large enough to hold the bit pattern of // any pointer. type uintptr uintptr func main() { d := demo1{true, 1, 2} p := unsafe.Pointer(&d) // 任意类型的指针可以转换为 Pointer 类型 pa := (*demo1)(p) // Pointer 类型变量可以转换成 demo1 类型的指针 up := uintptr(p) // Pointer 类型的变量可以转换成 uintprt 类型 pu := unsafe.Pointer(up) // uintptr 类型的变量可以转换成 Pointer 类型; 当 GC 时, d 的地址可能会发生变更, 因此, 这里的 up 可能会失效 fmt.Println(d.a, pa.a, (*demo1)(pu).a) // true true true }
포인터를 사용하는 6가지 방법
공식 문서에는 포인터를 사용하는 6가지 방법이 나와 있습니다.
포인터를 통해 *T1을 *T2로 변환
포인터는 메모리 조각을 직접 가리키므로 이 메모리 주소는 모든 유형으로 변환될 수 있습니다. 여기서 T1과 T2는 동일한 메모리 레이아웃을 가져야 하며 비정상적인 데이터가 있을 수 있다는 점에 유의해야 합니다.
func main() { type myStr string ms := []myStr{"1", "2"} //ss := ([]string)(ms) Cannot convert an expression of the type '[]myStr' to the type '[]string' ss := *(*[]string)(unsafe.Pointer(&ms)) // 将 pointer 指向的内存地址直接转换成 *[]string fmt.Println(ms, ss) }
T1과 T2의 메모리 레이아웃이 다르면 어떻게 되나요? 아래 예에서, 데모1과 데모2는 동일한 구조를 포함하지만 메모리 정렬로 인해 서로 다른 메모리 레이아웃을 갖습니다. 포인터를 변환할 때, 데모1의 주소부터 24 "sizeof" 바이트를 읽고, 데모2의 메모리 정렬 규칙에 따라 변환이 수행되며, 첫 번째 바이트는 a:true로 변환되고, 8-16바이트가 됩니다. c.:2로 변환됩니다. 16-24바이트는 데모1의 범위를 벗어나지만 여전히 직접 읽을 수 있으며 예상치 못한 값인 b:17368000을 얻습니다.
type demo1 struct { a bool // 1 b int32 // 4 c int64 // 8 } type demo2 struct { a bool // 1 c int64 // 8 b int32 // 4 } func main() { d := demo1{true, 1, 2} pa := (*demo2)(unsafe.Pointer(&d)) // Pointer 类型变量可以转换成 demo2 类型的指针 fmt.Println(pa.a, pa.b, pa.c) // true, 17368000, 2, }
포인터 유형을 uintptr 유형으로 변환 "uinptr을 포인터로 변환하면 안 됩니다"
포인터는 모든 변수를 가리킬 수 있는 포인터 유형이며 포인터를 uintptr 포인터로 변환하여 인쇄할 수 있습니다. 변수의 주소를 가리킨다. 추가로: uintptr은 포인터로 변환되어서는 안 됩니다. 다음 예를 들어보겠습니다. GC가 발생하면 d의 주소가 변경될 수 있으며, 동기화되지 않은 업데이트로 인해 up이 잘못된 메모리를 가리킬 수 있습니다.
func main() { d := demo1{true, 1, 2} p := unsafe.Pointer(&d) up := uintptr(p) fmt.Printf("uintptr: %x, ptr: %p \n", up, &d) // uintptr: c00010c010, ptr: 0xc00010c010 fmt.Println(*(*demo1)(unsafe.Pointer(up))) // 不允许 }
通过算数计算将 Pointer 转换为 uinptr 再转换回 Pointer
当 Piointer 指向一个结构体时, 可以通过此方式获取到结构体内部特定属性的 Pointer。
func main() { d := demo1{true, 1, 2} // 等同于 unsafe.Pointer(&d.b); unsafe.Add(unsafe.Pointer(&d), unsafe.Offsetof(d.b)) pb := unsafe.Pointer(uintptr(unsafe.Pointer(&d)) + unsafe.Offsetof(d.b)) fmt.Println(pb) }
当调用 syscall.Syscall 的时候, 可以讲 Pointer 转换为 uintptr
前面说过, 由于 GC 会导致变量的地址发生变更, 因此不可以直接处理 uintptr。但是, 在调用 syscall.Syscall 时候可以允许传递一个 uintptr, 这里可以简单理解为是编译器做了特殊处理, 来保证 uintptr 是安全的。
- 调用方式:
- syscall.Syscall(SYS_READ, uintptr( fd ), uintptr(unsafe.Pointer(p)), uintptr(n))
下面这种方式是不允许的:
u := uintptr(unsafe.Pointer(p)) // 不应该保存到一个变量上 syscall.Syscall(SYS_READ, uintptr( fd ), u, uintptr(n))
可以将 reflect.Value.Pointer 或 reflect.Value.UnsafeAddr 的结果「uintptr」转换为 Pointer
在 reflect 包中的 Value.Pointer 和 Value.UnsafeAddr 直接返回了地址对应的值「uintptr」, 可以直接将其结果转为 Pointer
func main() { d := demo1{true, 1, 2} // 等同于 unsafe.Pointer(&d.b); unsafe.Add(unsafe.Pointer(&d), unsafe.Offsetof(d.b)) pb := unsafe.Pointer(uintptr(unsafe.Pointer(&d)) + unsafe.Offsetof(d.b)) // up := reflect.ValueOf(&d.b).Pointer(), pc := unsafe.Pointer(up); 不安全, 不应存储到变量中 pc := unsafe.Pointer(reflect.ValueOf(&d.b).Pointer()) fmt.Println(pb, pc) }
可以将 reflect.SliceHeader 或者 reflect.StringHeader 的 Data 字段与 Pointer 相互转换
SliceHeader 和 StringHeader 其实是 slice 和 string 的内部实现, 里面都包含了一个字段 Data「uintptr」, 存储的是指向 []T 的地址, 这里之所以使用 uinptr 是为了不依赖 unsafe 包。
func main() { s := "a" hdr := (*reflect.StringHeader)(unsafe.Pointer(&s)) // *string to *StringHeader fmt.Println(*(*[1]byte)(unsafe.Pointer(hdr.Data))) // 底层存储的是 utf 编码后的 byte 数组 arr := [1]byte{65} hdr.Data = uintptr(unsafe.Pointer(&arr)) hdr.Len = len(arr) ss := *(*string)(unsafe.Pointer(hdr)) fmt.Println(ss) // A arr[0] = 66 fmt.Println(ss) //B }
应用
string、byte 转换
在业务上, 经常遇到 string 和 []byte 的相互转换。我们知道, string 底层其实也是存储的一个 byte 数组, 可以通过 reflect 直接获取 string 指向的 byte 数组, 赋值给 byte 切片, 避免内存拷贝。
func StrToByte(str string) []byte { return []byte(str) } func StrToByteV2(str string) (b []byte) { bh := (*reflect.SliceHeader)(unsafe.Pointer(&b)) sh := (*reflect.StringHeader)(unsafe.Pointer(&str)) bh.Data = sh.Data bh.Cap = sh.Len bh.Len = sh.Len return b } // go test -bench . func BenchmarkStrToArr(b *testing.B) { for i := 0; i < b.N; i++ { StrToByte(`{"f": "v"}`) } } func BenchmarkStrToArrV2(b *testing.B) { for i := 0; i < b.N; i++ { StrToByteV2(`{"f": "v"}`) } } //goos: darwin //goarch: amd64 //pkg: github.com/demo/lsafe //cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz //BenchmarkStrToArr-12 264733503 4.311 ns/op //BenchmarkStrToArrV2-12 1000000000 0.2528 ns/op
通过观察 string 和 byte 的内存布局我们可以知道, 无法直接将 string 转为 []byte 「确实 cap 字段」, 但是可以直接将 []byte 转为 string
func ByteToStr(b []byte) string { return string(b) } func ByteToStrV2(b []byte) string { return *(*string)(unsafe.Pointer(&b)) } // go test -bench . func BenchmarkArrToStr(b *testing.B) { for i := 0; i < b.N; i++ { ByteToStr([]byte{65}) } } func BenchmarkArrToStrV2(b *testing.B) { for i := 0; i < b.N; i++ { ByteToStrV2([]byte{65}) } } //goos: darwin //goarch: amd64 //pkg: github.com/demo/lsafe //cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz //BenchmarkArrToStr-12 536188455 2.180 ns/op //BenchmarkArrToStrV2-12 1000000000 0.2526 ns/op
总结
本文介绍了如何使用 unsafe 包绕过类型检查, 直接操作内存。正如 go 作者对包的命名一样, 它是 unsafe 的, 随着 go 版本的迭代, 有些机制可能会发生变更。如无必要, 不应使用这个包。如果要使用 unsafe 包, 一定要理解清楚Pointer、uinptr、对齐系数等概念。
推荐学习:Golang教程
위 내용은 Golang의 안전하지 않은 패키지에 대해 알아보기의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

Golang과 C는 각각 공연 경쟁에서 고유 한 장점을 가지고 있습니다. 1) Golang은 높은 동시성과 빠른 발전에 적합하며 2) C는 더 높은 성능과 세밀한 제어를 제공합니다. 선택은 프로젝트 요구 사항 및 팀 기술 스택을 기반으로해야합니다.

Golang은 빠른 개발 및 동시 프로그래밍에 적합한 반면 C는 극심한 성능과 기본 제어가 필요한 프로젝트에 더 적합합니다. 1) Golang의 동시성 모델은 Goroutine 및 Channel을 통한 동시성 프로그래밍을 단순화합니다. 2) C의 템플릿 프로그래밍은 일반적인 코드 및 성능 최적화를 제공합니다. 3) Golang의 쓰레기 수집은 편리하지만 성능에 영향을 줄 수 있습니다. C의 메모리 관리는 복잡하지만 제어는 괜찮습니다.

goimpactsdevelopmentpositively throughlyspeed, 효율성 및 단순성.

C는 하드웨어 리소스 및 고성능 최적화가 직접 제어되는 시나리오에 더 적합하지만 Golang은 빠른 개발 및 높은 동시성 처리가 필요한 시나리오에 더 적합합니다. 1.C의 장점은 게임 개발과 같은 고성능 요구에 적합한 하드웨어 특성 및 높은 최적화 기능에 가깝습니다. 2. Golang의 장점은 간결한 구문 및 자연 동시성 지원에 있으며, 이는 동시성 서비스 개발에 적합합니다.

Golang은 실제 응용 분야에서 탁월하며 단순성, 효율성 및 동시성으로 유명합니다. 1) 동시 프로그래밍은 Goroutines 및 채널을 통해 구현됩니다. 2) Flexible Code는 인터페이스 및 다형성을 사용하여 작성됩니다. 3) NET/HTTP 패키지로 네트워크 프로그래밍 단순화, 4) 효율적인 동시 크롤러 구축, 5) 도구 및 모범 사례를 통해 디버깅 및 최적화.

GO의 핵심 기능에는 쓰레기 수집, 정적 연결 및 동시성 지원이 포함됩니다. 1. Go Language의 동시성 모델은 고루틴 및 채널을 통한 효율적인 동시 프로그래밍을 실현합니다. 2. 인터페이스 및 다형성은 인터페이스 방법을 통해 구현되므로 서로 다른 유형을 통일 된 방식으로 처리 할 수 있습니다. 3. 기본 사용법은 기능 정의 및 호출의 효율성을 보여줍니다. 4. 고급 사용에서 슬라이스는 동적 크기 조정의 강력한 기능을 제공합니다. 5. 레이스 조건과 같은 일반적인 오류는 Getest-race를 통해 감지 및 해결할 수 있습니다. 6. 성능 최적화는 sync.pool을 통해 개체를 재사용하여 쓰레기 수집 압력을 줄입니다.

Go Language는 효율적이고 확장 가능한 시스템을 구축하는 데 잘 작동합니다. 장점은 다음과 같습니다. 1. 고성능 : 기계 코드로 컴파일, 빠른 달리기 속도; 2. 동시 프로그래밍 : 고어 라틴 및 채널을 통한 멀티 태스킹 단순화; 3. 단순성 : 간결한 구문, 학습 및 유지 보수 비용 절감; 4. 크로스 플랫폼 : 크로스 플랫폼 컴파일, 쉬운 배포를 지원합니다.

SQL 쿼리 결과의 정렬에 대해 혼란스러워합니다. SQL을 학습하는 과정에서 종종 혼란스러운 문제가 발생합니다. 최근 저자는 "Mick-SQL 기본 사항"을 읽고 있습니다.


핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

AI Hentai Generator
AI Hentai를 무료로 생성하십시오.

인기 기사

뜨거운 도구

mPDF
mPDF는 UTF-8로 인코딩된 HTML에서 PDF 파일을 생성할 수 있는 PHP 라이브러리입니다. 원저자인 Ian Back은 자신의 웹 사이트에서 "즉시" PDF 파일을 출력하고 다양한 언어를 처리하기 위해 mPDF를 작성했습니다. HTML2FPDF와 같은 원본 스크립트보다 유니코드 글꼴을 사용할 때 속도가 느리고 더 큰 파일을 생성하지만 CSS 스타일 등을 지원하고 많은 개선 사항이 있습니다. RTL(아랍어, 히브리어), CJK(중국어, 일본어, 한국어)를 포함한 거의 모든 언어를 지원합니다. 중첩된 블록 수준 요소(예: P, DIV)를 지원합니다.

Atom Editor Mac 버전 다운로드
가장 인기 있는 오픈 소스 편집기

에디트플러스 중국어 크랙 버전
작은 크기, 구문 강조, 코드 프롬프트 기능을 지원하지 않음

PhpStorm 맥 버전
최신(2018.2.1) 전문 PHP 통합 개발 도구

WebStorm Mac 버전
유용한 JavaScript 개발 도구
