github.com/code-reading/golang@v0.0.0-20220303082512-ba5bc0e589a3/coding/unsafe/offsetof.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"unsafe"
     6  )
     7  
     8  type Programmer struct {
     9  	name     string
    10  	language string
    11  	age      int
    12  }
    13  
    14  type Modified struct {
    15  	age      int
    16  	name     string
    17  	language string
    18  }
    19  
    20  func main() {
    21  	m := Modified{10, "stefno", "go"}
    22  	fmt.Println(m)
    23  	fmt.Println(unsafe.Offsetof(m.age))      // 0 age在结构体user中的偏移量,也是结构体的地址
    24  	fmt.Println(unsafe.Offsetof(m.name))     // 8
    25  	fmt.Println(unsafe.Offsetof(m.language)) // 24
    26  
    27  	p := Programmer{"stefno", "go", 10}
    28  	fmt.Println(p)
    29  	fmt.Println(unsafe.Offsetof(p.name))     // 0  name在结构体user中的偏移量,也是结构体的地址
    30  	fmt.Println(unsafe.Offsetof(p.language)) // 16
    31  	fmt.Println(unsafe.Offsetof(p.age))      // 32
    32  
    33  	// 获取结构体的第一个字段地址
    34  	name := (*string)(unsafe.Pointer(&p))
    35  	*name = "update name " // 更新地址下的内容
    36  	// 获取结构体第二个字段地址 这里需要用到偏移
    37  	// 注意,这里先通过unsafe.Pointer(&p) 获取结构体第一个字段地址
    38  	// 但是 Pointer 不能计算, 所以需要先转换为 uintptr
    39  	// 再加上  unsafe.Offsetof(p.language) 指定字段的偏移
    40  	// unsafe.Pointer 可以理解返回的是一个*void 空指针
    41  	// golang 是强类型语言, 使用时必须确定类型
    42  	// 那么就需要进行类型强转, 因为结构体字段是string
    43  	// 所以强转指针 *string
    44  	lang := (*string)(unsafe.Pointer(uintptr(unsafe.Pointer(&p)) + unsafe.Offsetof(p.language)))
    45  	// 注意 lang 是指针
    46  	// *lang  才是指针所指地址赋值
    47  	*lang = "Golang"
    48  	fmt.Println(p)
    49  	//下面修改 age 的值
    50  	// age的偏移, 是要计算上前面的字段的地址长度的
    51  	// 第一种是继续使用 unsafe.Offsetof 算偏移, 这个是直接算当前字段离结构体起始位置的偏移,已经包含了
    52  	// 前面字段的长度, 不需要额外添加了
    53  	// 所以age的起始地址不是
    54  	// uintptr(unsafe.Pointer(&p)) +unsafe.Offsetof(p.language) + unsafe.Offsetof(p.age)
    55  	// 而是 unsafe.Pointer(uintptr(unsafe.Pointer(&p)) + unsafe.Offsetof(p.age)
    56  	age := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&p)) + unsafe.Offsetof(p.age)))
    57  	*age = 18
    58  	fmt.Println(p)
    59  	// 先后端结构体的地址,
    60  	// unsafe.Sizeof(p.name) + unsafe.Sizeof(language) 得到name 和 languange 占用的字节数
    61  	// 结构体地址+ 占用的字节数就得到了 p.age 的地址, 转成int 指针
    62  	pAge := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&p)) + unsafe.Sizeof(p.language) + unsafe.Sizeof(p.name)))
    63  	*pAge = 20
    64  	fmt.Println(p)
    65  	// 第三种也是Sizeof 算大小, 但是传入的是相应字段类型的零值
    66  	sage := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&p)) +
    67  		unsafe.Sizeof(string("")) + // 这里p.name 的占字节数
    68  		unsafe.Sizeof(string("")))) // 这里是 p.language 的占字节数
    69  	*sage = 30
    70  	fmt.Println(p)
    71  }
    72  
    73  /*
    74  output:
    75  {10 stefno go}
    76  0
    77  8
    78  24
    79  {stefno go 10}
    80  0
    81  16
    82  32
    83  {update name  Golang 10}
    84  {update name  Golang 18}
    85  {update name  Golang 20}
    86  {update name  Golang 30}
    87  */