孔乙已尚且知道回的四种写法,作为逗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的值。
法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("%vn", tp)
fmt.Printf("%vn", tp1)
}
执行go run main
(记得先 go mod init main)
输出:
&{
}
&{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("%vn", tp3)
}
输出:
&{
}
今天,你学会了吗?
FBI warning
上述这样的代码十分的机械,假设后续结构体发生了改变,则内存的寻址很可能错误。功能将不再正常。:)