孔乙已尚且知道回的四种写法,作为逗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(, ) tp1 := changestruc.NewMyStr(13, 15) 很显然,a,b两个值在后续,正常方法都不能修改。
本文中,将会在main中执行神奇的。
tp.a = tp.a + tp.b a 的值改为 a + b的值。
简单讲一下原理,传入的结构体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 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)
输出:
&{ } &{28 15} 大功告成了。
原理和法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) } 输出:
&{ } 今天,你学会了吗?
上述这样的代码十分的机械,假设后续结构体发生了改变,则内存的寻址很可能错误。功能将不再正常。:)