go: 无用之技 - 修改Private成员 孔乙已尚且知道回的四种写法,作为逗B程序员,怎么能不学点无用之技傍身? 本文是这些奇技淫巧的第一弹(也许没有第二弹),个人认识浅薄,如有错误,概不负责。:) 废话不说,今天来学习如何修改go结构体中的private(不可导出)值。 请看栗子 ## 准备结构体 ``` package changestruc func NewMyStr(a, b int) *MyStr { return &MyStr{a, b} } type MyStr struct { a int // 8 b int // 8 } ``` 假设有结构如上,在go中,小写的变量是不可导出的。 在main中,new出对象如下: ``` tp := changestruc.NewMyStr(12, 14) tp1 := changestruc.NewMyStr(13, 15) ``` 很显然,a,b两个值在后续,正常方法都不能修改。 本文中,将会在main中执行神奇的。 ``` tp.a = tp.a + tp.b ``` a 的值改为 a + b的值。 ## 法1: 汇编大法 简单讲一下原理,传入的结构体`MyStr`在内存中长这样: ``` type MyStr struct{ a int 8byte b int 8byte } ``` > go会按8byte做字节对齐,如果是uint8等结构会有影响,具体请搜索`go 汇编`。 当知道结构的内存布局,就可以用汇编来对它进行操作,具体请参考注释。 在main.go建同级文件`main.s` ``` #include "textflag.h" #include "funcdata.h" // func Unsafe_Mod(in *MyStr) TEXT ·Unsafe_Mod(SB), NOSPLIT, $0-8 // TEXT 定义一个函数, 8是argSize,因为传入的是指针,正好8byte MOVQ a+0(FP), AX //FP函数的帧指针,一般用来访问函数的参数和返回值,等同 AX = in MOVQ (AX), CX // 打括号是为了取值,因为传入的是指针,等同 CX = *AX[0,8] ADDQ 8(AX), CX // 同理,此句等同 CX += *AX[8,16] MOVQ CX, (AX) // *AX[0,8] = CX RET ``` ### 在main中声明函数 ``` package main // 申明函数,它在.s中实现 func Unsafe_Mod(in *changestruc.MyStr) func main() { tp := changestruc.NewMyStr(12, 14) tp1 := changestruc.NewMyStr(13, 15) Unsafe_Mod(tp) Unsafe_Mod(tp1) fmt.Printf("%v\n", tp) fmt.Printf("%v\n", tp1) } ``` 执行`go run main` (记得先 go mod init main) 输出: ``` &{26 14} &{28 15} ``` 大功告成了。 ## 法2 原理和法1类似,还是直接操作结构的内存,代码非常简单: ``` package main import ( "fmt" "main/changestruc" "unsafe" ) func main(){ tp3 := changestruc.NewMyStr(12, 14) ptr := unsafe.Pointer(tp3) // 找到结构体的地址 ptrToA := unsafe.Pointer(uintptr(ptr) + uintptr(0)) // a 的地址 ptrToIntA := (*int)(ptrToA) // a 的值 ptrToB := unsafe.Pointer(uintptr(ptr) + uintptr(8)) ptrToIntB := (*int)(ptrToB) *ptrToIntA = *ptrToIntA + *ptrToIntB // 直接修改值 fmt.Printf("%v\n", tp3) } ``` 输出: ``` &{26 14} ``` 今天,你学会了吗? ## FBI warning 上述这样的代码十分的机械,假设后续结构体发生了改变,则内存的寻址很可能错误。功能将不再正常。:) 来自 大脸猪 写于 2022-11-14 16:35 -- 更新于2022-11-14 17:04 -- 0 条评论