曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 6006|回复: 2
打印 上一主题 下一主题

加法及指令

[复制链接]

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
跳转到指定楼层
楼主
发表于 2010-1-13 22:53:07 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
1、ADD 指令
ADD 指令用于把两个数加。格式如下:
add source,  destination
其中,source 可以是立即值、内存位置或寄存器。destination 参数可以是寄存器或者内存位置中存储的值,但是源和目标不能同时是内存位置,加法的结果放在目标位置。

ADD 指令可以将 8 位、16位或者 32位值相加,和其他GNU汇编指令一样,必须通过 ADD 助记符的结尾添加 b(用于字节)、w(用于字) 或者 l(用于双字)来指定操作数的长度。如:
addb $10, %al
addw %bx, %cx
addl data, %eax
addl %eax, %eax

测试程序
.section .data
data:
    .int 40

.section .text
.global _start
_start:
    nop
    movl $0, %eax
    movl $0, %ebx
    movl $0, %ecx
    movb $20, %al
    addb $10, %al
    movsx %al, %eax
    movw $100, %cx
    addw %cx, %bx
    movsx %bx, %ebx
    movl $100, %edx
    addl %edx, %edx
    addl data, %eax
    addl %eax, data
    movl $1, %eax
    movl $0, %ebx
    int $0x80
在 gdb 里观察相应寄存器和内存地址的值
(gdb) print $eax
$1 = 70
(gdb) print $ebx
$2 = 100
(gdb) print $ecx
$3 = 100
(gdb) print $edx
$4 = 200
(gdb) x/d &data
0x80490b4 <data>:    110
所有的指令都按照期望的那样执行了。另外,ADD 同样适用于带符号的加法运算。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
沙发
 楼主| 发表于 2010-1-16 20:48:15 | 只看该作者

加法检测进位或者溢出情况

在整数相加时,总是要注意 EFLAGS 寄存器,以确保操作过程中不会发生奇怪的事情。

对于无符号整数,当二进制加法造成进位情况时(即结果大于允许的最大值),进位标志 (carry flag) 就会被设置为 1 。

对于带符号整数,当出现溢出的情况时 (结果值小于允许的最小负值,或者大于允许的最大正值),溢出标志 (overflow flag) 就会被设置为 1.

当这些标志被设置为 1 时,就知道目标操作数的长度太小,不能保存加法的结果值,并且包含非法值 --- 这个值就是答案里 “溢出” 的部分。

进位和溢出标志的设置与加法中使用的数据长度相关联。例如,在 ADDB 指令中,如果结果超过 255,就会把进位标志设置为 1,但是在 ADDW 指令中,除非结果超过 65535,否则不会设置进位标志。

下面测试程序检测无符号整数加法中的进位情况
.section .text
.global _start
_start:
    nop
    movl $0, %ebx
    movb $190, %bl
    movb $100, %al
    addb %al, %bl
    jc over
    movl $1, %eax
    int $0x80
   
over:
    movl $1, %eax
    movl $0, %ebx
    int $0x80
运行输出
$ ./addtest
$ echo $?
0
程序返回的结果为 0,说明正确地检测到了进位的情况。现在改动寄存器的值,使加法不产生进位:
movb $190, %bl
movb $10, %al
运行程序后,得到下面的结果:
$ ./addtest
$ echo $?
200
上面的结果没有产生进位,所以 jc over 语句没有执行,%ebx 寄存器中的值为  200,从终端返回。

注意:对于无符号整数,在了解加法结果是否超出数据值的界限方面,进位标志是至关重要的,如果不能确定输入值的长度,在执行无符号整数的加法时,总是应该检查进位标志。如果知道输入值的界限,就可以不必检查进位标志。

处理带符号整数时,进位标志是没有用处的。不仅结果过大时会设置它,而且只要结果值小于零,也会设置它。虽然它对无符号整数有所帮助,但是对于带符号整数,它是没有意义的(甚至会带来麻烦)。

替换的做法是,在使用带符号整数时,必须关注溢出标志,当结果溢出正值或负值界限时,这个标志会被设置为 1 。

测试溢出标志的检测:
.section .data
output:
    .asciz "The result is %d\\n"

.section .text
.global _start
_start:
    movl $-1590876934, %ebx
    movl $-1259230143, %eax
    addl %eax, %ebx
    jo over
    pushl %ebx
    pushl $output
    call printf
    add $8, %esp
    pushl $0
    call exit
over:
    pushl $0
    pushl $output
    call printf
    add $8, %esp
    pushl $0
    call exit
编译连接:
$ as -o add_overflow.o add_overflow.s
$ ld -dynamic-linker /lib/ld-linux.so.2 -lc -o add_overflow add_overflow.o
运行输出:
$ ./add_overflow
The result is 0
输出结果表明检测到了溢出情况,这是因为程序把两个大的负数相加超出了 32 位带符号整数所能承受的范围从而造成了溢出。

改一下上面程序:
movl $-190876934, %ebx
movl $-159230143, %eax

那么运行结果为:
$ ./add_overflow
The result is -316799948

注意:在对带符号整数相加时,如果不确定输入数据的长度,那么检查溢出标志以了解错误情况是很重要的。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
板凳
 楼主| 发表于 2010-1-16 21:54:34 | 只看该作者

ADC 指令

如果必须处理非常大的,不能存放到双字数据长度 (ADD 指令可以使用的最大长度) 中的带符号或者无符号整数,可以把值分割为多个双字数据元素,并且对每个元素执行独立的加法操作。

为了正确地完成这个工作,必须检测每个加法操作的进位标志。如果进位标志被设置为 1,就必须进位到下一对相加的数据中。在汇编中,如果要手动完成这一工作,则必须使用 ADD 和 JC (或者JO) 指令的组合,生成复杂的指令组来确定什么时候产生了进位(或者溢出)情况,并且确定什么时候需要把进位(或者溢出)添加到下一次的加法操作中。

幸运的是,这一工作,有 ADC 指令帮忙很好的完成。

ADC 指令执行两个无符号或者带符号整数的加法,并且把前一个 ADD 指令产生的进位标志的值包含在其中。为了执行多组字节的加法操作,可以把多个 ADC 指令链接在一起,因为 ADC 指令也按照操作结果正确地设置进位和溢出标志。

下面测试把两个大的数字相加: 7252051615 和 5732348928。这两个数都已经超过了 32 位无 符号整数的界限,所以我们必须使用 64 位无符号整数值,使用两个 32 位寄存器保存值。因为使用两个寄存器保存 64 位值,所以必须通过两次单独的 32 位加法把它们相加。低 32 位相加后,如果进位位被设置为 1 。就必须把它添加到高 32 位的加法中。可以使用 ADC 指令完成这个工作。

数的分解及相加原理如下图所示:

如上图所示,EAX:EBX 寄存器对保存着第 1 个值,ECX:EDX 寄存器对则保存着第 2 个值。为了把这两个 64 位值相加,首先必须使用 ADD 指令将 EBX 和 EDX 寄存器相加。从 EBX 和 EDX 寄存器中的值可以看出,加法会产生进位。之后,EAX 和 ECX 寄存器相加,并且加上第 1 个加法进位。

测试程序
.section .data
data1:
    .quad 7252051615
data2:
    .quad 5732348928

output:
    .asciz "The result is %qd\\n"

.section .text
.global _start

_start:
    movl data1, %ebx
    movl data1+4, %eax
    movl data2, %edx
    movl data2+4, %ecx
   
    addl %ebx, %edx
    adcl %eax, %ecx
   
    pushl %ecx
    pushl %edx
    pushl $output
    call printf
   
    addl $12, %esp
    pushl $0
    call exit
运行与输出
$ ./adc
The result is 12984400543
说明
上面程序中,printf 函数使用 %qd 参数来显示 64 位带符号整数值。
执行加法后,64 位的结果将保存在 ECX:EDX 寄存器对中。为了在 printf 函数中使用它们,必须把它们压入到堆栈中。注意,这里,首先压入包含高位字节的寄存器 ECX ,然后再压入 EDX。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2025-5-3 12:33 , Processed in 0.083567 second(s), 21 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表