golang汇编之常量和全局变量(三)

news2024/9/20 20:44:46

常量

Go汇编语言中常量以$美元符号为前缀。常量的类型有整数常量、浮点数常量、字符常量和字符串常量等几种类型。以下是几种类型常量的例子:

$1           // 十进制
$0xf4f8fcff  // 十六进制
$1.5         // 浮点数
$'a'         // 字符
$"abcd"      // 字符串

对于数值型常量,可以通过常量表达式构成新的常量,其中常量表达式中运算符的优先级和Go语言保持一致。

$2+2      // 常量表达式
$3&1<<2   // == $4
$(3&1)<<2 // == $4

全局变量

在Go语言中,变量根据作用域和生命周期有全局变量和局部变量之分。全局变量是包一级的变量,全局变量一般有着较为固定的内存地址,生命周期跨越整个程序运行时间。而局部变量一般是函数内定义的的变量,只有在函数被执行的时间才能被创建,当函数完成时将会回收(暂时不考虑闭包对局部变量捕获的问题)。

从Go汇编语言角度来看,局部变量和局部变量也有大的差异。在Go汇编中全局变量和全局函数更为相似,都是通过一个人为定义的符号来引用对应的内存,区别只是内存中存放是数据还是要执行的指令。因为在冯诺伊曼系统结构的计算机中指令也是数据,而且指令和数据存放在统一编址的内存中,因此指令和数据并没有本质的差别——我们甚至可以像操作数据那样动态生成指令。而局部变量则需了解了汇编函数之后,通过SP栈空间来隐式定义。

在Go汇编语言中,内存是通过SB伪寄存器定位。SB是Static base pointer 的缩写,意为静态内存的开始地址。所有的静态全局符号可以通过SB加一个偏移量定位,而我们定义的符号其实就是相对于SB内存开始地址偏移量。对于SB伪寄存器,全局变量和全局函数的符号并没有任何区别。

要定义全局变量,首先要声明一个变量对应的符号,以及变量对应的内存大小。导出变量符号的语法如下:

GLOBL symbol(SB), width

GLOBL汇编指令用于定义名为symbol的变量,变量对应的内存宽度为width,内存宽度部分必须用常量初始化。下面的代码通过汇编定义一个int32类型的count变量:

GLOBL ·count(SB),$4

其中符号·count以中点开头表示是当前包的变量,最终符号名为被展开为path/to/pkg.count。count变量的大小是4个字节,常量必须以$美元符号开头。内存的宽度必须是2的指数倍,编译器最终会保证变量的起始地址对齐到机器字宽度。需要注意的是,在Go汇编中我们无法为count变量指定具体的类型。在汇编中定义全局变量时,我们值关心变量的名字和内存大小,变量最终的类型只能在Go语言中声明。

变量定义之后,我们可以通过DATA汇编指令指定对应内存中的数据,语法如下:

DATA symbol+offset(SB)/width, value

具体的含义是从symbol+offset偏移量开始,width宽度的内存,用value常量对应的值初始化。DATA初始化内存时,width必须是1、2、4、8几个宽度之一,因为再大的内存无法一次性用一个uint64大小的值表示。

对于int32类型的count变量来说,我们既可以逐个字节初始化,也可以一次性初始化:

DATA ·count+0(SB)/1,$1
DATA ·count+1(SB)/1,$2
DATA ·count+2(SB)/1,$3
DATA ·count+3(SB)/1,$4

// or

DATA ·count+0(SB)/4,$0x01020304

因为X86处理器是小端序,因此用十六进制0x01020304初始化全部的4个字节,和用1、2、3、4逐个初始化4个字节是一样的效果。

最后还需要在Go语言中声明对应的变量(和C语言头文件声明变量的作用类似),这样垃圾回收器会根据变量的类型来管理其中的指针相关的内存数据。

bool类型变量

Go汇编语言定义变量无法指定类型信息,因此需要先通过Go语言声明变量的类型。以下是在Go语言中声明的几个bool类型变量:

var (
	boolValue  bool
	trueValue  bool
	falseValue bool
)

在Go语言中声明的变量不能含有初始化语句。然后下面是amd64环境的汇编定义:

GLOBL ·boolValue(SB),$1   // 未初始化

GLOBL ·trueValue(SB),$1   // var trueValue = true
DATA ·trueValue(SB)/1,$1  // 非 0 均为 true

GLOBL ·falseValue(SB),$1  // var falseValue = true
DATA ·falseValue(SB)/1,$0

int型变量

所有的整数类型均有类似的定义的方式,比较大的差异是整数类型的内存大小和整数是否是有符号。下面是声明的int32和uint32类型变量:

var int32Value int32

var uint32Value uint32

在Go语言中声明的变量不能含有初始化语句。然后下面是amd64环境的汇编定义:

GLOBL ·int32Value(SB),$4
DATA ·int32Value+0(SB)/1,$0x01  // 第0字节
DATA ·int32Value+1(SB)/1,$0x02  // 第1字节
DATA ·int32Value+2(SB)/2,$0x03  // 第3-4字节

GLOBL ·uint32Value(SB),$4
DATA ·uint32Value(SB)/4,$0x01020304 // 第1-4字节

汇编定义变量时并不区分整数是否有符号。

float型变量

Go汇编语言通常无法区分变量是否是浮点数类型,之后相关的浮点数机器指令会将变量当作浮点数处理。Go语言的浮点数遵循IEEE754标准,有float32单精度浮点数和float64双精度浮点数之分。

IEEE754标准中,最高位1bit为符号位,然后是指数位(指数为采用移码格式表示),然后是有效数部分(其中小数点左边的一个bit位被省略)。下图是IEEE754中float32类型浮点数的bit布局:

在这里插入图片描述

IEEE754浮点数还有一些奇妙的特性:比如有正负两个0;除了无穷大和无穷小还有inf非数;同时如果两个浮点数如果有序那么bit对应的整数也是有序的。

下面是在Go语言中先声明两个浮点数(如果没有在汇编中定义变量,那么声明的同时也会定义变量)。

var float32Value float32

var float64Value float64

然后在汇编中定义并初始化浮点数:

GLOBL ·float32Value(SB),$4
DATA ·float32Value+0(SB)/4,$1.5      // var float32Value = 1.5

GLOBL ·float64Value(SB),$8
DATA ·float64Value(SB)/4,$0x01020304 // bit 方式初始化

我们在上一节精简的算术指令中都是针对整数,如果要通过整数指令的处理浮点数加减法必须根据浮点数的运算规则进行:先对齐小数点,然后进行整数加减法,最后再对结果进行归一化并处理精度舍入问题。

string类型变量

从Go汇编语言角度看,字符串只是一种结构体。string的头结构定义如下:

type reflect.StringHeader struct {
	Data uintptr
	Len  int
}

在amd64环境中StringHeader有16个字节大写,因此我们先在Go代码声明字符串,然后在汇编中定义一个16字节大小的变量:

var helloworld string
GLOBL ·helloworld(SB),$16

同时我们可以为字符串准备真正的数据。在下面的汇编代码中,我们定义了一个text当前文件内的私有变量(以<>为后缀名),内容为“Hello World!”:

GLOBL text<>(SB),$16
DATA text<>+0(SB)/8,$"Hello Wo"
DATA text<>+8(SB)/8,$"rld!"

虽然text私有变量表示的字符串只有12个字符长度,但是我们依然需要将变量的长度扩展为2的指数倍数,这里也就是16个字节的长度。

然后使用text私有变量对应的内存地址来初始化字符串头结构体中的Data部分,并且手工指定Len部分为字符串的长度:

DATA ·helloworld+0(SB)/8,$text<>(SB) // StringHeader.Data
DATA ·helloworld+8(SB)/8,$12         // StringHeader.Len

需要注意的是,字符串是只读类型,要避免在汇编中直接修改字符串底层数据的内容。

slice类型变量

slice变量和string变量相似,只不过是对应的是切片头结构体而已。切片头的结构如下:

type reflect.SliceHeader struct {
	Data uintptr
	Len  int
	Cap  int
}

对比可以发现,切片的头的前2个成员字符串是一样的。因此我们可以在前面字符串变量的基础上,再扩展一个Cap成员就成了切片类型了:

var helloworld []byte
GLOBL ·helloworld(SB),$24            // var helloworld []byte("Hello World!")
DATA ·helloworld+0(SB)/8,$text<>(SB) // StringHeader.Data
DATA ·helloworld+8(SB)/8,$12         // StringHeader.Len
DATA ·helloworld+16(SB)/8,$16        // StringHeader.Len

GLOBL text<>(SB),$16
DATA text<>+0(SB)/8,$"Hello Wo"      // ...string data...
DATA text<>+8(SB)/8,$"rld!"          // ...string data...

因为切片和字符串的相容性,我们可以将切片头的前16个字节临时作为字符串使用,这样可以省去不必要的转换。

map/channel类型变量

map/channel等类型并没有公开的内部结构,它们只是一种未知类型的指针,无法直接初始化。在汇编代码中我们只能为类似变量定义并进行0值初始化:

var m map[string]int

var ch chan int
GLOBL ·m(SB),$8  // var m map[string]int
DATA  ·m+0(SB)/8,$0

GLOBL ·ch(SB),$8 // var ch chan int
DATA  ·ch+0(SB)/8,$0

在runtime包其实为汇编提供了一些辅助函数。比如在汇编中可以通过runtime.makemapruntime.makechan内部函数来创建map和chan变量。辅助函数的签名如下:

func makemap(mapType *byte, hint int, mapbuf *any) (hmap map[any]any)
func makechan(chanType *byte, size int) (hchan chan any)

需要注意的是,makemap函数可以创建不同类型的map,map的具体类型是通过mapType参数指定。

标识符规则和特殊标志

Go语言的标识符可以由绝对的包路径加标识符本身定位,因此不同包中的标识符即使同名也不会有问题。Go汇编是通过特殊的符号来表示斜杠和点符号,因为这样可以简化汇编器词法扫描部分代码的编写,只要通过字符串替换就可以了。

下面是汇编中常见的几种标识符的使用方式(通用也适用于函数标识符):

GLOBL ·pkg_name1(SB),$1
GLOBL main·pkg_name2(SB),$1
GLOBL my/pkg·pkg_name(SB),$1

此外,Go汇编中可以定义仅当前文件可以访问的私有标识符(类似C语言中文件内static修饰的变量),以<>为后缀名:

GLOBL file_private<>(SB),$1

这样可以减少私有标识符对其它文件内标识符命名的干扰。

此外,Go汇编语言还在"textflag.h"文件定义了一些标志。其中用于变量的标志有个DUPOK、RODATA和NOPTR几个。DUPOK表示该变量对应的标识符可能有多个,在链接时只选择其中一个即可(一般用于合并相同的常量字符串,减少重复数据占用的空间)。RODATA标志表示将变量定义在只读内存段,因此后续任何对此变量的修改操作将导致异常(panic也无法捕获)。NOPTR则表示此变量的内部不含指针数据,让垃圾回收器忽略对该变量的扫描。如果变量已经在Go代码中声明过的话,Go编译器会自动分析出该变量是否包含指针,这种时候可以不用手写NOPTR标志。

下面是通过汇编来定义一个只读的int类型的变量:

var const_id int // readonly
#include "textflag.h"

GLOBL ·const_id(SB),NOPTR|RODATA,$8
DATA  ·const_id+0(SB)/8,$9527

我们使用#include语句包含定义标志的"textflag.h"头文件(和C语言中预处理相同)。然后GLOBL汇编命令在定义变量时,给变量增加了NOPTR和RODATA两个标志(多个标志之间采用竖杠分割),表示变量中没有指针数据同时定义在只读代码段。

变量一般是可取地址的值,但是const_id虽然可以取地址,但是确实不能修改。不能修改的限制并不是由编译器提供,而是因为对该变量的修改会导致对只读内存段进行写导致,从而导致异常。

小结

以上我们初步演示了通过汇编定义全局变量的用法。但是实际中我们并不推荐通过汇编定义变量——因为用Go语言定义变量更加简单。在Go语言中定义变量,编译器可以帮助我们计算好变量的大小,生成变量的初始值,同时也包含了足够的类型信息。汇编语言的优势是挖掘机器的特性和性能,用汇编定义变量并无法发挥这些优势。因此在理解了汇编定义变量的用法后,建议大家谨慎使用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/512303.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【加解密篇】Passware Kit Forensic暴力美学-已知部分密码自定义解密详细参数设置

【加解密篇】Passware Kit Forensic暴力美学-已知部分密码自定义解密详细参数设置 都说"自制武器不一定是最强的&#xff0c;但最强的武器一定是自制的"&#xff0c;对于取证工具也是一样&#xff0c;虽然默认配置足够强&#xff0c;但如果我们能根据实时情景自定义…

arduino 导入 Brain 库

一、引言 最近在做一个可以用脑电波控制的arduino小车&#xff0c;需要用到Brain这个库&#xff0c;而且需要自己导入才能使用。之前试了很多方法&#xff0c;导入成功了&#xff0c;过了几个月又忘记怎么导入了&#xff0c;今天想起来记录一下&#xff0c;好记性不如烂笔头。 …

LNK2019 无法解析的外部符号,一种新的思路

LNK2019 无法解析的外部符号&#xff0c;一种新的思路 一般来说&#xff0c;这种问题的原因有如下几点&#xff1a; 函数只有声明没有实现如果是其它库的函数&#xff0c;可能没有链接lib文件如果函数是封装在dll中的&#xff0c;可能没有导出如果是模板函数或模板类&#xf…

初始MyBatis,w字带你解MyBatis

一.什么是mybatis mybatis的概念 MyBatis 是一款优秀的持久层框架&#xff0c;它支持定制化 SQL、存储过程以及高级映射。1、Mybatis是一个半ORM&#xff08;对象关系映射&#xff09;框架&#xff0c;底层封装了JDBC&#xff0c;是程序员在开发时只需要关注SQL语句本身&#…

U-boot顶层Makefile文件分析(一)

在阅读 uboot 源码之前&#xff0c;肯定是要先看一下顶层 Makefile&#xff0c;分析 gcc 版本代码的时候一定是先从顶层 Makefile 开始的&#xff0c;然后再是子 Makefile&#xff0c;这样通过层层分析 Makefile 即可了解整个工程的组织结构。顶层 Makefile 也就是 uboot 根目录…

界面控件DevExpress WPF MessageBox组件,进一步增强用户体验!

在DevExpress WPF v22.2版本中&#xff0c;官方引入了一系列功能来帮助改进一个常见WPF UI元素——MessageBox的用户体验&#xff0c;新的MessageBox相关选项包括&#xff1a; 文本选择支持自定义图像支持带有计时器的按钮&#xff0c;可以自动关闭窗口 ThemedMessageBox类允…

OJ练习第103题——最大矩形

最大矩形 力扣链接&#xff1a;85. 最大矩形 题目描述 给定一个仅包含 0 和 1 、大小为 rows x cols 的二维二进制矩阵&#xff0c;找出只包含 1 的最大矩形&#xff0c;并返回其面积。 示例 输入&#xff1a;matrix [[“1”,“0”,“1”,“0”,“0”],[“1”,“0”,“1”…

数据库管理-第七十四期 我的Oracle ACE之路(20230511)

数据库管理 2023-05-11 第七十四期 我的Oracle ACE之路1 差点错过2 ACE Associate3 一路走来总结 第七十四期 我的Oracle ACE之路 “终于等到你&#xff0c;还好我没放弃”。经过了整整4个月20天的等待&#xff0c;我终于收到了Oracle ACE Associate的确认邮件&#xff0c;成为…

『python爬虫』17. 线程池和进程池以及线程池实战(保姆级图文)

目录 1. 线程池和进程池解决的是调度问题2. 线程池3. 线程池实战&#xff1a;爬取西游记的标题总结 欢迎关注 『python爬虫』 专栏&#xff0c;持续更新中 欢迎关注 『python爬虫』 专栏&#xff0c;持续更新中 1. 线程池和进程池解决的是调度问题 ⽹站的数据太多了&#xff0…

典型随机噪声激励

1.按噪声的起源分类 根据噪声的起源&#xff0c;分为内部噪声和外部噪声。 内部噪声&#xff1a;来源于系统内部的涨落运动或被检测信号&#xff0c;如布朗粒子受到周围液体分子的无规则碰撞即为内部噪声&#xff1b; 外部噪声&#xff1a;来自系统所处外部环境的随机涨落&…

endNote X9 导入英文文献(谷歌学术、web of science 等)

文章目录 1 导入英文文献&#xff08;谷歌学术、web of science 等&#xff09;2 EndNote X9 插入参考文献常见问题总结3 EndNote X9 快速上手教程&#xff08;毕业论文参考文献管理器&#xff09; 1 导入英文文献&#xff08;谷歌学术、web of science 等&#xff09; 谷歌学术…

Vite如何实现秒级依赖预构建的能力

大家都知道, Vite是一个提倡 no-bundle 的构建工具,相比于传统的 Webpack,能做到开发时的模块按需编译,而不用先打包完再加载。这一点我们在快速上手这一节已经具体地分析过了。 需要注意的是,我们所说的模块代码其实分为两部分,一部分是源代码,也就是业务代码,另一部…

线性代数——特征值与特征向量的性质

&#xff08;1&#xff09;设A为方阵&#xff0c;则A与 A T A^{T} AT有相同的特征值。 此处用到了两个关键性质&#xff0c;一&#xff1a;单位阵的转置为其本身,二&#xff1a;转置并不改变行列式的值。 &#xff08;2&#xff09;&#xff1a; 设n阶方阵A&#xff08; a i j…

汽车电子的电磁兼容性分析

随着电子技术的不断发展&#xff0c;新技术越来越多的应用到汽车领域&#xff0c;因此各个电子装置和电子控制系统必须相互适应&#xff0c;这就是电磁兼容性(EMC)要求。为了保证汽车可靠性&#xff0c;设计师必须在早期设计阶段分析电磁兼容性问题。目前市场上推出了一些电磁兼…

jdk20-虚拟线程简单了解

普通线程 JDK中的普通线程和操作系统线程是一对一关系的&#xff0c;存在映射关系&#xff1b;由操作系统进行调度&#xff0c;线程过多&#xff0c;会引起操作系统频繁调度&#xff0c;导致线程上下文切换太过频繁&#xff0c;反而会降低系统性能&#xff1b;线程越多&#x…

即时通讯私有化部署有哪些优势?企业在选择时需要考虑哪些因素?

即时通讯私有化部署是一种将即时通讯系统部署在企业的私有服务器上&#xff0c;以保护企业的信息安全和隐私。相比于使用Saas部署即时通讯应用程序&#xff0c;私有化部署可以更好地保护企业的机密信息&#xff0c;避免信息泄露的风险。 在即时通讯私有化部署中&#xff0c;企业…

半导体热处理设备微环境中的精密微正压控制解决方案

摘要&#xff1a;针对半导体热处理设备微环境中的微正压精密控制&#xff0c;本文分析了现有技术造成微正压控制不稳定的原因&#xff0c;提出了相应的解决方案。解决方案主要是采用绝对电容真空计替代压差计&#xff0c;采用真空低漏率的高速电动针阀和电动球阀替代气体质量流…

【Python 正则表达式】零基础也能精通的正则表达式学习路线与参考资料

文章目录 一、正则表达式基础二、进阶知识三、优秀实践参考资料&#xff1a; 一、正则表达式基础 正则表达式是一种用来描述字符模式的语言&#xff0c;它可以用来匹配、查找、替换字符串中的文本。在Python中&#xff0c;我们可以使用re模块来操作正则表达式。 正则表达式基…

MATLAB三维曲面作图(三)特殊效果的绘图(灯光)

首先&#xff0c;我们来绘制一个海螺的复杂图形 close all t linspace(0,2*pi,512); [u v] meshgrid(t); a -0.4; b 0.5; c 0.1; n 3; x (a*(1-v/(2*pi)).*(1cos(u))c).*cos(n*v); y (a*(1-v/(2*pi)).*(1cos(u))c).*sin(n*v); z b*v/(2*pi)a*(1-v/(2*pi)).*sin(u); s…

RabbitMQ详解(五):过期时间TTL、死信队列、磁盘监控

过期时间TTL&#xff08;Time To Live&#xff09; 过期时间TTL表示可以对消息设置预期的时间&#xff0c;在这个时间内都可以被消费者接收获取&#xff1b;过了之后消息将自动被删除。 目前有两种方法可以设置 第一种方法是通过队列属性设置&#xff0c;队列中所有消息都有…