diff --git a/ch3-asm/ch3-01-basic.md b/ch3-asm/ch3-01-basic.md index fd8764f..22b5389 100644 --- a/ch3-asm/ch3-01-basic.md +++ b/ch3-asm/ch3-01-basic.md @@ -172,7 +172,7 @@ func main() { pkgpath.NameData: missing Go //type information for global symbol: size 8 ``` -提示汇编中定义的NameData符号没有类型信息。其实Go汇编语言中定义的数据并没有所谓的类型,每个符号只不过是对应一个内存而且。出现这种错误的原因是,Go语言的垃圾回收器在扫描NameData变量的时候,无法知晓该变量内部是否包含指针。因此,真正错误的原因并不是NameData没有类型,二是NameData变量没有标注是否会含有指针信息。 +提示汇编中定义的NameData符号没有类型信息。其实Go汇编语言中定义的数据并没有所谓的类型,每个符号只不过是对应一块内存而已。出现这种错误的原因是,Go语言的垃圾回收器在扫描NameData变量的时候,无法知晓该变量内部是否包含指针。因此,真正错误的原因并不是NameData没有类型,而是NameData变量没有标注是否会含有指针信息。 通过给NameData变量增加一个标志,表示其中不会包含指针数据可以修复该错误: @@ -193,9 +193,9 @@ var NameData [8]byte var Name string ``` -我们将NameData声明为长度为8的字节数组。因为编译器可以通过类型分析出该变量不会包含指针,因此汇编代码中可以NOPTR标志信息。 +我们将NameData声明为长度为8的字节数组。编译器可以通过类型分析出该变量不会包含指针,因此汇编代码中可以省略NOPTR标志。 -在这个实现中,Name字符串底层其实引用的是NameData内存对应的“gopher”字符串数据。因此,如果NameData发生变化的化,Name字符串的数据也会跟着变化的。 +在这个实现中,Name字符串底层其实引用的是NameData内存对应的“gopher”字符串数据。因此,如果NameData发生变化,Name字符串的数据也会跟着变化。 ```go func main() { @@ -218,7 +218,7 @@ DATA ·Name+8(SB)/8,$6 DATA ·Name+16(SB)/8,$"gopher" ``` -在新的结构中,Name符号对应的内存从16字节变为24字节,多出的8个字节用户存放底层的“gopher”字符串。·Name符号前16个字节依然对应reflect.StringHeader结构体:Data部分对应`$·Name+16(SB)`,表示数据的地址为Name符号往后偏移16个字节的位置;Len部分依然对应6个字节的长度。 +在新的结构中,Name符号对应的内存从16字节变为24字节,多出的8个字节存放底层的“gopher”字符串。·Name符号前16个字节依然对应reflect.StringHeader结构体:Data部分对应`$·Name+16(SB)`,表示数据的地址为Name符号往后偏移16个字节的位置;Len部分依然对应6个字节的长度。 ## 定义main函数 @@ -245,7 +245,7 @@ TEXT ·main(SB), $16-0 CALL runtime·printnl(SB) RET ``` -`TEXT ·main(SB), $16-0`用于定义`main`函数,其中`$16-0`表示`main`函数的帧大小是16个字节(对应string头的大小,用于给`runtime·printstring`函数传递参数),`0`表示`main`函数没有参数和返回值。`main`函数内部通过调用运行时内部的`runtime·printstring(SB)`函数来打印字符串。然后调用runtime·printnl打印换行符号。 +`TEXT ·main(SB), $16-0`用于定义`main`函数,其中`$16-0`表示`main`函数的帧大小是16个字节(对应string头部结构体的大小,用于给`runtime·printstring`函数传递参数),`0`表示`main`函数没有参数和返回值。`main`函数内部通过调用运行时内部的`runtime·printstring(SB)`函数来打印字符串。然后调用`runtime·printnl`打印换行符号。 Go语言函数在函数调用时,完全通过栈传递调用参数和返回值。先通过MOVQ指令,将helloworld对应的字符串头部结构体的16个字节复制到栈指针SP对应的16字节的空间,然后通过CALL指令调用对应函数。最后使用RET指令表示当前函数返回。 @@ -254,7 +254,7 @@ Go语言函数在函数调用时,完全通过栈传递调用参数和返回值 Go语言函数或方法符号在编译为目标文件后,目标文件中的每个符号均包含对应包的绝对导入路径。因此目标文件的符号可能非常复杂,比如“path/to/pkg.(*SomeType).SomeMethod”或“go.string."abc"”。目标文件的符号名中不仅仅包含普通的字母,还可能包含诸多特殊字符。而Go语言的汇编器是从plan9移植过来的二把刀,并不能处理这些特殊的字符,导致了用Go汇编语言手工实现Go诸多特性时遇到种种限制。 -Go汇编语言同样遵循Go语言少即是多的哲学,它只保留了最基本的特性:定义变量和全局函数。同时为了简化Go汇编器的词法扫描程序的实现,特别引入了Unicode中的中点`·`和大写的除法`/`,对应的Unicode码点为`U+00B7`和`U+2215`。汇编器编译后,中点`·`会被替换为ASCII中的点“.”,大写点除法会被替换为ASCII码中的除法“/”,比如`math/rand·Int`会被替换为`math/rand.Int`。这样可以将点和浮点数中的小数点、大写的除法和表达式中的除法符号分开,可以简化汇编程序此法分析部分的实现。 +Go汇编语言同样遵循Go语言少即是多的哲学,它只保留了最基本的特性:定义变量和全局函数。同时为了简化Go汇编器的词法扫描程序的实现,特别引入了Unicode中的中点`·`和大写的除法`/`,对应的Unicode码点为`U+00B7`和`U+2215`。汇编器编译后,中点`·`会被替换为ASCII中的点“.”,大写的除法会被替换为ASCII码中的除法“/”,比如`math/rand·Int`会被替换为`math/rand.Int`。这样可以将中点和浮点数中的小数点、大写的除法和表达式中的除法符号分开,可以简化汇编程序词法分析部分的实现。 即使暂时抛开Go汇编语言设计取舍的问题,中点`·`和除法`/`两个字符的如何输入就是一个挑战。这两个字符在 https://golang.org/doc/asm 文档中均有描述,因此直接从该页面复制是最简单可靠的方式。