善用debugger学习
学习自曹大
之前曹大和雨痕大神的书都涉及过一些debugger的高级用法,但是自己还是没用起来,这次借助整理再熟悉下,以后就用起来。
var a = new(T) 和 var a = &T{} 这两种语法有区别么?
type T struct { } func main() { var a = new(T) var b = &T{} println(a, b) }
推荐是使用第二种,但这不是本随笔的重点。我们可以在gdb里看一下,其实不如go tool objdump/compile 清晰,这里只是展示有这个功能,至于分析,明显这两个就不是一个方式实现的,但是开启优化后就会成一样的了。
,(gdb) disassemble main.main Dump of assembler code for function main.main: => 0x00000000004581d0 <+0>: mov %fs:0xfffffffffffffff8,%rcx 0x00000000004581d9 <+9>: cmp 0x10(%rcx),%rsp 0x00000000004581dd <+13>: jbe 0x458240 <main.main+112> 0x00000000004581df <+15>: sub $0x30,%rsp 0x00000000004581e3 <+19>: mov %rbp,0x28(%rsp) 0x00000000004581e8 <+24>: lea 0x28(%rsp),%rbp 0x00000000004581ed <+29>: lea 0xf(%rsp),%rax
// new(T) 0x00000000004581f2 <+34>: mov %rax,0x18(%rsp) 0x00000000004581f7 <+39>: lea 0xf(%rsp),%rax
// &T{} 0x00000000004581fc <+44>: mov %rax,0x20(%rsp) 0x0000000000458201 <+49>: mov %rax,0x10(%rsp)
0x0000000000458206 <+54>: callq 0x42b010 <runtime.printlock> 0x000000000045820b <+59>: mov 0x18(%rsp),%rax 0x0000000000458210 <+64>: mov %rax,(%rsp) 0x0000000000458214 <+68>: callq 0x42b910 <runtime.printpointer> 0x0000000000458219 <+73>: callq 0x42b250 <runtime.printsp> 0x000000000045821e <+78>: mov 0x10(%rsp),%rax 0x0000000000458223 <+83>: mov %rax,(%rsp) 0x0000000000458227 <+87>: callq 0x42b910 <runtime.printpointer> 0x000000000045822c <+92>: callq 0x42b2a0 <runtime.printnl> 0x0000000000458231 <+97>: callq 0x42b090 <runtime.printunlock> 0x0000000000458236 <+102>: mov 0x28(%rsp),%rbp 0x000000000045823b <+107>: add $0x30,%rsp 0x000000000045823f <+111>: retq 0x0000000000458240 <+112>: callq 0x4519d0 <runtime.morestack_noctxt> 0x0000000000458245 <+117>: jmp 0x4581d0 <main.main> End of assembler dump.
查看 go 的 interface 的数据结构
package main import ( "fmt" "io" "os" ) var ( v interface{} r io.Reader f *os.File fn os.File ) func main() { fmt.Println(v == nil) fmt.Println(r == nil) fmt.Println(f == nil) v = r fmt.Println(v == nil) v = fn fmt.Println(v == nil) v = f fmt.Println(v == nil) r = f fmt.Println(r == nil) }
true true true true false false false
是不是很奇怪,为什么都是”nil“接口却最后不等于nil了,解释可以解释接口有两个指针不一样,但是这好像不足以服众,还是用debugger看一下吧。
(gdb) b main.main Breakpoint 1 at 0x491c70: file /root/main.go, line 16. (gdb) r Starting program: /root/main Breakpoint 1, main.main () at /root/main.go:16 16 func main() { (gdb) n 17 fmt.Println(v == nil) (gdb) n true 18 fmt.Println(r == nil) (gdb) n true 19 fmt.Println(f == nil) (gdb) n true 20 v = r (gdb) p r $1 = {tab = 0x0, data = 0x0} (gdb) p v $2 = {_type = 0x0, data = 0x0}
(gdb) p f
$3 = (os.File *) 0x0
在 golang 中空 interface 和非空 interface 在数据结构上是不同的。空 interface 就只有 runtime._type 和 void* 指针组成。而非空 interface 则是 runtime.itab 和 void* 指针组成。
26 r = f (gdb) n 27 fmt.Println(r == nil) (gdb) n false 28 }(gdb) p r $14 = {tab = 0x4dc480 <File,io.Reader>, data = 0x0} (gdb) p v $15 = {_type = 0x4baf00, data = 0x0} (gdb) p *v._type $16 = {size = 8, ptrdata = 8, hash = 871609668, tflag = 9 '\t', align = 8 '\b', fieldAlign = 8 '\b', kind = 54 '6', equal = {void (void *, void *, bool *)} 0x4baf00, gcdata = 0x4d8b08 "\001\002\003\004\005\006\a\b\t\n\v\f\r\016\017\020\022\024\025\026\030\031\033\034\036\037\"%&(,-256<BUXx\216\231\330\335\345\377", str = 7300, ptrToThis = 0} (gdb) p *r.tab $17 = {inter = 0x4a9400, _type = 0x4baf00, hash = 871609668, _ = "\000\000\000", fun = {4749008}} (gdb) p *r.tab.inter $18 = {typ = {size = 16, ptrdata = 16, hash = 3769182245, tflag = 7 '\a', align = 8 '\b', fieldAlign = 8 '\b', kind = 20 '\024', equal = {void (void *, void *, bool *)} 0x4a9400, gcdata = 0x4d8b09 "\002\003\004\005\006\a\b\t\n\v\f\r\016\017\020\022\024\025\026\030\031\033\034\036\037\"%&(,-256<BUXx\216\231\330\335\345\377", str = 11039, ptrToThis = 36672}, pkgpath = {bytes = 0x493064 ""}, mhdr = {array = 0x4a9460, len = 1, cap = 1}} (gdb)
如果你把一个非空 interface 类型的 nil 值的 interface 变量赋值给一个空 interface 类型的变量,那么就会得到一个非空类型的非空 interface 变量。
当然有一个区分的手段
func isNil(s Stringer) { defer func() { if e := recover(); e != nil { fmt.Printf("panic in isNil(), err:%v\n", e) } }() if s == nil { fmt.Printf("judge1:s(%v) == nil\n", s) return } if reflect.ValueOf(s).IsNil() { fmt.Printf("judge2:s(%v) reflect nil\n", s) return } fmt.Printf("s(%v) pass\n", s) } ———————————————— 版权声明:本文为CSDN博主「xiaohu50」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/xiaohu50/article/details/50318963
大致就是这么个debugger思路,当然还有很多玩法,直接左拐到曹大那学,我再重复没太大意义(虽然也重复了很多orz)。
最后一个小tip,如何让gdb输出的按一个结构体字段来换行,不是很懂为什么这不是默认的。
set print pretty on
(gdb) set print pretty on (gdb) p *r.tab.inter $19 = { typ = { size = 16, ptrdata = 16, hash = 3769182245, tflag = 7 '\a', align = 8 '\b', fieldAlign = 8 '\b', kind = 20 '\024', equal = {void (void *, void *, bool *)} 0x4a9400, gcdata = 0x4d8b09 "\002\003\004\005\006\a\b\t\n\v\f\r\016\017\020\022\024\025\026\030\031\033\034\036\037\"%&(,-256<BUXx\216\231\330\335\345\377", str = 11039, ptrToThis = 36672 }, pkgpath = { bytes = 0x493064 "" }, mhdr = { array = 0x4a9460, len = 1, cap = 1 } }
详情
https://wizardforcel.gitbooks.io/100-gdb-tips/set-print-pretty-on.html
end