1
0
mirror of https://github.com/chai2010/advanced-go-programming-book.git synced 2025-05-24 04:22:22 +00:00

ch3: 规划图像编号

This commit is contained in:
chai2010 2018-12-14 21:50:29 +08:00
parent 67ed19bdd5
commit bcf560505d
28 changed files with 26 additions and 26 deletions

View File

@ -65,9 +65,9 @@
下图是某一层的任务将输入数据的0剔除非0的数据依次输出右边部分是解决方案。 下图是某一层的任务将输入数据的0剔除非0的数据依次输出右边部分是解决方案。
![](../images/ch3.2-1-arch-hsm-zero.jpg) ![](../images/ch3-1-arch-hsm-zero.jpg)
*图 3.2-1 人力资源机器* *图 3-1 人力资源机器*
整个程序只有一个输入指令、一个输出指令和两个跳转指令共四个指令: 整个程序只有一个输入指令、一个输出指令和两个跳转指令共四个指令:
@ -89,9 +89,9 @@ X86其实是是80X86的简称后面三个字母包括Intel 8086、80286
在使用汇编语言之前必须要了解对应的CPU体系结构。下面是X86/AMD架构图 在使用汇编语言之前必须要了解对应的CPU体系结构。下面是X86/AMD架构图
![](../images/ch3.2-2-arch-amd64-01.ditaa.png) ![](../images/ch3-2-arch-amd64-01.ditaa.png)
*图 3.2-2 AMD64架构* *图 3-2 AMD64架构*
左边是内存部分是常见的内存布局。其中text一般对应代码段用于存储要执行指令数据代码段一般是只读的。然后是rodata和data数据段数据段一般用于存放全局的数据其中rodata是只读的数据段。而heap段则用于管理动态的数据stack段用于管理每个函数调用时相关的数据。在汇编语言中一般重点关注text代码段和data数据段因此Go汇编语言中专门提供了对应TEXT和DATA命令用于定义代码和数据。 左边是内存部分是常见的内存布局。其中text一般对应代码段用于存储要执行指令数据代码段一般是只读的。然后是rodata和data数据段数据段一般用于存放全局的数据其中rodata是只读的数据段。而heap段则用于管理动态的数据stack段用于管理每个函数调用时相关的数据。在汇编语言中一般重点关注text代码段和data数据段因此Go汇编语言中专门提供了对应TEXT和DATA命令用于定义代码和数据。
@ -107,9 +107,9 @@ Go汇编为了简化汇编代码的编写引入了PC、FP、SP、SB四个伪
四个伪寄存器和X86/AMD64的内存和寄存器的相互关系如下图 四个伪寄存器和X86/AMD64的内存和寄存器的相互关系如下图
![](../images/ch3.2-3-arch-amd64-02.ditaa.png) ![](../images/ch3-3-arch-amd64-02.ditaa.png)
*图 3.2-3 Go汇编的伪寄存器* *图 3-3 Go汇编的伪寄存器*
在AMD64环境伪PC寄存器其实是IP指令计数器寄存器的别名。伪FP寄存器对应的是函数的帧指针一般用来访问函数的参数和返回值。伪SP栈指针对应的是当前函数栈帧的底部不包括参数和返回值部分一般用于定位局部变量。伪SP是一个比较特殊的寄存器因为还存在一个同名的SP真寄存器。真SP寄存器对应的是栈的顶部一般用于定位调用其它函数的参数和返回值。 在AMD64环境伪PC寄存器其实是IP指令计数器寄存器的别名。伪FP寄存器对应的是函数的帧指针一般用来访问函数的参数和返回值。伪SP栈指针对应的是当前函数栈帧的底部不包括参数和返回值部分一般用于定位局部变量。伪SP是一个比较特殊的寄存器因为还存在一个同名的SP真寄存器。真SP寄存器对应的是栈的顶部一般用于定位调用其它函数的参数和返回值。

View File

@ -110,9 +110,9 @@ DATA ·num+8(SB)/8,$0
下图是Go语句和汇编语句定义变量时的对应关系 下图是Go语句和汇编语句定义变量时的对应关系
![](../images/ch3.3-1-pkg-var-decl-01.ditaa.png) ![](../images/ch3-4-pkg-var-decl-01.ditaa.png)
*图 3.3-1 变量定义* *图 3-4 变量定义*
汇编代码中并不需要NOPTR标志因为Go编译器会从Go语言语句声明的`[2]int`类型中推导出该变量内部没有指针数据。 汇编代码中并不需要NOPTR标志因为Go编译器会从Go语言语句声明的`[2]int`类型中推导出该变量内部没有指针数据。
@ -174,9 +174,9 @@ Go汇编语言通常无法区分变量是否是浮点数类型与之相关的
IEEE754标准中最高位1bit为符号位然后是指数位指数为采用移码格式表示然后是有效数部分其中小数点左边的一个bit位被省略。下图是IEEE754中float32类型浮点数的bit布局 IEEE754标准中最高位1bit为符号位然后是指数位指数为采用移码格式表示然后是有效数部分其中小数点左边的一个bit位被省略。下图是IEEE754中float32类型浮点数的bit布局
![](../images/ch3.3-2-ieee754.jpg) ![](../images/ch3-5-ieee754.jpg)
*图 3.3-2 IEEE754浮点数结构* *图 3-5 IEEE754浮点数结构*
IEEE754浮点数还有一些奇妙的特性比如有正负两个0除了无穷大和无穷小Inf还有非数NaN同时如果两个浮点数有序那么对应的有符号整数也是有序的反之则不一定成立因为浮点数中存在的非数是不可排序的。浮点数是程序中最难琢磨的角落因为程序中很多手写的浮点数字面值常量根本无法精确表达浮点数计算涉及到的误差舍入方式可能也的随机的。 IEEE754浮点数还有一些奇妙的特性比如有正负两个0除了无穷大和无穷小Inf还有非数NaN同时如果两个浮点数有序那么对应的有符号整数也是有序的反之则不一定成立因为浮点数中存在的非数是不可排序的。浮点数是程序中最难琢磨的角落因为程序中很多手写的浮点数字面值常量根本无法精确表达浮点数计算涉及到的误差舍入方式可能也的随机的。
@ -306,18 +306,18 @@ func makechan(chanType *byte, size int) (hchan chan any)
首先查看前面已经见过的`[2]int`类型数组的内存布局: 首先查看前面已经见过的`[2]int`类型数组的内存布局:
![](../images/ch3.3-3-pkg-var-decl-02.ditaa.png) ![](../images/ch3-6-pkg-var-decl-02.ditaa.png)
*图 3.3-3 变量定义* *图 3-6 变量定义*
变量在data段分配空间数组的元素地址依次从低向高排列。 变量在data段分配空间数组的元素地址依次从低向高排列。
然后再查看下标准库图像包中`image.Point`结构体类型变量的内存布局: 然后再查看下标准库图像包中`image.Point`结构体类型变量的内存布局:
![](../images/ch3.3-4-pkg-var-decl-03.ditaa.png) ![](../images/ch3-7-pkg-var-decl-03.ditaa.png)
*图 3.3-4 结构体变量定义* *图 3-7 结构体变量定义*
变量也时在data段分配空间变量结构体成员的地址也是依次从低向高排列。 变量也时在data段分配空间变量结构体成员的地址也是依次从低向高排列。

View File

@ -38,9 +38,9 @@ TEXT ·Swap(SB), NOSPLIT, $0
下图是Swap函数几种不同写法的对比关系图 下图是Swap函数几种不同写法的对比关系图
![](../images/ch3.4-1-func-decl-01.ditaa.png) ![](../images/ch3-8-func-decl-01.ditaa.png)
*图 3.4-1 函数定义* *图 3-8 函数定义*
第一种是最完整的写法函数名部分包含了当前包的路径同时指明了函数的参数大小为32个字节对应参数和返回值的4个int类型。第二种写法则比较简洁省略了当前包的路径和参数的大小。如果有NOSPLIT标注会禁止汇编器为汇编函数插入栈分裂的代码。NOSPLIT对应Go语言中的`//go:nosplit`注释。 第一种是最完整的写法函数名部分包含了当前包的路径同时指明了函数的参数大小为32个字节对应参数和返回值的4个int类型。第二种写法则比较简洁省略了当前包的路径和参数的大小。如果有NOSPLIT标注会禁止汇编器为汇编函数插入栈分裂的代码。NOSPLIT对应Go语言中的`//go:nosplit`注释。
@ -82,9 +82,9 @@ TEXT ·Swap(SB), $0-32
下图是Swap函数中参数和返回值在内存中的布局图 下图是Swap函数中参数和返回值在内存中的布局图
![](../images/ch3.4-2-func-decl-02.ditaa.png) ![](../images/ch3-9-func-decl-02.ditaa.png)
*图 3.4-2 函数定义* *图 3-9 函数定义*
下面的代码演示了如何在汇编函数中使用参数和返回值: 下面的代码演示了如何在汇编函数中使用参数和返回值:
@ -150,9 +150,9 @@ func Foo(FP *SomeFunc_args, FP_ret *SomeFunc_returns) {
Foo函数的参数和返回值的大小和内存布局 Foo函数的参数和返回值的大小和内存布局
![](../images/ch3.4-3-func-arg-01.ditaa.png) ![](../images/ch3-10-func-arg-01.ditaa.png)
*图 3.4-3 函数的参数* *图 3-10 函数的参数*
下面的代码演示了Foo汇编函数参数和返回值的定位 下面的代码演示了Foo汇编函数参数和返回值的定位
@ -224,9 +224,9 @@ func Foo() {
下面是Foo函数的局部变量的大小和内存布局 下面是Foo函数的局部变量的大小和内存布局
![](../images/ch3.4-4-func-local-var-01.ditaa.png) ![](../images/ch3-11-func-local-var-01.ditaa.png)
*图 3.4-4 函数的局部变量* *图 3-11 函数的局部变量*
从图中可以看出Foo函数局部变量和前一个例子中参数和返回值的内存布局是完全一样的这也是我们故意设计的结果。但是参数和返回值是通过伪FP寄存器定位的FP寄存器对应第一个参数的开始地址第一个参数地址较低因此每个变量的偏移量是正数。而局部变量是通过伪SP寄存器定位的而伪SP寄存器对应的是第一个局部变量的结束地址第一个局部变量地址较大因此每个局部变量的偏移量都是负数。 从图中可以看出Foo函数局部变量和前一个例子中参数和返回值的内存布局是完全一样的这也是我们故意设计的结果。但是参数和返回值是通过伪FP寄存器定位的FP寄存器对应第一个参数的开始地址第一个参数地址较低因此每个变量的偏移量是正数。而局部变量是通过伪SP寄存器定位的而伪SP寄存器对应的是第一个局部变量的结束地址第一个局部变量地址较大因此每个局部变量的偏移量都是负数。
@ -258,9 +258,9 @@ func sum(a, b int) int {
下图展示了三个函数逐级调用时内存中函数参数和返回值的布局: 下图展示了三个函数逐级调用时内存中函数参数和返回值的布局:
![](../images/ch3.4-5-func-call-frame-01.ditaa.png) ![](../images/ch3-12-func-call-frame-01.ditaa.png)
*图 3.4-5 函数帧* *图 3-12 函数帧*
为了便于理解我们对真实的内存布局进行了简化。要记住的是调用函数时被调用函数的参数和返回值内存空间都必须由调用者提供。因此函数的局部变量和为调用其它函数准备的栈空间总和就确定了函数帧的大小。调用其它函数前调用方要选择保存相关寄存器到栈中并在调用函数返回后选择要恢复的寄存器进行保存。最终通过CALL指令调用函数的过程和调用我们熟悉的调用println函数输出的过程类似。 为了便于理解我们对真实的内存布局进行了简化。要记住的是调用函数时被调用函数的参数和返回值内存空间都必须由调用者提供。因此函数的局部变量和为调用其它函数准备的栈空间总和就确定了函数帧的大小。调用其它函数前调用方要选择保存相关寄存器到栈中并在调用函数返回后选择要恢复的寄存器进行保存。最终通过CALL指令调用函数的过程和调用我们熟悉的调用println函数输出的过程类似。

View File

@ -9,9 +9,9 @@
和C语言函数不同Go语言函数的参数和返回值完全通过栈传递。下面是Go函数调用时栈的布局图 和C语言函数不同Go语言函数的参数和返回值完全通过栈传递。下面是Go函数调用时栈的布局图
![](../images/ch3.6-1-func-stack-frame-layout-01.ditaa.png) ![](../images/ch3-13-func-stack-frame-layout-01.ditaa.png)
*图 3.6-1 函数调用参数布局* *图 3-13 函数调用参数布局*
首先是调用函数前准备的输入参数和返回值空间。然后CALL指令将首先触发返回地址入栈操作。在进入到被调用函数内之后汇编器自动插入了BP寄存器相关的指令因此BP寄存器和返回地址是紧挨着的。再下面就是当前函数的局部变量的空间包含再次调用其它函数需要准备的调用参数空间。被调用的函数执行RET返回指令时先从栈恢复BP和SP寄存器接着取出的返回地址跳转到对应的指令执行。 首先是调用函数前准备的输入参数和返回值空间。然后CALL指令将首先触发返回地址入栈操作。在进入到被调用函数内之后汇编器自动插入了BP寄存器相关的指令因此BP寄存器和返回地址是紧挨着的。再下面就是当前函数的局部变量的空间包含再次调用其它函数需要准备的调用参数空间。被调用的函数执行RET返回指令时先从栈恢复BP和SP寄存器接着取出的返回地址跳转到对应的指令执行。

View File

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB