go 函数和闭包
1 函数名到底是个啥
main.go
package main
import (
"fmt"
"unsafe"
)
func add(a, b int) int {
sum := 0
sum = a + b
return sum
}
func main() {
println(add)
addFuncVar := add
println(addFuncVar)
// 这里由于println() 和 fmt.Printf("%p") 打印 函数名, 处理上不同, 为了看函数名所代表直接内容
addFuncVarVal := *(*uint64)(unsafe.Pointer(&addFuncVar))
// 我们得到addFunc变量的值 与上面 println(add) 相同
fmt.Printf("函数名所表示的值: 0x%x\n", addFuncVarVal)
// 我们将这个值 作为内存地址, 将它转换为指针,找到它指向的内容
addFuncCodeRealAddr := *(*uint64)(unsafe.Pointer(uintptr(addFuncVarVal)))
fmt.Printf("把函数名表示的值作为地址,它指向的那块内存的内容 (即代码地址): 0x%x\n", addFuncCodeRealAddr)
// fmt.Printf 打印 函数名 地址 结果是 真正代码的地址
fmt.Printf("fmt.Printf(\"%%p\")打印的值: %p\n", add)
add(1, 2)
println(9999)
}./main 执行结果
dlv调试查看 add代码实际地址
直接查看汇编代码来看add代码内存地址
你可以继续用代码获取 add 编译后的指令,与上面的 4883ec18 是否一致 (注意左边是低地址的内容)
上面的操作可以说用代码来实现获取”代码内容”
结论
- 函数名是个二级指针!
- 带着为什么函数名是个二级指针的疑问,我们继续看闭包
2 闭包
Caution
闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合.
换而言之,闭包让开发者可以从内部函数访问外部函数的作用域
请看高亮的注释的思考
package main
// 外部函数
func outerFunc() func() int {
var x int=1
// 内部函数
innerFunc := func() int {
// x 属于 outerFunc的
// 如果x在栈上,执行完 outerFunc ,x 那么作为它的局部变量会被释放,这肯定不行
// 因为 我们这个 return的 innerFunc 还会操作x,所以逃逸到堆上
x = x + 1
return x
}
// 既然 innerFunc 会操作到x
// 那么 innerFunc 这个东西所在的内存除了它自己函数的信息(函数体)外
// 还必定含有x 这个数据的相关信息
/*
问: 为什么不直接将x 这个外部变量的信息编译到函数体内呢?
答: 如果直接在函数体内, 那么堆上申请内存,初始化x 的操作都在函数内,
然而这样你调用完函数后,没有任何东西再引用这个x了,x必定释放了,这与闭包本身的目的不一致了.
而且你重新调用一次函数, 重新初始化了,不行
*/
// 把x作为innerFunc的参数? 这不行,首先这不跟传参的一样吗, 还是值传递不成.
// 值传递? 搞成传递指针 好像行得通? (这里只是一种思考)
// 闭包的调用者堆上申请x的内存,(注:不同的闭包对外部变量的使用情况可能会有不同)
// 然后在自己的栈帧上存地址,将地址传递给闭包函数
return innerFunc
}
func main() {
// 创建一个闭包函数
closure := outerFunc()
// 调用闭包函数
closure()
}通过汇编来看看实际的情况
会看到当前这个闭包的结构
代码验证结构
package main
import (
"unsafe"
)
func outerFunc() func() int {
var x int = 1
innerFunc := func() int {
x = x + 1
return x
}
return innerFunc
}
//go:linkname inheap runtime.inheap
func inheap(arg uintptr) bool
func inHeapOrStack(b uintptr) bool
func main() {
type closureStruct struct {
F uintptr // 闭包的函数的地址
x *int // 闭包的外部变量 这里是指针
}
// 创建一个闭包函数
closure := outerFunc()
closureVal := *(*uint64)(unsafe.Pointer(&closure))
println(inheap(uintptr(closureVal))) //堆上
println(closure, closureVal) // 0xc000090000
closureCodeRealAddrAndVar := *(*closureStruct)(unsafe.Pointer(uintptr(closureVal)))
println(*closureCodeRealAddrAndVar.x) // 1
println(closureCodeRealAddrAndVar.x) // 0xc00008e000 堆上
println(closureCodeRealAddrAndVar.F) // 闭包函数的地址 0x1057820 只读
// 调用闭包函数
println(closure()) // 2
println(closure()) // 3
closure2 := outerFunc()
closureVal2 := *(*uint64)(unsafe.Pointer(&closure2))
closureCodeRealAddrAndVar2 := *(*closureStruct)(unsafe.Pointer(uintptr(closureVal2)))
// 函数地址一样
println(closureCodeRealAddrAndVar2.F == closureCodeRealAddrAndVar.F) // true
}我们看看下面这个例子的结构
验证一下闭包结构
package main
import (
"fmt"
"unsafe"
)
func outerFunc(x int) func() int {
innerFunc := func() int {
return x
}
return innerFunc
}
func main() {
// 创建一个闭包函数
closure := outerFunc(10)
// 这个是针对 上面的闭包结构
type closureStruct struct {
F uintptr
x int //直接存就行了. 因为没做修改等操作,就打印的需求
}
closureVal := *(*uint64)(unsafe.Pointer(&closure))
println(closureVal)
closureCodeRealAddrAndVar := *(*closureStruct)(unsafe.Pointer(uintptr(closureVal)))
println(closureCodeRealAddrAndVar.x) //10
println(closureCodeRealAddrAndVar.F) //闭包函数的地址
// 调用闭包函数
closure()
}3 println & fmt.Print
Tip
todo