孔乙已尚且知道回的四种写法,作为逗B程序员,怎么能不学点无用之技傍身?
本文是这些奇技淫巧的第一弹(也许没有第二弹),个人认识浅薄,如有错误,概不负责。:)
废话不说,今天来学习如何修改go结构体中的private(不可导出)值。
请看栗子
准备结构体
package changestrucfunc NewMyStr(a, b int)*MyStr{-
return&MyStr{a, b} }type MyStrstruct{a int// 8b 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的值。
法1: 汇编大法
简单讲一下原理,传入的结构体MyStr在内存中长这样:
type MyStrstruct{a int8byteb int8byte}
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,因为传入的是指针,正好8byteMOVQ a 0(FP), AX //FP函数的帧指针,一般用来访问函数的参数和返回值,等同 AX = inMOVQ (AX), CX // 打括号是为了取值,因为传入的是指针,等同 CX = *AX[0,8]ADDQ 8(AX), CX // 同理,此句等同 CX = *AX[8,16]MOVQ CX,(AX)// *AX[0,8] = CXRET
在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("%vn", tp)fmt.Printf("%vn", tp1)}
执行go run main (记得先 go mod init main)
输出:
&{}&{2815}
大功告成了。
法2
原理和法1类似,还是直接操作结构的内存,代码非常简单:
package mainimport(-
"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("%vn", tp3)}
输出:
&{}
今天,你学会了吗?
FBI warning
上述这样的代码十分的机械,假设后续结构体发生了改变,则内存的寻址很可能错误。功能将不再正常。:)


