数字类型
IA-32平台包含可以在汇编语言程序中使用的几种不同的数字数据类型。
核心的数字数据类型如下:
- 无符号整数
- 带符号整数
- 二进制编码的十进制
- 打包的二进制编码的十进制
- 单精度浮点数
- 双精度浮点数
- 双精度扩展浮点数
除了基本的数字数据类型之外,奔腾处理器的SIMD扩展还添加了其他高级数字数据类型:
- 64位打包整数
- 128位打包整数
- 128位打包单精度浮点数
- 128位打包双精度浮点数
虽然可用的数字数据类型的清单很长,但是在汇编语言中处理不同数据类型的数字是相对容易的。
整数
谈到整数我们首先需要了解下字节序:
https://www.jianshu.com/p/b4eab54a6bcc
https://zhuanlan.zhihu.com/p/35661391
https://juejin.im/post/5d7f45f9e51d4561e43a6d50
https://www.ruanyifeng.com/blog/2016/11/byte-order.html
字节序与bit序:
https://www.linuxjournal.com/article/6788?page=0,0
https://blog.ykyi.net/2016/09/%E5%8E%9F%E6%9D%A5%E6%AF%94%E7%89%B9%E4%BD%8D%E4%B9%9F%E6%9C%89%E5%A4%A7%E5%B0%8F%E7%AB%AFendinness-%E5%AD%97%E8%8A%82%E5%BA%8F%E5%92%8C%E6%AF%94%E7%89%B9%E5%BA%8F%E7%9A%84%E6%95%85%E4%BA%8B/
http://www.jeepxie.net/article/411653.html
https://blog.csdn.net/liuxingen/article/details/45420455
寄存器字节序:
https://www.thinbug.com/q/4504775 (寄存器的字节序是没有意义的,并且rex中含有的eax,ax,al的位置跟寄存器的字节序也无关)
内存的字节序跟处理器中寄存器的字节序可能是不一样的,但是这点不会对我们产生任何影响,处理器会自己完成这个转换,并且如上面讲过的,寄存器的字节序对我们是没有意义的。
基本的lA-32 平台支持4种不同的整数长度:
- 字节(Byte): 8 位
- 字( Word) : I 6位
- 双字(Do ubl eword): 32位
- 四字(Q u adword): 64位
无符号整数
无符号整数几乎是“所见即所得"的数据类型。组成整数的字节的值直接表示整数值。
根据使用的位的数目,4种不同长度的无符号整数可以生成4种不同数值范围的无符号整数。
下表列出了这些长度:
位 | 整数值 |
---|---|
8 | 0 到255 |
16 | 0 到65 535 |
32 | 0到4 294 967 295 |
64 | 0到18 446 744 073 709 551 615 |
带符号整数
虽然使用无符号整数很容易,但是其缺陷是没有办法表示负数。为了解决这个问题,在处理器上需要采用能够表示负数的方法。有3种方法用于在计算机中描述负数:
- 带符号数值
- 反码(One’s complement)
- 补码(Two’s complement)
所有这3种方法都使用和无符号整数相同的位长度(字节、乓字、“双字和四字),但是在位中表示十进制值的方式是不同的。IA-32平台使用补码方式表示带符号整数,但是了解每种方法如何工作是有好处的,参考之前的文章:原码,反码,补码
扩展整数
- 扩展无符号整数
把无符号整数值转换为位数更大的值时(比如把字转换为双字),必须确保所有的高位部分都被设置为零。不应该简单地把一个值复制给另一个值,比如:
movw %ax , %bx
这样不能保证EBX寄存器的高位部分包含零。为了完成这个操作,必须使用两条指令
movl $0, %ebx
movw %ax, %ebx
MOVL指令用于把零值加载到EBX寄存器中。这样保证EBX寄存器的每一位都是0 。然后可以安全地把AX寄存器中的无符号整数值传送给EBX寄存器。
为了帮助程序员应付这种情况,Intel提供了MOVZX指令。这条指令把长度小的无符号整数值(可以在寄存器中,也可以在内存中)传送给长度大的无符号整数值(只能在寄存器中) 。
MOYZX 指令的格式如下:
movzx source, destination
其中source可以是8位或16位寄存器或者内存位置,destination可以是16位或者32位寄存器。
- 扩展带符号整数
扩展带符号整数值和扩展无符号整数是大同的。使用零填充高位会改变负数的数据值。例如,把值-1(11111111)传送给双字会生成值0000000011111111,在带符号整数表示法中它是+127,而不是-1。为了使带符号扩展能够起作用,新添加的位必须被设置为l。因此,新的双字将生成值llllllllllllllll, 这是带符号整数表示法的值-1, 这是正确的。
Intel 提供了MOVSX指令,它允许扩展带符号整数并且保留符号。它和MOVZX 指令类似,但是它假设要传送的字节是带符号整数格式,并且试图在传送过程中保持带符号整数的值不变。
SIMD整数
Intel的单指令多数据(Single Instruction Multiple Data,SIMD)技术提供了定义整数的其他方式。这些新的整数类型使处理器可以同时对一组多个整数执行数学运算操作。
SIMD 架构使用打包的整数数据类型。打包的整数是能够表示多个整数值的一系列字节。可以把字节系列看作一个整体,对它执行数学操作,并行地处理系列中的各个整数值。
MMX整数
多媒体扩展(Multimedia Extension, MMX)技术是在奔腾MMX和奔腾II处理器中引人的,它提供3种新的整数类型:
- 64位打包字节整数
- 64位打包字整数
- 64位打包双字整数
以上每种数据类型都提供把多个整数数据元素包含到(或者说打包到)单一的64位MMX寄存器中的能力。
8 个字节整数、4个字整数或者2个双字整数都可以打包到单一的64位MMX寄存器中。
IA-32中MMX 寄存器被映射到FPU 寄存器,所以使用MMX寄存器时要小心。
记住,在使用任何MMX寄存器指令之前,都要把FPU寄存器中存储的所有数据保存到内存中。
MMX平台提供了对MMX寄存器中打包的每个整数值执行井行数学操作的附加指令。
传送MMX整数
可以使用MOVQ指令把数据传送到MMX寄存器中,但是必须决定当前应用程序将使用3 种打包整数格式中的哪一种。MOVQ指令的格式如下:
movq source, destination
其中source和destination可以是MMX寄存器、SSE寄存器或者64位的内存位置(但是不能在内存位置之间传送MMX整数)。
SSE 整数
流化SIMD扩展(Streaming SIMD Extension, SSE) 技术提供用于处理打包数据的8 个128 位XMM 寄存器(名为XMM0 到XMM7) 。SSE2技术(奔腾4处理器中引入的)提供4种额外的打包带符号整数数据类型:
- 128位打包字节整数
- 128 位打包字整数
- I 28位打包双字整数
- 128位打包四字整数
这些值被打包在128 位XMM寄存器中。可以把16 个字节整数、8 个字整数、4个双字整数或者2个四字整数打包在单一128位SSE寄存器中。
SSE平台提供附加的指令,用于对SSE寄存器中的打包数据值执行井行的数学操作。这使处理器可以使用相同的时钟周期处理多得多的信息。
传送SSE整数
MOVDQA和MOVDQU指令用于把128位数据传送到XMM寄存器中,或者在XMM寄存器之间传送数据。助记符的A和U部分代表对准和不对准,它们表示数据是如何存储在内存中的。对于对准16个字节边界的数据,就使用A选项;否则,就使用U选项。MOVDQA和MOVDQU指令的格式如下:
movdqa source, destination
其中source和destination可以是SSE 128位寄存器或者128位的内存位置(但是再次强调,不能在两个内存位置之间传送数据)。当使用对准的数据时,SSE指令执行得更快。还有,如果程序对未对准的数据使用MOVDQA 指令,就会造成硬件异常。
二进制编码的十进制
一进制编码的十进制(Binary Coded Decimal, BCD) 数据类型在计算机系统中已经存在很久了。BCD格式经常用于简化对使用十进制数字的设备(比如必须向人显示数字的设备,如时钟和计时器)的处理。处理器不是把十进制数字转换为二进制数字以便进行数学操作,然后再转换回十进制。
而是可以按照BCD格式保存数字井且执行数学操作。了解BCD如何工作以及处理器如何使用它,能对汇编语言程序设计有所帮助。下面几节介绍BCD格式和IA-32平台如何处理BCD数据类型。
BCD 的名称就说明了它的作用,它按照二进制格式对十进制数字进行编码。每个BCD值都是一个无符号8位整数,值的范围是0到9 。在BCD中,大于9 的8位值被认为是非法的。包含BCD值的字节组合在一起表示十进制的数位。在多字节的BCD值中,最低的字节保存十进制的个位的值,下一个较高位字节保存十位的值,依此类推。
BCD使用整个字节表示每个十进制数位,这样浪费了空间。打包的BCD被创建出来,帮助弥补这一损失。打包的BCD允许单一字节包含两个BCD 值。字节的低4位包含低位的BCD值,字节的高4位包含高位的BCD值。
可以看到, 即使是打包的BCD,效率也不高。使用4个字节,打包的BCD 只能表示从0到9999的数字。在无符号整数值中,使用4个字节可以表示的最大值是4292967295。
FPU BCD
FPU寄存器可以用于在FPU之内进行BCD数学运算操作。FPU包含8 个80位寄存器(从ST0到ST7),也可以使用它们保存80位BCD值。使用低位的9个字节存储BCD 值,格式是打包BCD,每个字节包含两个BCD值(产生18 个BCD数位)。大多数情况下,除了最高位的一位之外,不使用FPU寄存器的最高字节。这一位用作符号指示符一一1表示负的BCD值,0表示正值。
为了把BCD值加载到FPU寄存器,必须使用80位打包BCD格式在内存中创建值。值被传送给FPU寄存器之后,它就被自动地转换为扩展双精度浮点格式。在FPU中,对数据进行的任何数学操作都是按照浮点格式进行的。当准备从FPU获取结果时,浮点值被自动转换为80位打包BCD格式。
传送BCD值
IA-32 指令集包含处理80位打包BCD值的指令。可以使用FBLD和FBSTP指令把80位打包BCD值加载到FPU寄存器中以及从FPU寄存器获取这些值。
使用FPU寄存器的方式和使用通用寄存器稍微有些区别。8个FPU寄存器的行为类似于内存中的堆栈区域。可以把值压入和弹出FPU寄存器池。ST0 引用位于堆栈顶部的寄存器。当值被压入FPU寄存器堆栈时,它被存放在ST0寄存器中,ST0中原来的值被加载到ST1中。
FBLD指令用于把打包80位BCD值传送到FPU寄存器堆栈中。它的格式很简单:
fbld source
其中source是80位的内存位置。
浮点数
- 浮点格式
浮点格式使用科学记数法表示实数,科学记数法把数字表示为系数(coefficient) (也称为尾数(mantissa) )和指数(exponent) 。
实数25.92可以表示为2.592*10^1 。值2.592是系数,值10^1 是指数,基数(或底数)是10。必须把系数和指数相乘,才能获得原始的实数。
- 二进制浮点格式
计算机系统使用二进制浮点数,这种格式使用二进制科学记数法格式表示值。因为数字按照二进制格式表示,所以系数和指数都基于二进制值,而不是十进制值。
为了对二进制浮点值进行译码,必须首先了解二进制小数数字的意义。在十进制领域中,我们习惯于看到0.159这样的值。这个值表示的是0 + (1/10) + (5/100) + (9/1000) 。相同的原则也应用于二进制领域。
系数值1.0101 乘以指数2^2应该生成二进制值101.01,它表示十进制整数5, 加上分数(0/2) +(1/4) 。这生成十进制值5.25。
二进制小数数字是处理浮点值的过程中最容易混淆的部分。
二进制 | 十进制分数 | 十进制值 |
---|---|---|
0.1 | 1/2 | 0.5 |
0.01 | 1/4 | 0.25 |
0.001 | 1/8 | 0.125 |
0.0001 | 1/16 | 0.0625 |
0.00001 | 1/32 | 0.03125 |
0.000001 | 1/64 | 0.015625 |
二进制 | 十进制分数 | 十进制值 |
---|---|---|
10.101 | 2+1/2+1/8 | 2.625 |
10011.001 | 19+1/8 | 19.125 |
10110.1101 | 22+1/2+1/4+1/16 | 22.8125 |
1101.011 | 13+1/4+1/8 | 13.375 |
编写二进制浮点值时,二进制值通常被规格化了。这个操作把小数点移动到最左侧的数位并且修改指数进行补偿。例如值1101.011 变成了1.101011 *2^3。
标准浮点数据类型
虽然可能的实数值的数量是无限的,但是处理器用来处理值的位的数量是有限的。出于这个原因,创建了一个标准系统用于在计算机环境中近似地表示实数。虽然近似方式并不完美,但是它们提供了处理现实的实数系统的子集的系统。
1985年,电气和电子工程师学会(Institute of Electrical and Electronics Engineers, IEEE) 创建了称为IEEE标准754的浮点格式。这些格式用于在计算机系统中通用地表示实数。Intel 在IA-32平台中采用这种标准来表示浮点值。
IEEE标准754浮点标准使用3个成分把实数定义为二进制浮点值:
- 符号
- 有效数字
- 指数
符号位表示值是负的还是正的。符号位中的1 表示负值, 0表示正值。
有效数字部分表示浮点数的系数(coefficient) (或者说尾数(mantissa) )。系数可以是规格化的(normalized),也可以是非规格化的(denormalized) 。
当二进制值被规格化时,它写为小数点前有个1。指数被修改,表示移动了多少位才可以实现规格化(和科学记数法的方法类似)。
这意味着在规格化的值中,有效数字永远由1和二进制小数构成。
指数表示浮点数的指数部分。因为指数值可以是正值,也可以是负值,所以通过一个偏差值对它进行置偏。这样确保指数字段只能是无符号正整数。这还限制了这种格式中可用的最小和最大指数值。二进制浮点数的一般格式如图所示。
浮点数的这3个部分被包含在固定长度的数据格式之内。IEEE标准754定义了浮点数的两种长度:
- 32位(称为单精度)
- 64位(称为双精度)
可以用于表示有效数字的位的数最决定精度。
单精度浮点数使用23位有效数字值。但是,浮点格式假设有效数字的整数值永远是l, 并且不在有效数字值中使用它。这样实际上使有效数字的精度达到了24位。指数使用8位值,偏差值为127。这就是说指数值的范围是- 128到+ 127 (二进制指数)。这种组合生成的单精度浮点数的十进制范围是1.18*10^-38 到 3.40* 10^38
。
双精度浮点数使用52位小数值,它提供有效数字的精度为53位。指数使用11位值,偏差值为1023。这就是说指数值的范围是-1022 到+1023(二进制指数)。这种组合生成的双精度浮点数的十进制范围是2.23*10^-308 到 l.79*10^308
。
IA-32浮点值
IA-32平台使用IEEE标准754的单精度和双精度浮点格式,还使用它自己的80位格式,称为扩展双精度浮点格式。在执行浮点运算时,这3 种格式提供不同的精度级别。在浮点运算处理的过程中,扩展双精度浮点格式使用在80位FPU寄存器内。
Intel 的80位扩展双精度浮点格式使用64位作为有效数字,使用15位作为指数。扩展双精度浮点格式使用的偏差值为16383, 生成的指数范围是-16382 到+16383, 相应的十进制范围是3.37* 10^-4932 到 1.18*10^4932
。
下表总结在标准IA-32平台上使用的3 种类型的浮点格式。
数据类型 | 长度 | 有效数字位 | 指数位 | 范围 |
---|---|---|---|---|
单精度 | 32 | 24 | 8 | 1.18*10^-38 到 3.40*10^38 |
双精度 | 64 | 53 | 11 | 2.23*10^-308 到 l.79*10^308 |
扩展双精度 | 80 | 64 | 15 | 3.37* 10^-4932 到 1.18*10^4932 |
传送浮点值
FLD指令用于把浮点值传送入和传送出FPU 寄存器。FLD指令的格式是:
fld source
其中source可以是32 位、64位或者80位内存位置。
IA-32 的FLD 指令用于把存储在内存中的单精度和双精度浮点数加载到FPU寄存器堆栈中。
为了区分这两种数据长度,GNU汇编器使用FLDS 指令加载单精度浮点数, 而使用FLDL指令加载双精度浮点数。
类似地, FST指令用于获取FPU寄存器堆栈中顶部的值,并且把这个值存放到内存位置中。对于单精度数字,使用的指令是FSTS,双精度数字使用的指令是FSTL 。
IA-32指令集包含一些预置的浮点值,可以把它们加载到FPU寄存器堆栈中。下表列出了这些指令。
指令 | 描述 |
---|---|
FLDI | 把+1.0 压人FPU 堆栈中 |
FLDL2T | 把10的对数(底数2) 压入FPU 堆栈中 |
FLDL2E | 把e的对数(底数2) 压入FPU 堆栈中 |
FLDPI | 把pi 的值压入FPU 堆栈中 |
FLDLG2 | 把2的对数(底数10) 压人FPU 堆栈中 |
FLDLN2 | 把2的对数(底数e) 压入FPU 堆栈中 |
FLDZ | 把+0.0压人FPU 堆栈中 |
这些指令提供把常用数学值压入FPU堆栈的简便方式,以便对数据进行操作。大家可以发现FLDZ指令有些奇怪。在浮点数据类型中,+0.0 和-0.0之间是有区别的。对于大多数操作,它们被认为是相同的值,但是使用在除法中时,它们产生不同的值(正无穷大和负无穷大) 。
SSE浮点数据类型
除了3种标准浮点数据类型之外,实现SSE技术的Intel处理器还包含两种高级浮点数据类型。SSE技术引入了8个128位XMM寄存器,可以使用这些寄存器保存打包浮点数。
和打包BCD的概念类似,打包浮点数使多个浮点值可以存储在单一寄存器中。可以使用多个数据元素并行地执行浮点计算,这会比串行处理数据更快地生成结果。
下面是可用的2种新的128位浮点数据类型:
- 128位打包单精度浮点( SSE 中)
- 128位打包双精度浮点( SSE2 中)
因为一个单精度浮点值需要32位,所以128位寄存器可以保存4个打包单精度浮点值。类似地,它可以保存2个64位打包双精度浮点值。
这些新的数据类型在FPU或者MMX寄存器中是不可用的。只能在XMM 寄存器中使用它们,并且只能在支持SSE或者SSE2的处理器上使用。必须使用专门的指令加载和获取数据值,同样必须使用专门的数学指令对打包浮点数据进行数学操作。
传送SSE浮点值
正如我们期望的, IA-32指令集包含用于在处理器中传送新的SSE浮点数据类型值的指令。这些指令分为对打包单精度浮点数据进行操作的SSE指令,以及对打包双精度浮点数据进行操作
的SSE2指令。
- SSE浮点值
有一个完整的指令集用于在内存和处理器上的XMM寄存器之间传送128 位打包单精度浮点值。
下表列出用于传送SSE打包单精度浮点数据的指令。
指令 | 描述 |
---|---|
MOVAPS | 把4个对准的打包单精度值传送到XMM 寄存器或者内存 |
MOVUPS | 把4个不对准的打包单精度值传送到XMM 寄存器或者内存 |
MOVSS | 把1 个单精度值传送到内存或者寄存器的低双宁 |
MOVLPS | 把2 个单精度值传送到内存或者寄存器的低四字 |
MOVHPS | 把2 个单精度值传送到内存或者寄存器的高四宇 |
MOVLHPS | 把2个单精度值从低四字传送到高四字 |
MOVHLPS | 把2个单精度值从高四字传送到低四字 |
这些指令的每一条都使用128位XMM寄存器在XMM寄存器和内存之间传送打包32位单精度浮点值。不仅可以传送整组的打包单精度浮点值,也可以在XMM寄存器之间传送2个打包单精度浮点值的子集。
- SSE2 浮点值
和SSE数据类型类似,IA-32平台包含用于传送新的SSE2打包双精度浮点数据类型的指令。
下表列出可以使用的新指令。
指令 | 描述 |
---|---|
MOVAPD | 把2个对准的双精度值传送到XMM寄存器或者内存 |
MOVUPD | 把2个不对准的双精度值传送到XMM寄存器或者内存 |
MOVSD | 把1 个双精度值传送到内存或者寄存器的低四字 |
MOVHPD | 把1 个双精度值传送到内存或者寄存器的高四宇 |
MOVLPD | 把1 个双精度值传送到内存或者寄存器的低四字 |
###转换
IA-32指令集包含众多指令,用于把以一种数据类型表示的数据转换为另一种数据类型。程序需要把浮点数据转换为整数值(或者相反的转换)的情况并不少见。这些指令提供完成这种操作的简便方式,无需编写自己的算法。
转换指令
有很多指令用于转换数据类型,因为有不同的数据类型需要进行相互转换。下表介绍转换指令。
指令 | 转换 |
---|---|
CVTDQ2PD | 打包双字整数到打包双精度FP (XMM) |
CVTDQ2PS | 打包双字整数到打包单精度FP (XMM) |
CVTPD2DQ | 打包双精度FP到打包双字整数(XMM) |
CVTP02PI | 打包双精度FP到打包双字整数(MMX) |
CVTPD2PS | 打包双精度FP到打包单精度FP (XMM) |
CVTPI2PD | 打包双字整数到打包双精度FP (XMM) |
CVTPI2PS | 打包双字整数到打包单精度FP (XMM) |
CVTPS2DQ | 打包单精度FP到打包双字整数(XMM) |
CVTPS2PD | 打包单精度FP到打包双精度FP (XMM) |
CVTPS2PI | 打包单精度FP到打包双字整数(MMX) |
CVTTPD2PI | 打包双精度FP到打包双字整数(MMX , 截断) |
CVITPD2DQ | 打包双精度FP到打包双字整数(XMM , 截断) |
CVTIPS2DQ | 打包单精度FP到打包双字整数(XMM, 截断) |
CVTTPS2PI | 打包单精度FP到打包双字整数(MMX, 截断) |
上表中的描述涉及可以在其中存放结果的目标寄存器,目标寄存器可以是MMX寄存器,也可以是XMM寄存器。另外,最后4条指令是截断的转换。在其他指令中,如果转换不精确,就
会由XMM MXCSR寄存器的13位和14位控制进行舍入。这些位确定值被向上入还是向下舍。在截断的转换中,会自动执行向零方向的舍入。
源值可以从内存位置、MMX寄存器(对于64位值)或者XMM寄存器(对于64位或者128位值)获得。
上述笔记内容学习自 AT&T Professional Assembly Language — Chapter VII