Go语言其程序符号重命名
发布时间:2021-11-03 10:30:29  所属栏目:语言  来源:互联网 
            导读:Go程序源代码中,关键字、接口、类型、常量、变量、方法、函数、字段、标签(label)等等的名称都可以称为符号。 Go可执行程序中,符号表主要包含两种类型的符号: 数据对象(Data object) 函数(Function) 一般情况下(不是绝对的),在源代码编译为可执行程序的
                
                
                
            | Go程序源代码中,关键字、接口、类型、常量、变量、方法、函数、字段、标签(label)等等的名称都可以称为符号。
	 
	Go可执行程序中,符号表主要包含两种类型的符号:
	 
	数据对象(Data object)
	函数(Function)
	一般情况下(不是绝对的),在源代码编译为可执行程序的过程中,
	 
	关键字、局部变量、标签会转变为指令、数据或者消失,而不再是符号
	接口、类型、全局常量会被保存为不可变数据,而不再是符号
	函数、方法、全局变量、全局常量会被重命名,保存在符号表中
	本文主要总结了函数、方法、全局变量在编译过程中通用的重命名规则,不讨论类似内联优化、闭包、非空接口、编译器生成等复杂的情况。
	 
	规则
	Go 1.18版本之前符号重命名常见规则列表如下:
	 
	包名.变量名
	包名.函数名
	包名.函数名.funcN
	包名.函数名.funcN.N
	包名.类型.函数名
	包名.类型.函数名.funcN
	包名.类型.函数名.funcN.N
	包名.(*类型).函数名
	包名.(*类型).函数名.funcN
	包名.(*类型).函数名.funcN.N
	模块名/包名.变量名
	模块名/包名.函数名
	模块名/包名.函数名.funcN
	模块名/包名.函数名.funcN.N
	模块名/包名.类型.函数名
	模块名/包名.类型.函数名.funcN
	模块名/包名.类型.函数名.funcN.N
	模块名/包名.(*类型).函数名
	模块名/包名.(*类型).函数名.funcN
	模块名/包名.(*类型).函数名.funcN.N
	包名.init
	包名.init.N
	模块名/包名.init
	模块名/包名.init.N
	以上规则罗列过于详细,主要是因为包含了过多的匿名函数命名规则;本文会缩小分类粒度进行归纳:
	 
	普通函数
	匿名函数
	方法
	全局常量
	模块
	初始化函数
	环境
	OS : Ubuntu 20.04.2 LTS; x86_64 
	Go : go version go1.16.2 linux/amd64 
	代码清单
	完整代码已经上传到 Github 仓库:https://github.com/fooree/go-names
	 
	目录和文件结构如下:
	 
	 
	 
	go.mod
	module github.com/fooree/go-names 
	 
	go 1.16 
	main.go
	package main 
	 
	import ( 
	  "debug/elf" 
	  "fmt" 
	  "github.com/fooree/go-names/internal" 
	  "github.com/fooree/go-names/internal/foo" 
	  "github.com/fooree/go-names/internal/foo/ree" 
	  "os" 
	  "path/filepath" 
	  "reflect" 
	  "sort" 
	  "strings" 
	  "time" 
	) 
	 
	//go:noinline 
	func anonymousType() { 
	  t := reflect.TypeOf(struct { 
	    Name string 
	  }{ 
	    Name: "Jack", 
	  }) 
	  fmt.Printf("name=%s, string=%s, addres=%pn", t.Name(), t.String(), t) 
	} 
	 
	func main() { 
	  anonymousType() 
	  ree.Run() 
	  foo.Y.Foo() 
	  internal.X.Foo() 
	   
	  name, _ := filepath.Abs(os.Args[0]) 
	  file, err := elf.Open(name) 
	  if err != nil { 
	    panic(err) 
	  } 
	  defer func() { _ = file.Close() }() 
	  symbols, err := file.Symbols() 
	  if err != nil { 
	    panic(err) 
	  } 
	 
	  slice := make([]string, 0, 100) 
	  for _, symbol := range symbols { 
	    const module = "github.com/fooree/go-names" 
	    const name = "main" 
	    if strings.HasPrefix(symbol.Name, module) || strings.HasPrefix(symbol.Name, name) { 
	      slice = append(slice, symbol.Name) 
	    } 
	  } 
	 
	  go func() { 
	    sort.Slice(slice, func(i, j int) bool { 
	      return slice[i] < slice[j] 
	    }) 
	    go func() { 
	      fmt.Println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 
	    }() 
	  }() 
	 
	  time.Sleep(time.Second) 
	 
	  for _, sym := range slice { 
	    fmt.Println(sym) 
	  } 
	} 
	internal/a.go
	 
	package internal 
	 
	import ( 
	  "fmt" 
	  "reflect" 
	) 
	 
	type Foo interface { 
	  Foo() 
	} 
	 
	type Int int 
	 
	var X Int 
	 
	//go:noinline 
	func (i *Int) Foo() { 
	  t := reflect.TypeOf(i) 
	  go func() { 
	    fmt.Printf("i am Int, name=%s, string=%sn", t.Name(), t.String()) 
	  }() 
	} 
	 
	func init() { 
	  X = Int(0x123) 
	} 
	 
	func init() { 
	  fmt.Println("X =", X) 
	} 
	internal/foo/b.go
	package foo 
	 
	import ( 
	  "fmt" 
	  "reflect" 
	) 
	 
	type Ree struct { 
	  Name string 
	} 
	 
	//go:noinline 
	func (r Ree) Foo() { 
	  anonymousType() 
	  t := reflect.TypeOf(r) 
	  fmt.Printf("i am Ree, name=%s, string=%sn", t.Name(), t.String()) 
	} 
	 
	//go:noinline 
	func anonymousType() { 
	  t := reflect.TypeOf(struct { 
	    Name string 
	  }{ 
	    Name: "Jack", 
	  }) 
	  fmt.Printf("name=%s, string=%s, addres=%pn", t.Name(), t.String(), t) 
	} 
	 
	var Y = Ree{"Rose"} 
	 
	func init() { 
	  fmt.Println("Y =",Y) 
	} 
	internal/foo/ree/c.go
	package ree 
	 
	import "sort" 
	 
	var arr = []int{1, 5, 6, 2, 7, 3, 7, 2} 
	 
	func Run() { 
	  sort.Slice(arr, func(i, j int) bool { 
	    return arr[i]<arr[j] 
	  }) 
	} 
	查看符号表
	编译以上代码并执行,在执行过程中,对可执行程序本身进行符号解析,过滤并输出以上代码中定义的符号。
	 
	 
	 
	普通函数
	在Go语言中,所有的代码都必须位于某个包(package)中。
	 
	在Go语言中,最特殊的一个包名是main,无论它所在的目录名称是什么,编译后该包下的符号都必须以 main.开头。
	 
	在Go语言中,最特殊的一个函数是main包中的main函数,其编译之后的符号名称为main.main,Go运行时将该函数作为程序的入口。
	 
	main包中的其它有名称的函数,都会被编译器重命名为“包名.函数名”的格式,例如main.anonymousType。
	 
	匿名函数
	顾名思义,匿名函数就是没有名称的函数。
	 
	函数中定义的匿名函数,会被重命名为“包名.函数名.funcN”的格式,其中N是一个可递增的数字。
	 
	例如,在以上代码清单中,
	 
	main函数里defer关键字后的func() { _ = file.Close() }()是第一个匿名函数,被重命名为main.main.func1。
	 
	main函数里go关键字后的func是第二个匿名函数,被重命名为main.main.func2。
	 
	main.main.func2函数里又定义了两个匿名函数,它们不再被重命名为funcN格式,而是被重命名为funcN.N格式,分别为main.main.func2.1和main.main.func2.2。
	 
	方法
	专属于某一个数据类型的函数,称为方法。
	 
	方法,只不过是语法层面的一个称谓而已,其本质就是函数;方法的接受者就是其第一个参数,所以方法至少有一个参数。
	 
	在 A Tour of Go (https://tour.golang.org/methods/1) 中,对函数的定义为:
	 
	A method is a function with a special receiver argument. 
	方法的定义格式有两种:
	 
	1.接受者为数据类型
	 
	例如,reflect/value.go源文件中的Elem方法:
	 
	func (v Value) Elem() Value { 
	    // 此处省略方法代码 
	} 
	2.接受者为指针类型
	 
	例如,reflect/value.go源文件中的Value方法:
	 
	func (it *MapIter) Value() Value {  
	    // 此处省略方法代码 
	} 
	通常情况下,以上两种格式的方法定义,对应的重命名规则分别如下:
	 
	包名.类型.方法名,例如:reflect.Value.Elem
	 
	包名.(*类型).方法名,例如:reflect.(*MapIter).Value
	 
	 
	 
	实际情况是:编译过程中的方法重命名规则要复杂的多。后续其他的专题文章会逐渐介绍。
	 
	方法中如果包含匿名函数,重命名规则是在其后追加funcN或funcN.N。
	 
	全局变量
	全局变量的重命名规则是“包名.变量名”。
	 
	例如os/proc.go源文件中定义的Args变量。
	 
	 
	 
	包层级
	在Go语言中,一个包可以包含和定义其他的包,这是通过子目录实现的,从而形成了包的层级结构。
	 
	如果包存在层级结构,则使用“/”进行包名之间的连接,从而实现包的编译重命名。
	 
	例如,io/fs/源码目录中定义包名是fs,该包中的变量和函数,在编译后它们的包名都是“io/fs”。
	 
	 
	 
	模块
	模块(module)是Go语言的依赖管理工具。
	 
	一个模块一般会包含一个或多个包(package)。
	 
	模块中的包、函数、方法、全局变量、匿名函数的重命名规则与以上总结的规则一致,只是需要增加前缀“模块名/”。
	 
	例如,文本代码清单中定义的模块名称是github.com/fooree/go-names,模块中定义的符号重命名如下:
	 
	github.com/fooree/go-names/internal.(*Int).Foo        // 方法名 
	github.com/fooree/go-names/internal.(*Int).Foo.func1  // 匿名函数 
	github.com/fooree/go-names/internal.X                 // 全局变量 
	github.com/fooree/go-names/internal/foo.Ree.Foo       // 方法名 
	github.com/fooree/go-names/internal/foo.Y             // 全局变量 
	github.com/fooree/go-names/internal/foo.anonymousType // 函数名 
	github.com/fooree/go-names/internal/foo/ree.Run       // 函数名 
	github.com/fooree/go-names/internal/foo/ree.Run.func1 // 匿名函数 
	github.com/fooree/go-names/internal/foo/ree.arr       
	初始化函数
	关于初始化函数的重命名规则,请阅读 【Go】初始化函数。
	 
	结语
	本文总结了一些基本的符号重命名规则。 (编辑:海洋资讯信息网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! | 
站长推荐
            
        
