Go语言之程序符号重命名

开发 后端
Go程序源代码中,关键字、接口、类型、常量、变量、方法、函数、字段、标签(label)等等的名称都可以称为符号。

[[427663]]

Go程序源代码中,关键字、接口、类型、常量、变量、方法、函数、字段、标签(label)等等的名称都可以称为符号。

Go可执行程序中,符号表主要包含两种类型的符号:

  1. 数据对象(Data object)
  2. 函数(Function)

一般情况下(不是绝对的),在源代码编译为可执行程序的过程中,

  • 关键字、局部变量、标签会转变为指令、数据或者消失,而不再是符号
  • 接口、类型、全局常量会被保存为不可变数据,而不再是符号
  • 函数、方法、全局变量、全局常量会被重命名,保存在符号表中

本文主要总结了函数、方法、全局变量在编译过程中通用的重命名规则,不讨论类似内联优化、闭包、非空接口、编译器生成等复杂的情况。

规则

Go 1.18版本之前符号重命名常见规则列表如下:

  1. 包名.变量名
  2. 包名.函数名
  3. 包名.函数名.funcN
  4. 包名.函数名.funcN.N
  5. 包名.类型.函数名
  6. 包名.类型.函数名.funcN
  7. 包名.类型.函数名.funcN.N
  8. 包名.(*类型).函数名
  9. 包名.(*类型).函数名.funcN
  10. 包名.(*类型).函数名.funcN.N
  11. 模块名/包名.变量名
  12. 模块名/包名.函数名
  13. 模块名/包名.函数名.funcN
  14. 模块名/包名.函数名.funcN.N
  15. 模块名/包名.类型.函数名
  16. 模块名/包名.类型.函数名.funcN
  17. 模块名/包名.类型.函数名.funcN.N
  18. 模块名/包名.(*类型).函数名
  19. 模块名/包名.(*类型).函数名.funcN
  20. 模块名/包名.(*类型).函数名.funcN.N
  21. 包名.init
  22. 包名.init.N
  23. 模块名/包名.init
  24. 模块名/包名.init.N

以上规则罗列过于详细,主要是因为包含了过多的匿名函数命名规则;本文会缩小分类粒度进行归纳:

  1. 普通函数
  2. 匿名函数
  3. 方法
  4. 全局常量
  5. 模块
  6. 初始化函数

环境

  1. OS : Ubuntu 20.04.2 LTS; x86_64 
  2. Go : go version go1.16.2 linux/amd64 

代码清单

完整代码已经上传到 Github 仓库:https://github.com/fooree/go-names

目录和文件结构如下:

go.mod

  1. module github.com/fooree/go-names 
  2.  
  3. go 1.16 

main.go

  1. package main 
  2.  
  3. import ( 
  4.   "debug/elf" 
  5.   "fmt" 
  6.   "github.com/fooree/go-names/internal" 
  7.   "github.com/fooree/go-names/internal/foo" 
  8.   "github.com/fooree/go-names/internal/foo/ree" 
  9.   "os" 
  10.   "path/filepath" 
  11.   "reflect" 
  12.   "sort" 
  13.   "strings" 
  14.   "time" 
  15.  
  16. //go:noinline 
  17. func anonymousType() { 
  18.   t := reflect.TypeOf(struct { 
  19.     Name string 
  20.   }{ 
  21.     Name"Jack"
  22.   }) 
  23.   fmt.Printf("name=%s, string=%s, addres=%p\n", t.Name(), t.String(), t) 
  24.  
  25. func main() { 
  26.   anonymousType() 
  27.   ree.Run() 
  28.   foo.Y.Foo() 
  29.   internal.X.Foo() 
  30.    
  31.   name, _ := filepath.Abs(os.Args[0]) 
  32.   file, err := elf.Open(name
  33.   if err != nil { 
  34.     panic(err) 
  35.   } 
  36.   defer func() { _ = file.Close() }() 
  37.   symbols, err := file.Symbols() 
  38.   if err != nil { 
  39.     panic(err) 
  40.   } 
  41.  
  42.   slice := make([]string, 0, 100) 
  43.   for _, symbol := range symbols { 
  44.     const module = "github.com/fooree/go-names" 
  45.     const name = "main" 
  46.     if strings.HasPrefix(symbol.Name, module) || strings.HasPrefix(symbol.Namename) { 
  47.       slice = append(slice, symbol.Name
  48.     } 
  49.   } 
  50.  
  51.   go func() { 
  52.     sort.Slice(slice, func(i, j int) bool { 
  53.       return slice[i] < slice[j] 
  54.     }) 
  55.     go func() { 
  56.       fmt.Println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
  57.     }() 
  58.   }() 
  59.  
  60.   time.Sleep(time.Second
  61.  
  62.   for _, sym := range slice { 
  63.     fmt.Println(sym) 
  64.   } 

internal/a.go

  1. package internal 
  2.  
  3. import ( 
  4.   "fmt" 
  5.   "reflect" 
  6.  
  7. type Foo interface { 
  8.   Foo() 
  9.  
  10. type Int int 
  11.  
  12. var X Int 
  13.  
  14. //go:noinline 
  15. func (i *Int) Foo() { 
  16.   t := reflect.TypeOf(i) 
  17.   go func() { 
  18.     fmt.Printf("i am Int, name=%s, string=%s\n", t.Name(), t.String()) 
  19.   }() 
  20.  
  21. func init() { 
  22.   X = Int(0x123) 
  23.  
  24. func init() { 
  25.   fmt.Println("X =", X) 

internal/foo/b.go

  1. package foo 
  2.  
  3. import ( 
  4.   "fmt" 
  5.   "reflect" 
  6.  
  7. type Ree struct { 
  8.   Name string 
  9.  
  10. //go:noinline 
  11. func (r Ree) Foo() { 
  12.   anonymousType() 
  13.   t := reflect.TypeOf(r) 
  14.   fmt.Printf("i am Ree, name=%s, string=%s\n", t.Name(), t.String()) 
  15.  
  16. //go:noinline 
  17. func anonymousType() { 
  18.   t := reflect.TypeOf(struct { 
  19.     Name string 
  20.   }{ 
  21.     Name"Jack"
  22.   }) 
  23.   fmt.Printf("name=%s, string=%s, addres=%p\n", t.Name(), t.String(), t) 
  24.  
  25. var Y = Ree{"Rose"
  26.  
  27. func init() { 
  28.   fmt.Println("Y =",Y) 

internal/foo/ree/c.go

  1. package ree 
  2.  
  3. import "sort" 
  4.  
  5. var arr = []int{1, 5, 6, 2, 7, 3, 7, 2} 
  6.  
  7. func Run() { 
  8.   sort.Slice(arr, func(i, j int) bool { 
  9.     return arr[i]<arr[j] 
  10.   }) 

查看符号表

编译以上代码并执行,在执行过程中,对可执行程序本身进行符号解析,过滤并输出以上代码中定义的符号。

普通函数

在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) 中,对函数的定义为:

  1. A method is a function with a special receiver argument. 

方法的定义格式有两种:

1.接受者为数据类型

例如,reflect/value.go源文件中的Elem方法:

  1. func (v Value) Elem() Value { 
  2.     // 此处省略方法代码 

2.接受者为指针类型

例如,reflect/value.go源文件中的Value方法:

  1. func (it *MapIter) Value() Value {  
  2.     // 此处省略方法代码 

通常情况下,以上两种格式的方法定义,对应的重命名规则分别如下:

包名.类型.方法名,例如: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,模块中定义的符号重命名如下:

  1. github.com/fooree/go-names/internal.(*Int).Foo        // 方法名 
  2. github.com/fooree/go-names/internal.(*Int).Foo.func1  // 匿名函数 
  3. github.com/fooree/go-names/internal.X                 // 全局变量 
  4. github.com/fooree/go-names/internal/foo.Ree.Foo       // 方法名 
  5. github.com/fooree/go-names/internal/foo.Y             // 全局变量 
  6. github.com/fooree/go-names/internal/foo.anonymousType // 函数名 
  7. github.com/fooree/go-names/internal/foo/ree.Run       // 函数名 
  8. github.com/fooree/go-names/internal/foo/ree.Run.func1 // 匿名函数 
  9. github.com/fooree/go-names/internal/foo/ree.arr       

初始化函数

关于初始化函数的重命名规则,请阅读 【Go】初始化函数。

结语

本文总结了一些基本的符号重命名规则。

本文转载自微信公众号「Golang In Memory」,可以通过以下二维码关注。转载本文请联系Golang In Memory公众号。

 

责任编辑:武晓燕 来源: Golang In Memory
相关推荐

2020-12-31 09:06:44

Go语言Reflect

2021-10-03 22:18:14

Go语言整数

2021-10-23 06:42:14

Go语言接口

2021-10-16 17:53:35

Go函数编程

2022-03-28 13:34:26

Go泛型部署泛型

2010-11-19 13:48:18

2024-01-05 20:46:14

2013-08-20 10:11:20

Go系统管理员

2021-05-12 08:53:54

Go语言调度

2021-10-18 10:53:26

Go 代码技术

2009-12-22 15:28:51

Linux批量重命名文

2011-03-04 09:48:21

PureFTPd

2013-03-22 15:40:32

VS项目整体命名.NET

2012-06-20 15:01:04

Web

2011-12-22 10:08:56

塞班诺基亚Belle

2010-09-13 13:33:51

sql server外

2024-01-08 08:23:07

Go语言代码

2021-05-27 08:47:16

C语言C语言程序开发

2012-08-07 09:29:09

程序员编程

2018-06-25 13:10:16

Linux复制重命名
点赞
收藏

51CTO技术栈公众号