II 数据的表示与运算

1. 数据的表示

1.1. 二进制数据的组织

名字 宽度 英文 定义方式 C 语言中等价类型
字节 8 bit byte db (unsigned) char;
16 bit word dw (unsigned) short int;
双字 32 bit double word dd (unsigned) long int; float;
四字 64 bit quadruple word (qword) dq (unsigned) long long; double;
十字节 80 bit ten byte (tbyte) dt long double;

在 data 段中可以定义变量和数组,对应语法如下:

data segment
  s db "Hello, World" ; 字符串 char[13]
  a dd 1, 2, 3, 4, 5  ; 数组 int[5]
  b dw 5 dup(0)       ; 数组 short int[5],初始值全为0
  c db 0FFh;          ; 值为0FFh的字符 char
  f dd 3.14           ; 值为3.14的浮点数 float
data ends
  • dup(数) 来表示数组,用 ? 表示无初始值或初始值任意。
  • 在定义字符串时,可以通过 0Dh0Ah 来表示换行。

1.2. 二进制浮点数的表示

略。

2. 数据的运算

2.1. 零扩充和符号扩充

  • 当把低位无符号数赋给高位无符号数时,必须采用零扩充规则,即在左边补 0。
  • 当把低位有符号数赋给高位有符号数时,必须采用符号扩充规则,即在左边补上原数的最高位(符号位)。

2.1.1. cbwcwd 符号扩充

  • cbw,即 convert byte to word,可将 AL 中的值符号扩充到 AX 中
  • cwd,即 convert word to double word,可将 AX 中的值符号扩充到 DX:AX 中

2.1.2. movzx 零扩充

movzx dest, src 把 src 零扩充到寄存器 dest 中。指令格式:

  • movzx reg16, reg8|reg8
  • movzx reg32, reg8|mem8|reg16|mem16

2.1.3. movsx 符号扩充

movsx dest, src 功能与格式与 movzx 类似,区别在于 movsx 是符号扩充。

2.2. 整数运算

2.2.1. 加法 add / inc / adc

  • add dest, srcdest += src
  • adc dest, src(带进位加):dest += src + CF
  • inc opop++

add / adc 指令若发生进位会将 CF 寄存器置 1,否则将 CF 寄存器置 0。inc 指令不会影响 CF 寄存器

2.2.2. 减法 sub / sbb / dec / neg

  • sub dest, srcdest -= src
  • sbb dest, src(带错位减):dest -= src + CFdest = dest - src - CF
  • dec opop--
  • neg op(取相反数):op = -op

2.2.3. 乘法 mul / imul

mul 进行无符号整数乘法。

  • 当 mul 后跟一个 8 位寄存器或 8 位变量作为乘数时,被乘数一定是 AL,乘积一定是 AX。
  • 当 mul 后跟一个 16 位寄存器或 16 位变量作为乘数时,被乘数一定是 AX,乘积一定是 DX:AX。
  • 当 mul 后跟一个 32 位寄存器或 32 位变量作为乘数时,被乘数一定是 EAX,乘积一定是 EDX:EAX。

imul 进行有符号整数乘法,其第一类用法和 mul 的上述用法相同。第二类用法可以包含两个或三个操作数,其中前两个操作数可以是寄存器或变量,第三个操作数只能是常数:

  • imul eax, ebx:EAX = EAX × EBX
  • imul eax, ebx, 3:EAX = EAX × EBX × 3

2.2.4. 除法 div / idiv

div 进行无符号整数除法。

  • 当 div 后跟一个 8 位寄存器或 8 位变量作为除数时,被除数一定是 AX,商是 AL,余数是 AH
  • 当 div 后跟一个 16 位寄存器或 16 位变量作为除数时,被除数一定是 DX:AX,商是 AX,余数是 DX
  • 当 div 后跟一个 32 位寄存器或 32 位变量作为除数时,被除数一定是 EDX:EAX,商是 EAX,余数是 EDX

除法溢出有来两种情形:

  • 除数是 0。
  • 被除数除以除数的商无法保存到相应寄存器中。

当发生除法溢出时,会在 div 指令上方插入并调用一条 int 00h 中断指令(默认行为:显示 divide overflow 并强行结束程序运行)。如果修改 0:0 处的中断向量,则可以在发生中断时转到我们自己的中断函数,对除法溢出的问题进行处理。iret 后会重新执行除法指令。

idiv 进行有符号整数除法,用法参考 div

2.3. 浮点数运算

  • fld 压入小数类型的变量。
  • fild 把整数类型的变量转化为小数类型并压入。
  • fst 把小数寄存器 st(0) 保存到变量中。
  • fstp 把小数寄存器 st(0) 保存到变量中并弹出 st(0)。
  • fadd / fsub / fmul / fdiv 进行浮点数的四则运算;如用 fmul st, st(1) 进行乘法计算。

CPU 内部一共有 8 个小数寄存器,分别叫做 st(0)、st(1)、…、st(7)。其中 st(0)简称 st 这 8 个寄存器的宽度均达到 80 位,相当于 C 语言中的 long double 类型。

2.4. 位运算

2.4.1. 逻辑运算

指令 用法 含义 C 语言等价运算符言
and add ax, bx &
or or ax, bx |
xor xor ax, ax 异或 ^
not not ax 取反 ~

2.4.2. 逻辑移位 shlshr

适用于无符号数的移位运算,空位补 0,最后一个被移动的位会被放到 CF 标志中。

2.4.3. 算术移位 salsar

适用于有符号数的移位运算。salshl 完全相同,sar 会在被操作数是负数时补 1,否则行为和 shr 相同。同样的,最后一个被移动的位会被放到 CF 标志中。

2.4.4. 循环移位 rolror

对应变量进行循环移位操作,即被移出的值会从另一侧填补到数中。注意最后一个被移动的位会在被放到另一端的同时放到 CF 标志中。

2.4.5. 带进位循环移位 rclrcr

同样是循环移位操作,但会把 CF 标志看作其中一位参与运算(如被操作数宽度为 16 位,可看作一个长度为 17 的环)。

2.5. 字符串操作与运算

Fun Fact:C 语言中的三个关于字符串的库函数

  • strcpy(target, source); 永远按正方向复制

  • memcpy(target, source); 永远按正方向复制

  • memmove(target, source); 会自行选择方向保证部分重叠时正确

字符串指令的方向会被 DF 标志控制,当 DF=0 时会按照正方向(低地址到高地址)运行,当 DF=1 时会按照反方向(高地址到低地址)运行。可通过执行 cldstd 来控制。

2.5.1. 字符串复制指令 rep movsb/w/d

rep movsb,即 move string in byte,会以字节byte)为单位复制字符串。如果将 movsb 换成 movswmovsd,则分别会以 word、double word 为单位复制字符串。

在执行 repo movsb 执行前需要做以下准备工作:

  1. DS:SI 指向源字符串(SI 就是 source index)
  2. ES:DI 指向目标字符串(DI 就是 destination index)
  3. CX 寄存器存储移动次数
  4. DF 标志表示方向(如果)

rep movsb 的行为的伪代码如下:

again:
	if (cx == 0) goto done
	asm mov byte ptr es:[di], byte ptr es:[do]  // movsw/d的话这里就换为word/dword
	df == 0 ? (si++, di++) : (si--, di--)      // movsw/d的话这里就需要增减2/4
	cx--
	goto again
done:

2.5.2. 字符串比较指令 repe/ne cmpsb/w/d

单条 cmpsb 指令的行为如下:

asm cmp byte ptr ds:[si], byte ptr es:[di]
df == 0 ? (si++, di++) : (si--, di--)

reperepne 则控制了 cmp 比较结果的行为,如 repe 的行为如下(若本次比较相等则继续比较下一个):

again:
	if (cx == 0) goto done
	asm cmp byte ptr ds:[si], byte ptr es:[di]
	df == 0 ? (si++, di++) : (si--, di--)
	cx--
	if (zf == 1) goto again    // repne的话这里就要求ZF=0
done:

2.5.3. 字符串搜索指令 repe/ne sacsb/w/d

这条字符串指令就只和 ES:DI (注意不是 DS:SI 哦)所指向的字符串有关,CPU 将在其中搜索 AL/AX/EAX 的值(取决于是 sacsb 还是 sacswsacsd)。

repe sacsb 的行为如下:

again:
	if (cx == 0) goto done
	asm cmp al, byte ptr es:[di]  // sacsw/d的话这里还要换成AX/EAX
	df == 0 ? (di) : (di--)
	cx--
	if (zf == 1) goto again       // repne的话这里就要求ZF=0
done:

2.5.4. 写入字符串指令 rep stosb/w/d

把 AL/AX/EAX 的值写入 ES:DI 指向的目标字符串中。

2.5.5. 读取字符串指令 lodsb/w/d

从 DS:SI 所指向的源字符串中读取一个 byte/word/double word 保存到 AL/AX/EAX。

例:用 lodsbstosb 指令过滤掉字符串中的空格并存储到另一字符串中

data segment
s db "Ada Lovelace is often regarded as "
  db "the first computer programmer."
slen = $ - offset s
t db slen dup('T')
data ends

code segment
assume cs:code, ds:data
main:
   mov ax, data
   mov ds, ax
   mov es, ax
   mov si, offset s; ds:si->s[0]
   mov di, offset t; es:di->t[0]
   mov cx, slen    ; CX = length of s
   cld             ; DF = 0
   jcxz done       ; jump if cx is zero
again:
   lodsb           ; AL=ds:[si], si++
   cmp al, ' '
   je skip
   stosb           ; es:[di]=AL, di++
skip:
   loop again      ; dec cx
                   ; jnz again
done:
   xor al, al      ; AL = 00h
   stosb           ; es:[di]=AL, di++
   mov ah, 4Ch
   int 21h
code ends
end main

评论

TABLE OF CONTENTS

1. 数据的表示
1.1. 二进制数据的组织
1.2. 二进制浮点数的表示
2. 数据的运算
2.1. 零扩充和符号扩充
2.2. 整数运算
2.3. 浮点数运算
2.4. 位运算
2.5. 字符串操作与运算

© 2018-2025 memset0.

All rights reserved.

Source Code

Built with Gatsby.js


Made with ❤️ in China