목차
Memory Alignment
Unsafe Pointer
포인터를 사용하는 6가지 방법
포인터를 통해 *T1을 *T2로 변환
포인터 유형을 uintptr 유형으로 변환 "uinptr을 포인터로 변환하면 안 됩니다"
通过算数计算将 Pointer 转换为 uinptr 再转换回 Pointer
当调用 syscall.Syscall 的时候, 可以讲 Pointer 转换为 uintptr
可以将 reflect.Value.Pointer 或 reflect.Value.UnsafeAddr 的结果「uintptr」转换为 Pointer
可以将 reflect.SliceHeader 或者 reflect.StringHeader 的 Data 字段与 Pointer 相互转换
应用
string、byte 转换
总结
백엔드 개발 Golang Golang의 안전하지 않은 패키지에 대해 알아보기

Golang의 안전하지 않은 패키지에 대해 알아보기

Apr 02, 2023 am 08:30 AM
go 후방

일부 하위 수준 라이브러리에서는 안전하지 않은 패키지를 사용하는 경우를 자주 볼 수 있습니다. 이 기사는 Golang의 안전하지 않은 패키지를 이해하고 안전하지 않은 패키지의 역할과 포인터 사용 방법을 소개하는 데 도움이 되기를 바랍니다.

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에서 검색할 수 있습니다.

Golang의 안전하지 않은 패키지에 대해 알아보기

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가지 방법이 나와 있습니다.

  1. 포인터를 통해 *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)
}
로그인 후 복사

Golang의 안전하지 않은 패키지에 대해 알아보기

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, 
}
로그인 후 복사

Golang의 안전하지 않은 패키지에 대해 알아보기

  1. 포인터 유형을 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)))    // 不允许
}
로그인 후 복사
  1. 通过算数计算将 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)
}
로그인 후 복사
  1. 当调用 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))

  1. 可以将 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)
}
로그인 후 복사
  1. 可以将 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

Golang의 안전하지 않은 패키지에 대해 알아보기

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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

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

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

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

AI Clothes Remover

AI Clothes Remover

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

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

AI Hentai Generator

AI Hentai Generator

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

인기 기사

R.E.P.O. 에너지 결정과 그들이하는 일 (노란색 크리스탈)
3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 최고의 그래픽 설정
3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 아무도들을 수없는 경우 오디오를 수정하는 방법
3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25 : Myrise에서 모든 것을 잠금 해제하는 방법
4 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌

뜨거운 도구

메모장++7.3.1

메모장++7.3.1

사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전

SublimeText3 중국어 버전

중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기

스튜디오 13.0.1 보내기

강력한 PHP 통합 개발 환경

드림위버 CS6

드림위버 CS6

시각적 웹 개발 도구

SublimeText3 Mac 버전

SublimeText3 Mac 버전

신 수준의 코드 편집 소프트웨어(SublimeText3)

Golang 함수 수명주기 및 변수 범위에 대한 심층적인 이해 Golang 함수 수명주기 및 변수 범위에 대한 심층적인 이해 Apr 19, 2024 am 11:42 AM

Go에서 함수 수명주기에는 정의, 로드, 연결, 초기화, 호출 및 반환이 포함됩니다. 변수 범위는 함수 수준과 블록 수준으로 구분됩니다. 함수 내의 변수는 내부적으로 표시되지만 블록 내의 변수는 블록 내에서만 표시됩니다. .

Go에서 정규식을 사용하여 타임스탬프를 일치시키는 방법은 무엇입니까? Go에서 정규식을 사용하여 타임스탬프를 일치시키는 방법은 무엇입니까? Jun 02, 2024 am 09:00 AM

Go에서는 정규식을 사용하여 타임스탬프를 일치시킬 수 있습니다. ISO8601 타임스탬프를 일치시키는 데 사용되는 것과 같은 정규식 문자열을 컴파일합니다. ^\d{4}-\d{2}-\d{2}T \d{ 2}:\d{2}:\d{2}(\.\d+)?(Z|[+-][0-9]{2}:[0-9]{2})$ . regexp.MatchString 함수를 사용하여 문자열이 정규식과 일치하는지 확인합니다.

Go WebSocket 메시지를 보내는 방법은 무엇입니까? Go WebSocket 메시지를 보내는 방법은 무엇입니까? Jun 03, 2024 pm 04:53 PM

Go에서는 gorilla/websocket 패키지를 사용하여 WebSocket 메시지를 보낼 수 있습니다. 특정 단계: WebSocket 연결을 설정합니다. 문자 메시지 보내기: WriteMessage(websocket.TextMessage,[]byte("Message"))를 호출합니다. 바이너리 메시지 보내기: WriteMessage(websocket.BinaryMessage,[]byte{1,2,3})를 호출합니다.

Golang과 Go 언어의 차이점 Golang과 Go 언어의 차이점 May 31, 2024 pm 08:10 PM

Go와 Go 언어는 서로 다른 특성을 지닌 서로 다른 개체입니다. Go(Golang이라고도 함)는 동시성, 빠른 컴파일 속도, 메모리 관리 및 크로스 플랫폼 이점으로 유명합니다. Go 언어의 단점은 다른 언어에 비해 생태계가 덜 풍부하고 구문이 더 엄격하며 동적 타이핑이 부족하다는 점입니다.

Golang 기술 성능 최적화에서 메모리 누수를 방지하는 방법은 무엇입니까? Golang 기술 성능 최적화에서 메모리 누수를 방지하는 방법은 무엇입니까? Jun 04, 2024 pm 12:27 PM

메모리 누수로 인해 파일, 네트워크 연결, 데이터베이스 연결 등 더 이상 사용하지 않는 리소스를 닫는 방식으로 Go 프로그램 메모리가 지속적으로 증가할 수 있습니다. 더 이상 강력하게 참조되지 않는 경우 약한 참조를 사용하여 메모리 누수 및 가비지 수집 대상 개체를 방지합니다. go 코루틴을 사용하면 메모리 누수를 방지하기 위해 종료 시 코루틴 스택 메모리가 자동으로 해제됩니다.

IDE에서 Golang 함수 문서를 보는 방법은 무엇입니까? IDE에서 Golang 함수 문서를 보는 방법은 무엇입니까? Apr 18, 2024 pm 03:06 PM

IDE를 사용하여 Go 함수 문서 보기: 함수 이름 위에 커서를 놓습니다. 단축키(GoLand: Ctrl+Q, VSCode: GoExtensionPack 설치 후 F1을 누르고 "Go:ShowDocumentation" 선택)를 누릅니다.

Golang의 오류 래퍼를 사용하는 방법은 무엇입니까? Golang의 오류 래퍼를 사용하는 방법은 무엇입니까? Jun 03, 2024 pm 04:08 PM

Golang에서 오류 래퍼를 사용하면 원래 오류에 상황별 정보를 추가하여 새로운 오류를 생성할 수 있습니다. 이는 다양한 라이브러리나 구성 요소에서 발생하는 오류 유형을 통합하여 디버깅 및 오류 처리를 단순화하는 데 사용할 수 있습니다. 단계는 다음과 같습니다. error.Wrap 함수를 사용하여 원래 오류를 새 오류로 래핑합니다. 새 오류에는 원래 오류의 상황별 정보가 포함됩니다. fmt.Printf를 사용하면 래핑된 오류를 출력하여 더 많은 컨텍스트와 실행 가능성을 제공할 수 있습니다. 다양한 유형의 오류를 처리할 때 오류 유형을 통합하려면 오류.Wrap 함수를 사용하세요.

단위 테스트 Go 동시 기능 가이드 단위 테스트 Go 동시 기능 가이드 May 03, 2024 am 10:54 AM

단위 테스트 동시 기능은 동시 환경에서 올바른 동작을 보장하는 데 도움이 되므로 매우 중요합니다. 동시 기능을 테스트할 때는 상호 배제, 동기화, 격리와 같은 기본 원칙을 고려해야 합니다. 동시 기능은 경쟁 조건을 시뮬레이션하고, 테스트하고, 결과를 확인하여 단위 테스트할 수 있습니다.

See all articles