开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第6天,点击查看活动详情
汇编语言是直面计算机的编程语言,因此理解计算机结构是掌握汇编语言的前提。它其实是一种非常简单的编程语言,因为汇编语言面向的计算机模型就是非常简单的。觉得汇编语言难学主要有几个原因:不同类型的CPU都有自己的一套指令;即使是相同的CPU,32位和64位的运行模式依然会有差异;不同的汇编工具有自己特有的汇编指令;不同的操作系统和高级编程语言和底层汇编的调用规范也并不相同。
一些概念
CPU是由指令和寄存器组成,指令是每个CPU内置的算法,指令处理的对象就是全部的寄存器和内存。我们可以将每个指令看作是CPU内置标准库中提供的一个个函数,然后基于这些函数构造更复杂的程序的过程就是用汇编语言编程的过程。而Go汇编中又另外引入了四个伪寄存器 (PC、FP、SP、SB)。四个伪寄存器加其它的通用寄存器就是Go汇编语言对CPU的重新抽象,抽象的结构也适用于其它非X86类型的体系结构。 在AMD64环境,伪PC寄存器其实是IP指令计数器寄存器的别名。伪FP寄存器对应的是函数的帧指针,一般用来访问函数的参数和返回值。伪SP栈指针对应的是当前函数栈帧的底部(不包括参数和返回值部分),一般用于定位局部变量。伪SP是一个比较特殊的寄存器,因为还存在一个同名的SP真寄存器。真SP寄存器对应的是栈的顶部,一般用于定位调用其它函数的参数和返回值。当需要区分伪寄存器和真寄存器的时候只需要记住一点:伪寄存器一般需要一个标识符和偏移量为前缀,如果没有标识符前缀则是真寄存器。比如 (SP) 、 +8(SP) 没有标识符前缀为真SP寄存器,而 a(SP) 、 b+8(SP) 有标识符为前缀表示伪寄存器。
常量、变量命名
Go汇编语言中常量以$美元符号为前缀。以下是几种类型常量的例子:
$10 // 十进制
$0xf4f8fcff // 十六进制
$1.5 // 浮点数
$'a' // 字符
$"abcd" // 字符串
GLOBL ·NameData(SB),$8 //(NameData也是常量,它对应的是NameData包变量的地址)
DATA ·NameData(SB)/8,$"hello world"
GLOBL ·Name(SB),$16
DATA ·Name+0(SB)/8,$·NameData(SB)
DATA ·Name+8(SB)/8,$6
而全局变量跟全局函数比较相似,在Go汇编语言中,内存是通过SB伪寄存器定位。SB是Static base pointer的缩写(静态内存的开始地址)。我们可以将SB想象为一个和内容容量有相同大小的字节数组,所有的静态全局符号通常可以通过SB加一个偏移量定位,而我们定义的符号
其实就是相对于SB内存开始地址偏移量。对于SB伪寄存器,全局变量和全局函数的符号并没有任何区别:
// GLOBL汇编指令用于定义名为symbol的变量,变量对应的内存宽度为width,内存 宽度部分必须用常量初始化。
GLOBL symbol(SB), width // 和上个月的函数变量的定义相似
// 通过汇编定义一个int32类型的count变量
GLOBL ·count(SB),$4
注:在Go汇编中无法为count变量指定具体的类型。在汇编中定义全局变量时,我们只需要关心变量的名字和内存大小,变量最终的类型只能在Go语言中声明。
变量定义之后,就可以通过DATA汇编指令指定对应内存中的数据,语法:
// 从symbol+offset偏移量开始,width宽度的内存,用value常量对应的值初始化
DATA symbol+offset(SB)/width, value
// 对于int32类型的变量来说,可以逐个字节初始化,也可以一次性初始化:
DATA ·count+0(SB)/1,$1
DATA ·count+1(SB)/1,$2
DATA ·count+2(SB)/1,$3
DATA ·count+3(SB)/1,$4
// 或者
DATA ·count+0(SB)/4,$0x04030201
注:DATA初始化内存时,width必须是1、2、4、8几个宽度之一,因为再大的内存无法一次性用一个uint64大小的值表示。
以上就是变量命名的语法,可能没有实践看起来有点懵,想深入的话建议还是得看文档去学习每个变量类型的语法和注意点。