Back to Top

golang的一些特性分析笔记

反汇编相关

在线反汇编:

# -N 禁止优化
# -l 禁止内联
# -S 打印汇编
# -m 打印编译优化策略(包括逃逸情况和函数是否内联,以及变量分配在堆或栈)
# -m=1 -m后面指定的数字越大越详细

go tool compile -m -l main.go

go tool compile -N -l -S main.go
go tool compile -S main.go

go tool compile -N -l main.go && go tool objdump main.o 
go tool compile main.go && go tool objdump main.o


# 使用objdump会更容易看linux平台的代码
go build -gcflags "all=-l"
objdump -Sd XXX

string

在部分内置调用上, []byte可以无损的转化为string, 而无需调用runtime.slicebytetostring

目前已知的调用有:

  • map查找(备注, map赋值不可以)
var b []byte
var m map[string]string
s := m[string(b)]
  • 字符串拼接
var b []byte
s := "ABC" + string(b)
  • 字符串比较
var b []byte
bs := "ABC" == string(b)

map

for k := range m {
    delete(m, k)
}
  • range map 可以一边遍历一边删除
  • 以上代码会被优化为runtime.mapclear

range

源码: cmd/compile/internal/walk/range.go

range会再编译器通过插入代码的方式实现.

interface{}

type eface struct {
    _type *_type
    data  unsafe.Pointer
}
  • interface{} 是一个结构体, 由类型和指针组成
  • 如果赋值的元素是结构体(不是结构体指针), 则会进行复制结构体, 并将复制的结构体的指针赋值给data

如下面的例子, 修改a和c都不会影响b

var a = item {
    name: "aaa",
}
var b interface{} = a
var c = b.(item)
a.name = "bbb"
c.name = "ccc"
fmt.Println(b.(item).name)
  • interface{}包含类型和值, 当interface不是nil的时候, 存在类型不为nil, 值为nil的情况, 尤其在error返回的时候要注意, error一定要返回nil, 而不要返回某个类型的nil. 如下面的例子, 输出为 “a is nil” “b is not nil”
var a *item = nil
if a == nil {
    fmt.Println(a, "a is nil")
} else {
    fmt.Println(a, "a is not nil")
}
var b interface{} = a
if b == nil {
    fmt.Println(b, "b is nil")
} else {
    fmt.Println(b, "b is not nil")
}

这个实现原理是interface{}的判空只看类型是不是nil, 不看value, 下面的代码中, type被放在第一个参数rax, 类型被放在了第二个参数rbx

func worker(i interface{}) {
  488100:	49 3b 66 10          	cmp    0x10(%r14),%rsp
  488104:	0f 86 aa 00 00 00    	jbe    4881b4 <main.worker+0xb4>
  48810a:	48 83 ec 30          	sub    $0x30,%rsp
  48810e:	48 89 6c 24 28       	mov    %rbp,0x28(%rsp)
  488113:	48 8d 6c 24 28       	lea    0x28(%rsp),%rbp
  488118:	48 89 44 24 38       	mov    %rax,0x38(%rsp)
  48811d:	48 89 5c 24 40       	mov    %rbx,0x40(%rsp)
	if i == nil {
  488122:	48 85 c0             	test   %rax,%rax
  488125:	74 43                	je     48816a <main.worker+0x6a>

逃逸分析

func print0(v interface{})  {
    // v does not escape
}

func print1(v interface{}) int {
    // v does not escape
    i := len(v.(string))
    return i
}

func print3(v interface{}) reflect.Type {
    // leaking param: v
    rv := reflect.ValueOf(v)
    return rv.Type()
}

func print()  {
    s1 := ""
    s2 := ""
    s3 := ""
    s4 := ""
    print0(s1) // s1 does not escape
    print1(s2) // s2 does not escape
    print1(s3) // s3 does not escape
    fmt.Println(s4) // s4 escapes to heap
}

reflect.ValueOf 会造成leaking param, 可以参考一些golang的知识点中的反射部分.

defer函数修改return的值, 需要使用具名return

func f1() (i int) { // return 2
    i = 1
    defer func() { 
        i = 2
    }()
    return i 
}
func f2 () int { // return 1
    i := 1
    defer func() { 
        i = 2
    }()
    return i 
}

简单说一下这个的原理, 可以理解为函数堆栈上有一个返回值变量的空间, 当该变量匿名时, 执行return N时, 会给该变量赋值, 当defer执行后, 会将该变量再赋值给eax(eax是第一个返回值)

简单来说,这个原理可以理解为函数堆栈上有一个用于存储返回值的变量空间。 当该变量未命名时,执行return N语句时,会将该变量赋值为N。 紧接着执行defer语句后,该变量的值会被再次赋值给eax(eax是第一个返回值)。

000000000045f220 <main.f1>:
	return i                                                                ; 因为返回的是具名的返回值变量, 所以不需要赋值给返回值变量
  45f28b:	e8 b0 2b fd ff       	callq  431e40 <runtime.deferreturn>
  45f290:	48 8b 44 24 08       	mov    0x8(%rsp),%rax                   ; 将具名的返回值变量赋值给%rax
  45f295:	48 8b 6c 24 70       	mov    0x70(%rsp),%rbp
  45f29a:	48 83 c4 78          	add    $0x78,%rsp
  45f29e:	c3                   	retq 

000000000045f300 <main.f2>:
	return i
  45f36b:	48 8b 44 24 10       	mov    0x10(%rsp),%rax          
  45f370:	48 89 44 24 08       	mov    %rax,0x8(%rsp)                   ; 赋值给匿名的返回值变量
  45f375:	e8 c6 2a fd ff       	callq  431e40 <runtime.deferreturn>
  45f37a:	48 8b 44 24 08       	mov    0x8(%rsp),%rax                   ; 将匿名的返回值变量赋值给%rax
  45f37f:	48 8b 6c 24 78       	mov    0x78(%rsp),%rbp
  45f384:	48 83 ec 80          	sub    $0xffffffffffffff80,%rsp
  45f388:	c3                   	retq   

package list模式的几个坑

经常使用 go 命令中的 ... 参数,以便能够对多个 package 执行命令。例如:

  • go test ./...
  • go build ./cmd/...
  • go clean ./cmd/...
  • go install ./cmd/...

最近因为... 参数的问题被坑了两次,由于网上对应的文档较少,我也没有时间去做更多的分析,所以仅简单的记录:

go test ./... 编译失败时提示不明显

当使用go test ./...时,其中某个模块编译失败,进程会返回-2,编译错误信息在stderr, 如果有-v就更不明显了, 尽量要分离stderr和stdout

go build -buildvcs=false ./cmd/... 不传递参数

go build -buildvcs=false ./cmd/... 并不会将 -buildvcs=false 参数传递给每个 package,为了解决这个问题,我们改用以下代码:

export GOFLAGS="${GOFLAGS} -buildvcs=false" 
go build ./cmd/...

stderr重定向问题

有一些panicunrecoverable的, 例如:

func doubleUnlock() {
    var mu sync.Mutex
    mu.Lock()
    mu.Unlock()
    mu.Unlock()
}

在这种情况下,panic 产生的错误信息输出到 stderr 中。

原本计划使用 os.Stderr = filestderr 重定向到文件中,但发现 print 函数(panic 输出错误信息的函数)会绕过 Golang 的 os.Stderr,因此需要使用 syscall.Dup2(int(file.Fd()),int(os.stderr.Fd())) 来实现 stderr 的重定向,或在父进程中处理错误输出。

参数和返回值

Go internal ABI specification

golang 函数之间的调用传递参数和结果是通过使用 stack 和 registers 的联合方式。

amd64 架构使用以下 9 个寄存器序列来存储整数参数和结果: RAX, RBX, RCX, RDI, RSI, R8, R9, R10, R11

func arguments(i1 int8, i2 int16, f1 float32 , f2 float64, i3 int32, i4 int64)  (int8, int16, float32, float64, int32, int64) {
	return 1, 2, 3, 4, 5, 6
}

arguments(6, 5, 4, 3, 2, 1)
func arguments(i1 int8, i2 int16, f1 float32 , f2 float64, i3 int32, i4 int64)  (int8, int16, float32, float64, int32, int64) {
  49af40:	48 83 ec 28          	sub    $0x28,%rsp
  49af44:	48 89 6c 24 20       	mov    %rbp,0x20(%rsp)
  49af49:	48 8d 6c 24 20       	lea    0x20(%rsp),%rbp
  49af4e:	88 44 24 30          	mov    %al,0x30(%rsp)
  49af52:	66 89 5c 24 32       	mov    %bx,0x32(%rsp)
  49af57:	f3 0f 11 44 24 34    	movss  %xmm0,0x34(%rsp)
  49af5d:	f2 0f 11 4c 24 38    	movsd  %xmm1,0x38(%rsp)
  49af63:	89 4c 24 40          	mov    %ecx,0x40(%rsp)
  49af67:	48 89 7c 24 48       	mov    %rdi,0x48(%rsp)
  49af6c:	c6 44 24 05 00       	movb   $0x0,0x5(%rsp)
  49af71:	66 c7 44 24 06 00 00 	movw   $0x0,0x6(%rsp)
  49af78:	0f 57 d2             	xorps  %xmm2,%xmm2
  49af7b:	f3 0f 11 54 24 0c    	movss  %xmm2,0xc(%rsp)
  49af81:	0f 57 d2             	xorps  %xmm2,%xmm2
  49af84:	f2 0f 11 54 24 18    	movsd  %xmm2,0x18(%rsp)
  49af8a:	c7 44 24 08 00 00 00 	movl   $0x0,0x8(%rsp)
  49af91:	00 
  49af92:	48 c7 44 24 10 00 00 	movq   $0x0,0x10(%rsp)
  49af99:	00 00 
	return 1, 2, 3, 4, 5, 6
  49af9b:	c6 44 24 05 01       	movb   $0x1,0x5(%rsp)
  49afa0:	66 c7 44 24 06 02 00 	movw   $0x2,0x6(%rsp)
  49afa7:	f3 0f 10 15 49 46 02 	movss  0x24649(%rip),%xmm2        # 4bf5f8 <$f32.40400000>
  49afae:	00 
  49afaf:	f3 0f 11 54 24 0c    	movss  %xmm2,0xc(%rsp)
  49afb5:	f2 0f 10 15 9b 46 02 	movsd  0x2469b(%rip),%xmm2        # 4bf658 <$f64.4010000000000000>
  49afbc:	00 
  49afbd:	f2 0f 11 54 24 18    	movsd  %xmm2,0x18(%rsp)
  49afc3:	c7 44 24 08 05 00 00 	movl   $0x5,0x8(%rsp)
  49afca:	00 
  49afcb:	48 c7 44 24 10 06 00 	movq   $0x6,0x10(%rsp)
  49afd2:	00 00 
  49afd4:	0f b6 44 24 05       	movzbl 0x5(%rsp),%eax
  49afd9:	0f b7 5c 24 06       	movzwl 0x6(%rsp),%ebx
  49afde:	8b 4c 24 08          	mov    0x8(%rsp),%ecx
  49afe2:	f3 0f 10 44 24 0c    	movss  0xc(%rsp),%xmm0
  49afe8:	f2 0f 10 4c 24 18    	movsd  0x18(%rsp),%xmm1
  49afee:	bf 06 00 00 00       	mov    $0x6,%edi
  49aff3:	48 8b 6c 24 20       	mov    0x20(%rsp),%rbp
  49aff8:	48 83 c4 28          	add    $0x28,%rsp
  49affc:	c3                   	retq   
  49affd:	cc                   	int3   
  49affe:	cc                   	int3   
  49afff:	cc                   	int3   

  (6, 5, 4, 3, 2, 1)
49b486:	b8 06 00 00 00       	mov    $0x6,%eax
49b48b:	bb 05 00 00 00       	mov    $0x5,%ebx
49b490:	f3 0f 10 05 64 41 02 	movss  0x24164(%rip),%xmm0        # 4bf5fc <$f32.40800000>
49b497:	00 
49b498:	f2 0f 10 0d b0 41 02 	movsd  0x241b0(%rip),%xmm1        # 4bf650 <$f64.4008000000000000>
49b49f:	00 
49b4a0:	b9 02 00 00 00       	mov    $0x2,%ecx
49b4a5:	bf 01 00 00 00       	mov    $0x1,%edi
49b4aa:	e8 91 fa ff ff       	callq  49af40 <main.arguments>

slice, string, interface{} 传参

slice 会用三个寄存器传递 string 会用两个寄存器传递 interface{} 会用两个寄存器传递(第一个是type, 第二个是value指针)

本文链接, 未经许可,禁止转载