一、数字数据类型
IA-32平台下核心数字数据类型如下:
- 无符号整数
- 带符号整数
- 二进制编码的十进制
- 打包的二进制编码的十进制
- 单精度浮点数
- 双精度浮点数
- 双精度扩展浮点数
SIMD扩展还添加了其他高级数字数据类型:
- 64位打包整数
- 128位打包整数
- 128位打包单精度浮点数
- 128位打包双精度浮点数
二、整数
1、标准整数长度
基本IA-32平台支持4种不同的整数长度:
- 字节(Byte):8位
- 字(Word):16位
- 双字(Doubleword):32位
- 四字(Quadword):64位
储存在内存中的超过1个字节的整数被存储为小端数(little-endian)格式。也就是,低位字
节储存在最低的内存位置,其余字节顺序储存在它之后。但把整数值传送给寄存器时,值按照
大端数(big-endian)格式存储在寄存器中。
2、无符号整数
根据使用的位的数目,4种不同长度的无符号整数可以生成4种不同数值范围的无符号整数。
8位整数值包含在单一字节之内。字节中包含的二进制就是实际的整数值。
16位无符号整数包含在两个连续的字节中,它们组合在一起构成一个字(word)。下面显
示储存在寄存器中的字值的例子。
32位无符号整数值(以小端格式)包含在4个连续的字节中,它们组合在一起构成双字。双
字是IA-32平台上最常用的无符号整数格式。下图是双字大端格式,就像寄存器中那样:
64位无符号整数值包含在8个连续的字节中,它们组合在一起构成四字。下图是四字的例
子:
3、带符号整数
为了解决负数问题,有三种方法在计算机中用于描述负数;
- 带符号数值
- 反码(One‘s complement)
- 补码(Two’s complement)
所有这三种方法都使用和无符号整数相同的位长度(字节、字、双字和四字),但是在位中
表示十进制值的方式是不同的。IA-32平台使用补码方式表示带符号整数。
1)带符号数值
带符号数值的方法把组成带符号整数的位分为两部分:符号位和数值位。字节的最大有效
位(最左侧的一位)用于表示值的符号。正数的最大有效位包含0,而负数的这个位置是1。值
中的其余位使用一般的二进制值表示数字的数字,如下图:
带符号数值的一个问题是有两种不同的表示0值的方式:00000000(十进制的+0)和
10000000(十进制的-0)。简单的带符号整数的加法和减法不能按照无符号数字的方式进行。
2)反码
反码方法采用无符号整数的相反代码生成相应的负值。因此,00000001的反码就是
11111110。同样对于带符号数字,当执行数学操作时,反码数字会产生一些问题。
3)补码
补码通过使用简单的数学技巧,解决了带符号数值和反码方法的数学运算问题。对于负整
数值,值的反码加上1就是它的反码。例如00000001的反码,结果是11111110,反码加1就是它
的补码11111111。
带符号数的最大值是无符号值的一半,如下表:
4、使用带符号整数
内存或者寄存器中表示的带符号整数经常难以识别,除非知道期望的是什么。如下面的程
序:
.section .data
data:
.int -45
.section .text
.globl _start
_start:
movl $-345, %ecx
movw $0xffb1, %dx
movl data, %ebx
movl $1, %eax
int $0x80
下面是gdb中调试的结果:
可以看到ECX和EBX显示了正确的答案,而EDX出现了问题,因为调试器试图把整个EDX
寄存器作为带符号符号整数数据值显示,所以它假设整个EDX寄存器包含一个双字带符号整数
(32位)。因为EDX寄存器只包含一个单字整数(16位),所以解释出的值是错误的。所以寄
存器中的数据仍然是正确的(0xffb1),但是调试器认为的这个数字表示的内容是错误的。
5、扩展整数
1)扩展无符号整数
把无符号整数值转换为位数更大的值时(比如把字转换为双字),必须确保所有的高位部分
都被设置为0。不应该简单地把一个值复制给另外一个值,比如:
movw %aw, %bx
这样不能保证EBX寄存器的高位部分包含零。为了完成这个操作,必须使用两条指令:
movl $0, %ebx
movw %ax,%ebx
为了帮助程序员应付这种情况,Intel提供了MOVZX指令。这条指令把长度小的无符号整数
值(可在寄存器中,也可以在内存中)传送给长度大的无符号整数值(只能在寄存器中)。格
式如下:
movzx source, destination
/*其中source可以是8位或16位寄存器或者内存位置,destination可以是16位或者32位寄存器*/
.section .text
.globl _start
_start:
movl $279, %ecx
movzx %cl, %ebx
movl $1, %eax
int $0x80
2)扩展带符号整数
为了使带符号扩展能够起作用,新添加的位必须被设置为1。Intel提供了MOVSX指令,它
允许扩展带符号整数并且保留符号。
.section .text
.globl _start
_start:
movw $-79, %cx
movl $0, %ebx
movw %cx, %bx
movsx %cx, %eax
movl $1, %eax
movl $0, %ebx
int $0x80
6、在GNU汇编器中定义整数
.quad命令创建四字的带符号整数值,可以定义一个或者多个带符号整数值,但是为每个值
分配8个字节。
.section .data
data1:
.int 1, -1, 463345, -333252322, 0
data2:
.quad 1, -1, 4663345, -3332523322, 0
.section .text
.globl _start
_start:
movl $1, %eax
movl $0, %ebx
int $0x80
三、SIMD整数
Intel的单指令多数据(Single Instruction Multiple Data,SIMD)技术提供了定义整数的其他
方式。这些新的整数类型使处理器可以同时对一组多个整数执行数学运算操作。SIMD架构使用
打包的整数数据类型,打包的整数是能够表示多个整数值的一系列字节。可以把字节系列看作
一个整体,对它执行数学操作,并行地处理系列中的各个整数值。
1、MMX整数
多媒体扩展(Multimedia Extension,MMX)提供三种新的整数类型:
- 64位打包字节整数
- 64位打包字整数
- 64位打包双字整数
以上每种数据类型都提供把多个整数数据元素包含到(或者说打包到)单一的64位MMX寄
存器中的能力。下图演示如何把每种数据类型填充到64位寄存器中。
2、传送MMX整数
可以使用MOVQ指令把数据传送到MMX寄存器中,但是必须决定当前应用程序使用3种打包
整数格式中的哪一种。MOVQ指令的格式如下:
movq source, destination
/*其中source和destination可以是MMX寄存器、SSE寄存器或者64位寄存器
(但是不能在内存位置之间传送MMX整数)*/
mmxtest.s程序演示了把双字和字节整数加载到MMX寄存器中:
.section .data
values1:
.int 1, -1
values2:
.byte 0x10, 0x05, 0xff, 0x32, 0x47, 0xe4, 0x00, 0x01
.section .text
.globl _start
_start:
movq values1, %mm0
movq values2, %mm1
movl $1, %eax
movl $0, %ebx
int $0x80
3、SSE整数
流化SIMD扩展(Streaming SIMD Extension,SSE)技术提供用于处理打包数据的8个128
位XMM寄存器(名为XMM0到XMM7)。SSE2计数提供了4种额外的打包带符号整数数据类
型:
- 128位打包字节整数
- 128位打包字整数
- 128位打包双字整数
- 128位打包四字整数
这些值被打包在128位XMM寄存器中,如下图所示:
4、传送SSE整数
MOVDQA和MOVDQU指令用于把128位数据传送到XMM寄存器中,或者在XMM寄存器之间
传送数据。助记符A和U部分代表对准和不对准,它们表示数据是如何存储在内存中的。对于对
准16个字节边界的数据,就使用A选项;否则,就使用U选项。格式如下:
movdqa source, desination
/*其中source和destination可以是SSE128位寄存器或者128位的内存位置
(不能在两个内存位置之间传送数据)。当使用对准数据时,SSE指令执行得更快。
如果程序未对准的数据使用MOVDQA指令,就会造成硬件异常*/
ssetest.s程序演示把128位数据传送到SSE寄存器中:
四、二进制编码的十进制
二进制编码的十进制(Binary Coded Decimal,BCD)数据类型。
1、BCD是什么
它按照二进制格式对十进制数字进行编码。每个BCD值都是一个无符号8位整数,值的范围
是0到9。在BCD中大于9的8位值被认为是非法的。包含BCD值的字节组合在一起表示十进制的
数位。在多字节的BCD值中,最低的字节保存十进制的个位的值,下一个较高字节保存十位的
值,依此类推。
BCD用整个字节表示每隔十进制数位浪费了空间,下图用打包的方式一个字节包含两个
BCD值,字节的低4位包含低位的BCD值,字节的高4位包含高位的BCD值。
2、FPU BCD值
FPU寄存器可以用于在FPU之内进行BCD数学运算操作。FPU包含8个80位寄存器(从
ST0到ST7),也可以使用它们保存80位BCD值。使用地位的9个字节存储BCD值,格式是打
包BCD,每个字节包含两个BCD值(产生18个BCD数位)。
3、传送BCD值
可以使用FBLD和FBSTP指令把80位打包BCD值加载到FPU寄存器中以及从FPU寄存器获
取这些值。8个FPU寄存器的行为类似于内存中的堆栈区域。可以把值压入和弹出FPU寄存器
池。ST0引用位于堆栈顶部的寄存器,当值被压入FPU寄存器堆栈时。它被存放在ST0寄存器
中,ST0中原来的值被加载到ST1。
FBLD指令用于把打包80位BCD值传送到FPU寄存器堆栈中,格式如下:
fbld source
/*其中source是80位的内存位置*/
.section .data
data1:
.byte 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
data2:
.int 2
.section .text
.globl _start
_start:
fbld data1
fimul data2
fbstp data1
movl $1, %eax
movl $0, %ebx
int $0x80
五、浮点数
1、浮点数是什么
整数表示用于计数的数字。最终,负数的概念被包含在整数中,成为带符号整数数字系
统。整数和BCD数据类型都只能包含整数。
1)浮点格式
浮点格式使用科学记数法表示实数。科学记数法把数字表示为系数(coefficient)(也称为
尾数(mantissa))和指数(exponent),比如3.6845*10^2。
2)二进制浮点格式
计算机系统使用二进制浮点数,这种格式使用二进制科学记数法格式表示值。因为数字按
照二进制格式表示,所以系数和指数都基于二进制值,而不是十进制值。这种格式如
1.0101*2^2。处理系统的小数部分(小数点后面的部分)容易引起混乱。
二进制浮点数值的几个例子如下:
2、标准浮点数数据类型
IEEE标准754浮点标准使用3个成分把实数定义为二进制浮点数:
- 符号
- 有效数字
- 指数
符号位表示值是负的还是正的。符号位中1表示负值,0表示正值。
有效数字部分表示浮点数的系数(coefficient)(或者说尾数(mantissa))。系数可以是
规格化的(normalized) ,也可以是非规格化的(denormalized)。当二进制值被规格化时,
它写为小数点前有个1。指数被修改,表示移动了多少位才可以实现规格化,这意味着在规格化
的值中,有效数字永远由1和二进制小数构成。
指数表示浮点数的指数部分,因为指数值可以是正值,也可以是负值,所以通过一个偏差
值对它进制置偏。这样确保指数字段只能是无符号正整数。这还限制了这种格式中可用的最小
和最大指数值。
浮点数的这3个部分被包含在固定长度的数据格式之内。IEEE标准754定义了浮点数的两种长度:
- 32位(称为单精度)
- 64位(称为双精度)
下图显示了这两种不同精度类型的位的布局。
单精度浮点数使用23位有效数字值。符号格式假设有效数字的整数值永远是1,并不在有
效数字值中使用它。这样实际上使用有效数字的精度达到了24位。指数使用8位值,偏差值为
127。这就是说指数值的范围是-128到+127(二进制指数)。这种组合生成的单精度浮点数的
十进制范围是1.18*10^-38(1.00...0×2^-126)到3.40*10^38( 1.1111...1×2^127 )。
双精度浮点数使用52位小数值,它提供有效数字的精度为53位。指数使用11位值,偏差
值为1023.这就是说指数值的范围是-1024到+1023。这种组合生成的双精度浮点数的十进制范
围是2.23*10^-308到1.79*10^308。
3、IA-32浮点值
IA-32使用IEEE标准754的单精度和双精度浮点格式,还使用自己的80位格式,称为扩展双
精度浮点格式,使用64位作为有效数字,使用15位作为指数,偏差值16383,指数范围-16384
到+16383,相应十进制范围是3.37*10^-4932到1.18*10^4932。
4、在GNU汇编器中定义浮点值
.float命令用于创建32位单精度值,.double命令用于创建64位双精度值。
5、传送浮点值
FLD命令用于把浮点值传送入和传送出FPU寄存器。格式如下:
fld source
/*其中source可以是32位、64位或者80位内存位置*/
.section .data
value1:
.float 12.34
value2:
.double 2353.631
.section .bss
.lcomm data, 8
.section .text
.globl _start
_start:
flds value1 #把单精度浮点值压入FPU寄存器堆栈
fldl value2 #把双精度浮点值压入FPU寄存器最栈
fstl data #FST指令用于获取FPU寄存器堆栈
movl $1, %eax
movl $0, %ebx
int $0x80
6、使用预置的浮点值
.section .text
.globl _start
_start:
fld1
fldl2t
fldl2e
fldpi
fldlg2
fldln2
fldz
movl $1, %eax
movl $0, %ebx
int $0x80
7、SSE浮点数据类型
下面可用2种新的128位浮点数据类型:
- 128位打包单精度浮点(SSE中)
- 128位打包双精度浮点(SSE2中)
8、传送SSE浮点值
1)SSE浮点值
有一个完整的指令集用于在内存和处理器上的XMM寄存器之间传送128位打包单精度浮点
值。
.section .data
value1:
.float 12.34, 2345.543, -3493.2, 0.000003
value2:
.float -5439.234, 32121.4, 1.0094, 0.000003
.section .bss
.lcomm data, 16
.section .text
.globl _start
_start:
movups value1, %xmm0
movups value2, %xmm1
movups %xmm0, %xmm2
movups %xmm0, data
movl $1, %eax
movl $0, %ebx
int $0x80
2)SSE2浮点值
SSE2打包双精度浮点数据类型指令。
.section .data
value1:
.double 12.34, 2345.543
value2:
.double -5439.234, 32121.4
.section .bss
.lcomm data, 16
.section .text
.globl _start
_start:
movupd value1, %xmm0
movupd value2, %xmm1
movupd %xmm0, %xmm2
movupd %xmm0, data
movl $1, %eax
movl $0, %ebx
int $0x80
3)SSE3浮点值
- MOVSHDUP
- MOVSLDUP
- MOVDDUP
六、转换
1、转换指令
2、转换范例
.section .data
value1:
.float 1.25, 124.79, 200.0, -312.5
value2:
.int 1, -435, 0, -25
.section .bss
.lcomm data, 16
.section .text
.globl _start
_start:
cvtps2dq value1, %xmm0
cvttps2dq value1, %xmm1
cvtdq2ps value2, %xmm2
movdqu %xmm0, data
movl $1, %eax
movl $0, %ebx
int $0x80