直接调用
package main
type user struct {
age int
}
func (u user) say() {
println("hello")
}
func main() {
u := user{13}
u.say()
}
在上面的代码里,user 类型的方法say() 使用value 语义,编译后,观察生成的代码:
$ go build -gcflags="-S" say.go 2>&1 | less
"".user.say t=1 size=86 args=0x8 locals=0x18
.................................
0x001d 00029 (/tmp/test/say.go:9) CALL runtime.printlock(SB)
0x0022 00034 (/tmp/test/say.go:14) LEAQ go.string."hello"(SB), AX
0x0029 00041 (/tmp/test/say.go:9) MOVQ AX, (SP)
0x002d 00045 (/tmp/test/say.go:9) MOVQ $5, 8(SP)
0x0036 00054 (/tmp/test/say.go:9) PCDATA $0, $0
0x0036 00054 (/tmp/test/say.go:9) CALL runtime.printstring(SB)
0x003b 00059 (/tmp/test/say.go:9) PCDATA $0, $0
0x003b 00059 (/tmp/test/say.go:9) CALL runtime.printnl(SB)
0x0040 00064 (/tmp/test/say.go:9) PCDATA $0, $0
0x0040 00064 (/tmp/test/say.go:9) CALL runtime.printunlock(SB)
..........................
"".(*user).say t=1 dupok size=194 args=0x8 locals=0x38
.....................................
0x0041 00065 (<autogenerated>:1) CALL runtime.printlock(SB)
0x0046 00070 (<autogenerated>:1) LEAQ go.string."hello"(SB), AX
0x004d 00077 (<autogenerated>:1) MOVQ AX, (SP)
0x0051 00081 (<autogenerated>:1) MOVQ $5, 8(SP)
0x005a 00090 (<autogenerated>:1) PCDATA $0, $1
0x005a 00090 (<autogenerated>:1) CALL runtime.printstring(SB)
0x005f 00095 (<autogenerated>:1) PCDATA $0, $1
0x005f 00095 (<autogenerated>:1) CALL runtime.printnl(SB)
0x0064 00100 (<autogenerated>:1) PCDATA $0, $1
0x0064 00100 (<autogenerated>:1) CALL runtime.printunlock(SB)
..........................
上面可以看到, 编译器生成了多余的方法, (*user).say ,它的功能貌似包含了user.say 的功能, 观察最后生成的二进制文件:
$ go tool objdump say |grep ' main\.'
TEXT main.main(SB) /tmp/test/say.go
say.go:11 0x44d604 ebaa JMP main.main(SB)
TEXT main.init(SB) /tmp/test/say.go
say.go:15 0x44d65d ebb1 JMP main.init(SB)
刚才编译时生成的user.say, (*user).say 哪去了?优化掉了?
$ go build -gcflags="-m" say.go
./say.go:7: can inline user.say
./say.go:11: can inline main
./say.go:13: inlining call to user.say
<autogenerated>:1: inlining call to user.say
<autogenerated>:1: (*user).say .this does not escape
最后,应该是调用了user.say 方法,只是被内联进main() ,最终二进制里看不见了 。 关掉inline 看看怎么样?
$go build -gcflags="-S -l" say.go 2>&1 | less
"".(*user).say t=1 dupok size=166 args=0x8 locals=0x38
0x0048 00072 (<autogenerated>:1) CALL "".user.say(SB)
这里(*user).say 调用 user.say, 为什么生成不使用的(*user).say ?
user 的say方法 改成pointer 语义:
package main
type user struct {
age int
}
func (u *user) say() {
println("hello")
}
func main() {
u := &user{13}
u.say()
}
观察编译结果:
$ go build -gcflags="-S" say.go 2>&1 | less
"".(*user).say t=1 size=86 args=0x8 locals=0x18
0x0000 00000 (/tmp/test/say.go:7) TEXT "".(*user).say(SB), $24-8
0x0000 00000 (/tmp/test/say.go:7) MOVQ (TLS), CX
0x0009 00009 (/tmp/test/say.go:7) CMPQ SP, 16(CX)
...................
只有(*user).say , 没有user.say, 观察二进制:
$ go tool objdump say | grep ' main\.'
TEXT main.main(SB) /tmp/test/say.go
say.go:11 0x44d616 eb98 JMP main.main(SB)
TEXT main.init(SB) /tmp/test/say.go
say.go:15 0x44d66d ebb1 JMP main.init(SB)
观察优化分析:
$go build -gcflags="-m" say.go
./say.go:7: can inline (*user).say
./say.go:11: can inline main
./say.go:13: inlining call to (*user).say
./say.go:7: (*user).say u does not escape
./say.go:12: main &user literal does not escape
使用value 语义定义方法,编译器就暗自生成多余的带有pointer 语义的方法,实现同样的功能。 如果用户已经定义pointer 语义方法,就不再生成value 语义的方法。 仿佛编译器就是要确保类型上要有pointer 方法,猜测原因: golang 的interface 是隐式满足,只要某个接口约束的方法集,某个类型全部都有,就自动满足, 保不准别的代码定义了类似:
type speaker interface {
say()
}
而接口内部表达是:
type iface struct {
tab *itab
data unsafe.Pointer
}
包含两个pointer, 其中data 指向 接口实现者: method receiver ,接口内部实现使用pointer 语义。 现在的user 类型怎么去满足speaker ,为了内存布局的兼容, user 类型的say() 方法也需要绑定在*user上。
间接调用
根据上面老外的链接,autogenerated method ,在接口上迂回调用时,才能用上。
package main
type speaker interface {
say()
}
type user struct {
age int
}
func (u user) say() {
println("hello")
}
func main() {
var s speaker
u := user{13}
s = u
s.say()
}
二进制文件dump:
$ go tool objdump say |grep ' main\.'
TEXT main.user.say(SB) /tmp/test/say.go
say.go:11 0x44d6b4 ebaa JMP main.user.say(SB)
TEXT main.main(SB) /tmp/test/say.go
say.go:15 0x44d723 eb9b JMP main.main(SB)
TEXT main.init(SB) /tmp/test/say.go
say.go:21 0x44d77d ebb1 JMP main.init(SB)
TEXT main.(*user).say(SB) <autogenerated>
<autogenerated>:1 0x44d83d e93effffff JMP main.(*user).say(SB)
autogenerated 的(*user).say 进入了二进制文件, 大小 964404 . 在println后加上panic() 调用,可以观察到 s.say() ->(*user).say -> user.say 的调用链.
package main
type speaker interface {
say()
}
type user struct {
age int
}
func (u *user) say() {
println("hello")
}
func main() {
var s speaker
u := &user{13}
s = u
s.say()
}
二进制文件dump:
$ go tool objdump say |grep ' main\.'
TEXT main.(*user).say(SB) /tmp/test/say.go
say.go:11 0x44d604 ebaa JMP main.(*user).say(SB)
TEXT main.main(SB) /tmp/test/say.go
say.go:15 0x44d665 eba9 JMP main.main(SB)
TEXT main.init(SB) /tmp/test/say.go
say.go:21 0x44d6bd ebb1 JMP main.init(SB)
二进制文件大小960231, 调用链: s.say() ->(*user).say