Golang的unsafe.Pointer是真的不安全

本文探讨了Golang中使用指针避免数据拷贝的方法,通过将字符串转换为字节切片来实现。然而,这种方法存在安全隐患,如不当的容量(cap)读取可能导致内存越界。文章强调了代码安全的重要性。

今天看了一篇文章,介绍通过指针的方式在类型强转的过程中避免数据拷贝。代码如下:

a :="aaa"
ssh := *(*reflect.StringHeader)(unsafe.Pointer(&a))
b := *(*[]byte)(unsafe.Pointer(&ssh))
fmt.Printf("%T",b)
fmt.Printf("%v",b)

原理就是,内置string类型其实本质是个reflect.StringHeader,内置切片类型其本质其实是reflect.SliceHeader,他们的定义分别如下:

type StringHeader struct {
	Data uintptr
	Len  int
}

type SliceHeader struct {
	Data uintptr
	Len  int
	Cap  int
}

这样在进行指针强转的过程中,其实是把指向reflect.StringHeader结构体的指针赋值给指向reflect.SliceHeader结构体的指针。表面上看打印的结果没问题,但其实有大问题。在程序里如果增加下面这条语句就能看出端倪。

fmt.Printf("len(b)=%d,cap(b)=%d\n",len(b),cap(b))

在我的电脑上运行结果如下:

len(b)=3,cap(b)=4833819

可以看到len(b)结果是正确的,但是cap(b)结果看上去让人感觉莫名其妙。这是为什么呢?

这是因为,在读取b这个reflect.SliceHeader类型数据的时候,它其实是强制套在一个reflect.StringHeader数据上的。这样第一个指针Data内容是相同的,第二个Len字段数据也是相同的,这导致len(b)的结果和len(a)的结果是相同的。但有安全隐患的是cap(b),也就是b的第三个字段Cap,它在reflect.StringHeader里是没有数据跟它对应的,所以访问这个字段的数据其实是内存越界访问了reflect.StringHeader结构体外面的数据,这是不安全的。

所以强烈不建议用类似的奇技淫巧工作,还是写一些安全点的代码好。unsafe.Pointer从名字上也警告我们,这是不安全的,它绕开了golang的安全性检查,做的都是一些不保证安全的工作。

(全文完)

参考资料:

https://mp.weixin.qq.com/s?__biz=MzAwMDAxNjU4Mg==&mid=2247483669&idx=1&sn=88f754ddabc04eb3f66ba8ac37ee1461&chksm=9aee28bcad99a1aa1ada41cfccaffc7ef4719a9bc11c1bef45b7d1b5427c1faa12d8d0c3156f&token=2092782362&lang=zh_CN&scene=21#wechat_redirect

### Golang 中 `atomic.Pointer` 的用法与实现 在 Golang 的 `sync/atomic` 包中,并存在直接命名为 `atomic.Pointer` 的类型或方法[^1]。然而,Golang 的 `sync/atomic` 包确实支持对指针类型的原子操作,例如通过 `unsafe.Pointer` 类型来实现类似的功能。以下是对原子指针操作的详细说明及其实现示例。 #### 1. 原子指针操作的支持 Golang 的 `sync/atomic` 包允许对 `unsafe.Pointer` 类型进行原子操作,包括加载 (`Load`)、存储 (`Store`) 和比较交换 (`CompareAndSwap`)。这些操作可以用来实现对任意指针类型的原子更新。以下是相关的原子操作函数: - **`LoadPointer(ptr *unsafe.Pointer) unsafe.Pointer`**:原子地加载并返回存储在 `ptr` 中的值。 - **`StorePointer(ptr *unsafe.Pointer, val unsafe.Pointer)`**:原子地将 `val` 存储到 `ptr` 中。 - **`CompareAndSwapPointer(ptr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool)`**:如果 `*ptr` 等于 `old`,则原子地将其替换为 `new` 并返回 `true`;否则返回 `false`。 #### 2. 使用 `unsafe.Pointer` 实现原子指针操作 由于 `sync/atomic` 包中的原子操作仅支持有限的内置类型(如 `int32`、`int64`、`uint32`、`uint64` 等)以及 `unsafe.Pointer`,因此需要借助 `unsafe.Pointer` 来操作自定义类型的指针。以下是一个使用 `unsafe.Pointer` 实现原子指针操作的示例: ```go package main import ( "fmt" "sync" "sync/atomic" "unsafe" ) type MyStruct struct { Field int } func main() { var ptr unsafe.Pointer // 原子指针变量 // 创建一个初始结构体实例 s1 := &MyStruct{Field: 1} ptr = unsafe.Pointer(s1) // 创建另一个结构体实例 s2 := &MyStruct{Field: 2} // 原子更新指针 atomic.StorePointer(&ptr, unsafe.Pointer(s2)) // 原子加载指针 loadedPtr := atomic.LoadPointer(&ptr) loadedStruct := (*MyStruct)(loadedPtr) fmt.Println("Loaded struct:", loadedStruct.Field) // 比较并交换指针 swapped := atomic.CompareAndSwapPointer(&ptr, unsafe.Pointer(s2), unsafe.Pointer(s1)) if swapped { fmt.Println("Swap successful") } else { fmt.Println("Swap failed") } // 再次加载指针以验证结果 loadedPtrAfterSwap := atomic.LoadPointer(&ptr) loadedStructAfterSwap := (*MyStruct)(loadedPtrAfterSwap) fmt.Println("Loaded struct after swap:", loadedStructAfterSwap.Field) } ``` #### 3. 注意事项 - 使用 `unsafe.Pointer` 时需格外小心,因为正确的转换可能导致程序崩溃或未定义行为[^1]。 - `sync/atomic` 包并未提供针对指针类型的加法操作(如 `AddPointer`),这是因为指针加法通常涉及内存地址计算,容易引发安全问题[^3]。 - 在并发环境中,原子操作是确保数据一致性的关键工具,但应避免过度依赖它们,以免降低代码可读性[^4]。 #### 4. 示例解释 上述代码展示了如何使用 `sync/atomic` 包对指针类型进行原子操作: - 使用 `atomic.StorePointer` 更新指针值。 - 使用 `atomic.LoadPointer` 加载当前指针值。 - 使用 `atomic.CompareAndSwapPointer` 进行条件更新。 这些操作适用于需要在多线程环境下安全地管理指针引用的场景。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值