Go语言普通指针unsafe.Pointer uintpt之间的关系及指针运算

 更新时间:2023年12月18日 10:59:15   作者:菜皮日记  
这篇文章主要为大家介绍了Go语言普通指针unsafe.Pointer uintpt之间的关系及指针运算示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

C 语言指针运算

指针运算就是对指针类型的变量做常规数学运算,例如加减操作,实现地址的偏移。指针运算在 C 语言中是原生支持的,可以直接在指针变量上做加减,例如:

#include <stdio.h>
const int MAX = 3;
int main ()
{
   int  var[] = {10, 100, 200};
   int  i, *ptr;
   /* 指针中的数组地址 */
   ptr = var;
   for ( i = 0; i < MAX; i++)
   {
      printf("存储地址:var[%d] = %p\n", i, ptr );
      printf("存储值:var[%d] = %d\n", i, *ptr );
      /* 直接对指针做++操作,指向下一个位置 */
      ptr++;
   }
   return 0;
}

结果

存储地址:var[0] = e4a298cc
存储值:var[0] = 10
存储地址:var[1] = e4a298d0
存储值:var[1] = 100
存储地址:var[2] = e4a298d4
存储值:var[2] = 200

C 语言指针运算犹如一把双刃剑,使用得当会起到事半功倍,有神之一手的效果,反之则会产生意想不到的 bug 而且很难排查。因为在做指针运算时是比较抽象的,具体偏移了多少之后指向到了哪里是非常不直观的,可能已经偏离了设想中的位置而没有发现,运行起来就会出现错误。

找出数组中最小的元素

例如这段 C 代码,找出数组中最小的元素:

#include <stdio.h>
int findMin(int *arr, int length) {
    int min = *arr;
    for (int i = 0; i <= length; i++) {  // 注意这里是 i <= length,而不是 i < length
        printf("i=%d v=%d\n", i, *(arr+i));
        if (*(arr + i) < min) {
            min = *(arr + i);
        }
    }
    return min;
}
int main() {
    int arr[] = {1, 2, 3, 4, 5};
    int length = sizeof(arr) / sizeof(arr[0]);
    printf("Min value is: %d\n", findMin(arr, length));
    return 0;
}

数组中最小的是 1,可结果却是 0:

i=0 v=1
i=1 v=2
i=2 v=3
i=3 v=4
i=4 v=5
i=5 v=0
Min value is: 0

这是由于在 findMin 函数中循环条件是 i ≤ length ,超出数组大小多循环了一次,实际上数组已经越界,而 C 语言的数组实际上就是指针,C 运行时认为这是在指针运算,所以不会报错,导致数组访问到了其他内存地址,最终得到了一个错误结果。

事实上有很多病毒和外挂的原理就是利用指针来访问并修改程序运行时内存数据来达到目的。例如游戏外挂可能会搜索和修改内存中的特定值,以改变玩家的生命值、金钱或其他游戏属性。通过指针运算,外挂可以直接访问这些内存位置并对其进行修改。而病毒可能使用指针运算来插入其自己的代码到一个运行中的程序,或者篡改程序的正常控制流,以达到其恶意目的。

在 C 语言之后的很多语言多多少少都对指针做了限制,例如 PHP 中的引用就可以看做是指针的简化版,而 Java 甚至干脆移除了指针。

Go 指针运算

对指针做加法会报错

在 Go 中默认的普通指针也是指代的是一个内存地址,值类似 0x140000ac008,但 Go 的普通指针不支持指针运算的,例如对指针做加法会报错:

a := 10
var p *int = &amp;a
p = p + 1

报错

invalid operation: p + 1 (mismatched types *int and untyped int)

但 Go 还是提供了一种直接操作指针的方式,就是 unsafe.Pointer 和 uintptr。

uintptr 是一个整型,可理解为是将内存地址转换成了一个整数,既然是一个整数,就可以对其做数值计算,实现指针地址的加减,也就是地址偏移,类似跟 C 语言中一样的效果。

而 unsafe.Pointer 是普通指针和 uintptr 之间的桥梁,通过 unsafe.Pointer 实现三者的相互转换。

*T <-> unsafe.Pointer <-> uintptr

先看看这三位都长什么样:

func main() {
    a := 10
    var b *int
    b = &a
    fmt.Printf("a is %T, a=%v\n", a, a)
    fmt.Printf("b is %T, b=%v\n", b, b)
    p := unsafe.Pointer(b)
    fmt.Printf("p is %T, p=%v\n", p, p)
    uptr := uintptr(p)
    fmt.Printf("uptr is %T, uptr=%v\n", uptr, uptr)
}

输出

a is int, a=10
b is *int, b=0x140000ae008
p is unsafe.Pointer, p=0x140000ae008
uptr is uintptr, uptr=1374390247432

举一个通过指针运算修改结构体的例子

type People struct {
    age    int32
    height int64
    name   string
}
people := &People{}
fmt.Println(people)
// 将 people 普通指针转成 unsafe.Pointer 再转为 uintptr
// 后面再加上 height 字段相对于结构体本身的偏移量,就得到了 height 的地址的 uintptr 值
// 再将 height 的 uintptr 值转成 unsafe.Pointer 赋值给 height 变量
// 所以现在 height 的类型是 unsafe.Pointer
height := unsafe.Pointer(uintptr(unsafe.Pointer(people)) + unsafe.Offsetof(people.height))
fmt.Printf("people addr is %v\n", unsafe.Pointer(people))
fmt.Printf("height is %T\n", height)
fmt.Printf("height addr is %v\n", height)
println("---")
// 使用类型转换,将 unsafe.Pointer 类型的 height 转换成 *int 指针
// 再通过最前面的 * 解引用,修改其值 身高2米26
*((*int)(height)) = 226
fmt.Println(people)
// 同样的操作可以修改年龄和名字
age := unsafe.Pointer(uintptr(unsafe.Pointer(people)) + unsafe.Offsetof(people.age))
*((*int)(age)) = 18
name := unsafe.Pointer(uintptr(unsafe.Pointer(people)) + unsafe.Offsetof(people.name))
*((*string)(name)) = "小明"
fmt.Println(people)

输出

people: &{0 0 }
people addr is 0x1400005e020
height is unsafe.Pointer
height addr is 0x1400005e028
---
people: &{0 226 }
people: &{18 226 }
people: &{18 226 小明}

通过指针转换将一个字节切片转成浮点数组

再看一个操作,通过指针转换,将一个字节切片转成浮点数组:

package main
import (
    "fmt"
    "unsafe"
)
func main() {
    // 假设我们有一个字节切片,并且我们知道它是由浮点数表示的
    byteSlice := []byte{0, 0, 0, 0, 0, 0, 240, 63} // 1.0 的 IEEE-754 表示
    // 使用 unsafe 把字节切片转换为浮点数切片
    floatSlice := (*[1]float64)(unsafe.Pointer(&byteSlice[0]))
    fmt.Println(floatSlice)
}

输出

&[1]

这个过程不需要 Go 的类型检查,绕过了很多流程,相对来说性能会更高。

所以大体上通过 unsafe.Pointer 的指针运算会应用在如下几个方面:

  • 性能优化: 当性能是关键因素时,unsafe 可以用来避免一些开销。例如,通过直接操作内存,可以避免切片或数组的额外分配和复制。
  • C 语言交互: 当使用 cgo 与 C 语言库交互时,unsafe 包通常用于转换类型和指针。
  • 自定义序列化/反序列化: 在自定义的序列化或反序列化逻辑中,unsafe 可以用于直接访问结构的内存布局,可以提高性能。
  • 实现非标准的数据结构: 有时,特定的问题需要非标准的数据结构。unsafe 允许你直接操作内存,可以用来实现一些 Go 的标准库中没有的数据结构。
  • 反射: 与反射结合时,unsafe 可以用于访问结构体的私有字段。

以上就是Go语言普通指针unsafe.Pointer uintpt之间的关系及指针运算的详细内容,更多关于Go普通指针unsafe.Pointer uintptr的资料请关注脚本之家其它相关文章!

相关文章

  • mayfly-go部署和使用详解

    mayfly-go部署和使用详解

    这篇文章主要介绍了mayfly-go部署和使用详解,此处部署基于CentOS7.4部署,结合实例代码图文给大家讲解的非常详细,需要的朋友可以参考下
    2022-09-09
  • 详解Go语言如何热重载和优雅地关闭程序

    详解Go语言如何热重载和优雅地关闭程序

    我们有时会因不同的目的去关闭服务,一种关闭服务是终止操作系统,一种关闭服务是用来更新配置,本文就来和大家简单讲讲这两种方法的实现吧
    2023-07-07
  • Golang的第一个程序-Hello World

    Golang的第一个程序-Hello World

    这篇文章主要介绍了第一个Go程序-Hello World,在编写第一个go程序之前,我们要将系统的环境变量配好,下面来看具体的编一过程吧,需要的小伙伴可以参考一下
    2022-01-01
  • Go语言Telnet回音服务器的实现

    Go语言Telnet回音服务器的实现

    这篇文章主要介绍了Go语言Telnet回音服务器的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-01-01
  • Go安装和环境配置图文教程

    Go安装和环境配置图文教程

    本文主要介绍了Go安装和环境配置图文教程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-04-04
  • Golang多线程排序实现快速高效地处理大规模数据

    Golang多线程排序实现快速高效地处理大规模数据

    Golang多线程排序是一种快速高效地处理大规模数据的方法,通过使用Golang的协程和通道,可以将排序任务分配到多个线程中并行处理,提高了排序的效率和速度,需要详细了解可以参考下文
    2023-05-05
  • 探索分析Go HTTP GET请求发送body

    探索分析Go HTTP GET请求发送body

    这篇文章主要为大家介绍了探索分析Go HTTP GET请求发送body,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-11-11
  • 关于Golang中range指针数据的坑详解

    关于Golang中range指针数据的坑详解

    这篇文章主要给大家介绍了关于Golang中range指针数据的坑的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-02-02
  • Golang的os标准库中常用函数的整理介绍

    Golang的os标准库中常用函数的整理介绍

    这篇文章主要介绍了Go语言的os标准库中常用函数,主要用来实现与操作系统的交互功能,需要的朋友可以参考下
    2015-10-10
  • 使用golang如何优雅的关机或重启操作示例

    使用golang如何优雅的关机或重启操作示例

    这篇文章主要为大家介绍了使用golang如何优雅的关机或重启操作示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步早日升职加薪
    2022-04-04

最新评论