Golang 语言中的非类型安全指针

简介: Golang 语言中的非类型安全指针

介绍

Golang 语言中的 unsafe 包中包含的操作会绕过 Golang 程序的类型安全检查,直接操作内存,从而达到提升性能的目的。导入 unsafe 包可能是不可移植(non-portable)的(随着 Golang 的版本迭代,可能会失效),并且不受 Go 1 兼容性准则的保护,所以我们应该谨慎使用。

本文主要介绍 unsafe 包的 unsafe.Pointer,它表示任意类型的指针,它类似于 C 语言中的无类型指针 void*,可以作为指针类型 *T 和 uintptr 类型值之间互相转换的中转站。

我们知道 Golang 语言中的指针类型 *T,表示一个指向 T 类型变量的指针,因为 Golang 语言是强类型的静态语言,为了安全考虑,规定两个不同的指针类型之间不可以互相转换,比如 *int 不能与 *float64 互相转换。但是,实际上是可以使用 unsafe.Pointer 进行转换。

Golang 语言中的内置数据类型 uintptr 也可以表示任何指针,它实际是数值类型,可以用于存储内存地址。它和 unsafe.Pointer 最大的区别是 unsafe.Pointer 不支持指针运算,比如 + 运算符,但 uintptr 可以支持。以下是 uintptr 的源码:

// uintptr is an integer type that is large enough to hold the bit pattern of
// any pointer.
type uintptr uintptr

02

unsafe.Ponter 类型

有了前面内容的铺垫,我们开始介绍 unsafe.Ponter,它表示指向任意类型的指针。以下是 unsafe 包的源码:

// ArbitraryType is here for the purposes of documentation only and is not actually
// part of the unsafe package. It represents the type of an arbitrary Go expression.
type ArbitraryType int
type Pointer *ArbitraryType

unsafe.Ponter 类型有四个转换规则:

  • 任何类型的指针值 *T 都可以转换为 unsafe.Pointer
  • unsafe.Pointer 可以转换为任何类型的指针值 *T
  • uintptr 可以转换为 unsafe.Pointer
  • unsafe.Pointer 可以转换为 uintptr。

unsafe.Pointer 允许程序绕过类型安全检查读写任意内存,所以使用时应格外小心。

unsafe.Pointer 包含 6 个使用模式:

  • 使用 unsafe.Pointer 作为中转,将一个指针类型 *T 转换为另外一个指针类型 *T
  • unsafe.Pointer 转换为 uintptr(但不返回给 unsafe.Pointer),然后使用 uintptr 值。
  • unsafe.Pointer 转换为 uintptr,然后使用 uintptr 值进行算术运算,最后将运算结果 uintptr 值再转换为 unsafe.Pointer
  • 调用 syscall.Syscall 时,将 unsafe.Pointer 转换为 uintptr 值,作为参数传递。
  • reflect.Value.Pointerreflect.Value.UnsafeAddr 的返回结果 uintptr 值,从 uintptr 转换为 unsafe.Pointer
  • reflect.SliceHeaderreflect.StringHeader 值的 Data 字段与 unsafe.Pointer 进行转换。

03

unsafe.Pointer 和 uintptr 使用示例

因为 unsafe.Pointer 不支持运算,所以如果需要指针运算,还需要借助 uintptr 实现。以下示例是通过指针偏移对 struct 结构体中的字段进行指针运算操作,从而找到该字段的内存地址。

package main
import (
 "fmt"
 "unsafe"
)
type user struct {
 name string
 age uint
}
func main () {
 // 定义一个指针变量
 student := new(user)
 // user 结构体中的 name 字段是第一个字段,可以直接通过指针修改,不需要使用偏移
 studentName := (*string)(unsafe.Pointer(student))
 *studentName = "lucy"
 // user 结构体中的 age 字段不是第一个字段,所以需要使用偏移才能找到 age 字段的内存地址,修改值
 studentAge := (*uint)(unsafe.Pointer(uintptr(unsafe.Pointer(student)) + unsafe.Offsetof(student.age)))
 *studentAge = 18
 fmt.Println(*student)
}

阅读上面这段代码,我们使用 new 函数定义了一个 *user 类型的指针变量 student,然后使用 unsafe.Pointer*user 类型的指针变量 student 转换为 *string 类型的指针变量 studentName,然后修改 studentName 的值,实际上就是修改 name 字段的值。

因为 age 字段不是 user 结构体的第一个字段,所以需要先使用偏移量找到 age 字段的内存地址,具体操作步骤是:先将 student 指针变量通过 unsafe.Pointer 和 uintptr 转换为 uintptr,然后就可以使用函数 unsafe.Offsetof 计算出 age 字段的偏移量,该函数返回结果也是 uintptr 类型,因为 uintptr 支持运算,最后使用 + 运算符获取 age 字段的内存地址。

找到 age 字段的内存地址之后,还要使用 unsafe.Pointer 转换为 *uint 指针类型,才可以对该内存地址进行读写操作。

该示例主要是为了方便介绍 uintptr 指针运算,没有实际意义。

04

总结

本文介绍了非类型安全指针,它可用于指针类型之间互相转换,但是它绕开了类型安全检查,同时随着 Golang 的版本迭代,unsafe 包可能会失效,并且 unsafe 包不受 Go 1 兼容性准则的保护,所以我们应该谨慎使用。

推荐阅读:

Golang 语言中的指针介绍

参考资料:

https://golang.org/pkg/unsafe/#Pointer 


目录
相关文章
|
3月前
|
Go
Golang语言之管道channel快速入门篇
这篇文章是关于Go语言中管道(channel)的快速入门教程,涵盖了管道的基本使用、有缓冲和无缓冲管道的区别、管道的关闭、遍历、协程和管道的协同工作、单向通道的使用以及select多路复用的详细案例和解释。
144 4
Golang语言之管道channel快速入门篇
|
3月前
|
Go
Golang语言之gRPC程序设计示例
这篇文章是关于Golang语言使用gRPC进行程序设计的详细教程,涵盖了RPC协议的介绍、gRPC环境的搭建、Protocol Buffers的使用、gRPC服务的编写和通信示例。
118 3
Golang语言之gRPC程序设计示例
|
2月前
|
C语言 C++
【C语言】指针篇-一篇搞定不同类型指针变量-必读指南(3/5)
【C语言】指针篇-一篇搞定不同类型指针变量-必读指南(3/5)
|
3月前
|
Prometheus Cloud Native Go
Golang语言之Prometheus的日志模块使用案例
这篇文章是关于如何在Golang语言项目中使用Prometheus的日志模块的案例,包括源代码编写、编译和测试步骤。
80 3
Golang语言之Prometheus的日志模块使用案例
|
2月前
|
前端开发 中间件 Go
实践Golang语言N层应用架构
【10月更文挑战第2天】本文介绍了如何在Go语言中使用Gin框架实现N层体系结构,借鉴了J2EE平台的多层分布式应用程序模型。文章首先概述了N层体系结构的基本概念,接着详细列出了Go语言中对应的构件名称,包括前端框架(如Vue.js、React)、Gin的处理函数和中间件、依赖注入和配置管理、会话管理和ORM库(如gorm或ent)。最后,提供了具体的代码示例,展示了如何实现HTTP请求处理、会话管理和数据库操作。
38 0
|
3月前
|
存储 Go
Go: struct 结构体类型和指针【学习笔记记录】
本文是Go语言中struct结构体类型和指针的学习笔记,包括结构体的定义、成员访问、使用匿名字段,以及指针变量的声明使用、指针数组定义使用和函数传参修改值的方法。
|
1月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
120 13
|
2月前
|
C语言
无头链表二级指针方式实现(C语言描述)
本文介绍了如何在C语言中使用二级指针实现无头链表,并提供了创建节点、插入、删除、查找、销毁链表等操作的函数实现,以及一个示例程序来演示这些操作。
37 0
|
3月前
|
存储 人工智能 C语言
C语言程序设计核心详解 第八章 指针超详细讲解_指针变量_二维数组指针_指向字符串指针
本文详细讲解了C语言中的指针,包括指针变量的定义与引用、指向数组及字符串的指针变量等。首先介绍了指针变量的基本概念和定义格式,随后通过多个示例展示了如何使用指针变量来操作普通变量、数组和字符串。文章还深入探讨了指向函数的指针变量以及指针数组的概念,并解释了空指针的意义和使用场景。通过丰富的代码示例和图形化展示,帮助读者更好地理解和掌握C语言中的指针知识。
142 4
|
4月前
|
C语言
【C初阶——指针5】鹏哥C语言系列文章,基本语法知识全面讲解——指针(5)
【C初阶——指针5】鹏哥C语言系列文章,基本语法知识全面讲解——指针(5)

热门文章

最新文章