
本文旨在讲解如何在 Go 语言中存储 new() 函数返回的指针所指向的内存地址。虽然可以使用 unsafe 包中的 Pointer 类型,但存在潜在的安全风险。本文将探讨更安全且更实用的方法,并简要介绍 reflect 和 unsafe 包的相关用法,帮助开发者更好地理解和使用指针地址。
在 Go 语言中,如果你需要存储 new() 函数分配的内存地址,并将其用作 map 的键,直接使用指针类型可能会遇到问题,因为 Go 是一种类型安全的语言。以下是一些建议和方法,帮助你安全有效地存储和使用指针地址。
使用 unsafe.Pointer (不推荐)
unsafe 包提供了一种将任何类型的指针转换为 unsafe.Pointer 的方法。unsafe.Pointer 可以转换为 uintptr,后者可以作为 map 的键。但是,使用 unsafe 包具有潜在的风险,因为它绕过了 Go 的类型安全检查。
package main
import (
"fmt"
"time"
"unsafe"
)
type T struct {
a, b int
}
func main() {
var t int64 = time.Now().UnixNano()
memmap := make(map[uintptr]int64)
fmt.Printf("%d\n", t)
var ptr *T = new(T)
ptr.a = 1
ptr.b = 2
fmt.Printf("%d %d %p %T\n", ptr.a, ptr.b, ptr, ptr)
// 将指针转换为 unsafe.Pointer,再转换为 uintptr
addr := uintptr(unsafe.Pointer(ptr))
memmap[addr] = t
fmt.Printf("Address stored in map: %x\n", addr)
// 从map中取出数据,需要将uintptr转换回指针才能访问原始数据,非常危险!
// 强烈不建议这样做
// restoredPtr := (*T)(unsafe.Pointer(addr))
// fmt.Printf("Restored value: %d %d\n", restoredPtr.a, restoredPtr.b)
}注意事项:
- 使用 unsafe.Pointer 需要谨慎,因为它可能导致程序崩溃或产生未定义的行为。
- 在垃圾回收过程中,如果指针不再被引用,内存可能会被释放,导致 unsafe.Pointer 指向无效的内存地址。
- 不建议从 uintptr 转换回原始指针类型,除非你非常确定内存仍然有效。
使用类型和地址的元组
更安全和推荐的方法是使用类型和地址的元组作为 map 的键。这可以确保类型安全,并避免 unsafe 包的潜在风险。虽然这种方法增加了复杂性,但它提供了更好的控制和安全性。
package main
import (
"fmt"
"reflect"
"time"
)
type T struct {
a, b int
}
type AddressKey struct {
Type reflect.Type
Addr uintptr
}
func main() {
var t int64 = time.Now().UnixNano()
memmap := make(map[AddressKey]int64)
fmt.Printf("%d\n", t)
var ptr *T = new(T)
ptr.a = 1
ptr.b = 2
fmt.Printf("%d %d %p %T\n", ptr.a, ptr.b, ptr, ptr)
// 使用 reflect 获取类型信息和地址
key := AddressKey{
Type: reflect.TypeOf(ptr).Elem(), // 获取指针指向的类型
Addr: reflect.ValueOf(ptr).Pointer(), // 获取指针地址
}
memmap[key] = t
fmt.Printf("Address stored in map: %v\n", key)
// 从map中取出数据
value, ok := memmap[key]
if ok {
fmt.Printf("Value from map: %d\n", value)
}
}代码解释:
- AddressKey 结构体: 定义了一个结构体,用于存储类型信息 (reflect.Type) 和地址 (uintptr)。
- reflect.TypeOf(ptr).Elem(): 获取指针 ptr 指向的类型,例如 T。
- reflect.ValueOf(ptr).Pointer(): 获取指针 ptr 的地址,返回 uintptr 类型。
- memmap[key] = t: 将 AddressKey 结构体作为键,将时间戳 t 作为值,存储到 map 中。
优点:
- 类型安全:使用 reflect 包获取类型信息,避免了直接使用 unsafe.Pointer 带来的类型安全问题。
- 明确性:明确地存储了类型信息和地址信息,方便后续使用。
缺点:
- 复杂性:相比直接使用 unsafe.Pointer,代码更加复杂。
- 性能:使用 reflect 包可能会带来一定的性能损耗。
总结
在 Go 语言中存储指针地址需要谨慎。虽然可以使用 unsafe.Pointer,但存在安全风险。更安全的方法是使用类型和地址的元组,这需要使用 reflect 包获取类型信息和地址信息。选择哪种方法取决于你的具体需求和对安全性的考虑。建议优先选择类型安全的方案,避免使用 unsafe 包,除非你有充分的理由并且了解潜在的风险。










