4-段寄存器&寻址方式

一、段寄存器及使用

1、存储器分段

(1)物理地址相关

  • 内存(逻辑存储器):CPU能通过CPU总线直接寻址访问的存储器

  • 物理地址:内存(逻辑存储器)每一个字节单元有一个唯一的地址,称为物理地址。

    1. 这个物理地址就是各存储器在CPU总线上的地址,利用它CPU可以直接访问到对应的存储空间
    2. CPU的地址线数量决定可产生的最大物理地址,n根地址线—>最大地址2^n-1,32位CPU通常有32根地址线,因此32位CPU的电脑最大只能装4G的内存条
  • 物理地址空间:所有可形成的物理地址的集合

    1. 物理地址空间大小不等于实际安装物理内存大小
    2. Intel8086有20根地址线,物理地址的范围是0到FFFFF
      Intel80386有32根地址线,物理地址的范围是0到FFFFFFFF

(2)存储器分段

  • 为了有效地管理存储器,常常把

    1. 线性的物理地址空间划分为若干逻辑段
    2. 存储空间被划分为若干存储段

    可以认为逻辑段和存储段是对应的

  • 通常,运行的程序把不同的数据存储于存储器中的不同存储段,包括

    1. 代码:要执行的指令序列(存储于代码段
    2. 数据:要处理加工的内容(存储数据段
    3. 堆栈:按先进后出规则存取的区域(存储于堆栈段

2、逻辑地址

(1)逻辑地址

  • 分段后,程序中使用的某个存储单元总是属于某个段,所以可以 某某段 某某单元方式表示存储单元
  • 逻辑地址程序中用于表示存储单元的地址
    1. 由于采用分段存储管理方式,程序中使用的逻辑地址是二维的,第一维给出某某段,第二维给出段内的某某单元。
    2. 二维的逻辑地址可以表示为:段号∶段内地址
    3. (段内)偏移存储单元的物理地址与所在段起始地址的差值。这个差值恰好是段内地址,因此二维的逻辑地址又可以表示为:段号∶偏移
    4. 实方式下,段号是段值
      保护方式下,段号是段选择子
    5. 有效地址/偏移地址:逻辑地址中的偏移称作有效地址或偏移地址,汇编程序中,不同的数据往往固定存放在不同的段,有唯一对应的段寄存器,因而这个地址用的最多。比如指示代码的EIP、指示堆栈位置的ESP、EBP等都是存储的有效地址

(2)逻辑地址转为物理地址

  • 物理地址 = 段起始地址 + 偏移

  • 获得物理地址的过程:

    1. 由段号得到段起始地址
    2. 加上偏移
  • 对于IA32

    1. 保护方式下:物理地址32位,段起始地址32位,偏移32位
    2. 实方式下:物理地址20位,段起始地址20位,偏移16位
    3. 可以参考:我理解的逻辑地址、线性地址、物理地址和虚拟地址(补充完整了)

    对于8086:

    1. 只有实方式:物理地址20位,段起始地址20位,偏移16位
    2. 可以参考:逻辑地址、线性地址和物理地址之间的转换
  • 如果整个程序只有一个段,则二维逻辑地址退化为一维。段起始地址完全相同,偏移决定一切。如果用VS2010来编写嵌入汇编程序,那么就是这种情况,只考虑偏移即可

(3)三种地址小结

地址 说明
物理地址 各存储器在CPU总线上的地址,是实际上可以直接访问到存储器的地址
逻辑地址 段号∶偏移 方式描述的地址,需要进行一些计算才能转换为物理地址
有效地址 就是逻辑地址中的偏移量

3、段寄存器

  • 作用:段寄存器存放着当前使用的逻辑地址中的段号(段值/段选择子),用于获得段起始地址。
  • 段寄存器是16位的,在实方式下存储16位段值;在保护方式下存储16位段选择子
  • 1747644039768.webp
  • 若代码段、堆栈段、数据段在同一个存储段,则CS、DS、SS给出相同的段起始地址
  • 若段寄存器给出的段起始地址为0,则偏移相当于物理地址

二、寻址方式

  • 寻址方式表示指令中操作数所在的方法
  • CPU常用的三种寻址方式:立即寻址寄存器寻址存储器寻址,此外还有固定寻址和I/O端口寻址
  • 以上三种方式都是对于偏移地址的不同描述,对于段基址:
    1. 基址寄存器是EBP或ESP时,默认的段寄存器是SS
    2. 否则,默认的段寄存器是DS

1、立即寻址

  • 立即寻址方式操作数本身就包含在指令中,直接作为指令的一部分给出,这样的操作数称为立即数

  • 注意:

    1. 只有源操作数可以使用立即寻址方式
    2. 如果立即数由多个字节构成,么在作为指令的一部分存储时,也采用“高高低低”规则。
  • 由于立即寻址方式的操作数是立即数,包含在指令中,所以执行指令时,不需要再到存储器中去取该操作数了。

    1. 立即寻址,会自动配合目的操作数尺寸,不需要dword ptr等参数
  • 示例:

1
2
3
4
5
6
7
8
9
//以下源操作数使用立即寻址方式
MOV EAX, 12345678H //给EAX寄存器赋初值
ADD BX, 1234H //给BX寄存器加上值1234H
SUB CL, 2 //从CL寄存器减去值2

//以下源操作数值一致,但是尺寸不同
MOV EDX, 1 //源操作数是32位
MOV DX, 1 //源操作数是16位
MOV DL, 1 //源操作数是8位

2、寄存器寻址

  • 寄存器寻址方式操作数存在CPU内部寄存器,指令指定寄存器

  • 适用范围:

    1. 8个32位寄存器:EAX,EBX,ECX,EDX,ESI,EDI,EBP,ESP
    2. 8个16位寄存器:AX,BX,CX,DX,BP,SI,DI,SP
    3. 8个8位寄存器:AH,AL,BH,BL,CH,CL,DH,DL
  • 操作数在寄存器中,不需要访问存储器取得操作数,这样指令执行速度较快

  • 示例:

1
2
3
4
MOV   EBP, ESP        //把ESP之值送到EBP
ADD EAX, EDX //把EAX之值与EDX之值相加,结果送到EAX
SUB DI, BX //把DI之值减去BX之值,结果送到DI
XCHG AH, DH //交换AH与DH之值

3、32位的存储器寻址方式

  • 存储器寻址方式给出存储单元偏移的寻址方式(在某个段内,给出存储单元的偏移即可找到它)
  • 存储器操作数:在指令中[xxx]意味着从xxx地址取数据,称这个[xxx]为存储器操作数。
  • 有效地址要访问的存储单元的段内偏移。在32位的存储器寻址方式下,存储单元有效地址可达32位
  • 采用32位的存储器寻址方式,能够给出32位的偏移
  • 有多种存储器寻址方式
    1. 直接寻址
    2. 寄存器间接
    3. 寄存器相对
    4. 基址加变址
    5. 通用方法
  • 说明:
    1. 存储器操作数尺寸是字节/字/双字
    2. 默认指令中的寄存器操作数的尺寸决定了存储器操作数的尺寸;但也可以显式指定存储器操作数尺寸
修饰符 功能
WORD PTR 指定尺寸为“字”
BYTE PTR 指定尺寸为“字节”
DWORD PTR 指定尺寸为“双字”

(1)直接寻址方式

  • 直接寻址方式:操作数在存储器中,指令直接包含操作数所在的存储单元的有效地址
  • 示例:
1
2
3
MOV   ECX, [95480H]       //源操作数采用直接寻址
MOV [9547CH], DX //目的操作数采用直接寻址
ADD BL, [95478H] //源操作数采用直接寻址
  • 即寻址和直接寻址的区别:

    1. 直接寻址中十六进制数表示地址,要到此地址取出操作数;立即寻址中表示操作数
    2. 直接寻址的地址要写在方括号[]
  • 注意:

    1. 直接寻址时,[]给出的是被取出数据的最低地址
    2. 按 “高高低低” 规则取出数据
      1747644493685.webp

(2)寄存器间接寻址方式

  • 寄存器间接寻址操作数在存储器中,由八个32位通用寄存器之一给出操作数所在存储单元有效地址。

  • 寄存器间接寻址和寄存器寻址的区别:

    1. 寄存器间接的Reg名称出现在方括号[]
    2. 寄存器间的Reg中存储的是操作数所在地址;寄存器寻址的Reg存储的是操作数
  • 注意:

    1. 操作数地址必须来自八个32位通用寄存器之一
  • 示例:

1
2
3
MOV   EAX, [ESI]    //源操作数寄存器间接寻址,ESI给出有效地址
MOV [EDI], CL //目的操作数寄存器间接寻址,EDI给出有效地址
SUB DX, [EBX] //源操作数寄存器间接寻址,EBX给出有效地址

(3)通用方式

  • 存储单元的有效地址可以由三部分内容相加构成

    1. 一个32位基地址寄存器
    2. 一个可乘上比例因子1/2/4/8的32位变址寄存器
    3. 一个8/16/32位位移量

    PS:可省去任意两部分
    1747644625918.webp

  • 注意:

    1. 变址寄存器乘上的比例因子取值只能是1/2/4/8之一
    2. 三部分中可以任意省略。省略后的寻址方式又有不同名称,但都属于通用方式,前面提到的直接寻址方式和寄存器间接寻址方式也是属于通用方式
  • 示意图:

  • 1747644764536.webp

  • 示例:

    1. 寄存器相对寻址方式[寄存器名+偏移]
    1
    2
    3
    MOV   EAX, [EBX+12H]      ;源操作数有效地址是EBX值加上12H
    MOV [ESI-4], AL ;目的操作数有效地址是ESI值减去4
    ADD DX, [ECX+5328H] ;源操作数有效地址是ECX值加上5328H
    1. 基址+变址寻址[寄存器名+寄存器名]
1
2
3
MOV   EAX, [EBX+ESI]      ;源操作数有效地址是EBX值加上ESI值
SUB [ECX+EDI], AL ;目的操作数有效地址是ECX值加上EDI值
XCHG [EBX+ESI], DX ;目的操作数有效地址是EBX值加上ESI值
3. `基址+带放大因子的变址寻址`:`[寄存器名+寄存器名*放大因子+偏移]`
1
2
3
4
5
MOV   EAX, [ECX+EBX*4]      ;EBX作为变址寄存器,放大因子是4
MOV [EAX+ECX*2], DL ;ECX作为变址寄存器,放大因子是2
ADD EAX, [EBX+ESI*8] ;ESI作为变址寄存器,放大因子是8
SUB ECX, [EDX+EAX-4] ;EAX作为变址寄存器,放大因子是1
MOV EBX, [EDI+EAX*4+300H] ;EAX作为变址寄存器,放大因子是4

(4)补充

  • 用 [address] 这样的方法从内存取值

    1. address是地址尾,也就是取出值的最低字节地址
    2. 存入寄存器时,低地址对应寄存器低位;高地址对应高位(“高高低低”规则
    3. 初始化好的全局变量,占用内存空间是连续的
  • 使用尺寸修饰符的示例:

  • 1747644937172.webp

  • 关于存储器寻址的说明

    1. 缺省段寄存器
      (1)如果基址寄存器不是EBP/ESP,缺省引用的段寄存器为DS
      (2)如果基址寄存器是EBP/ESP,缺省引用的段寄存器为SS
      (3)如果EBP作为变址寄存器(ESP不能做变址寄存器),缺省引用的段寄存器为DS
    2. 有效地址
      (1)无论存储器寻址方式具体是哪种,如果基址寄存器、变址 寄存器、比例因子、位移量这些算出来超过32位,只有低32位有效

三、取有效地址指令LEA

名称 LEA(取有效地址指令)
格式 LEA REG,OPRD
动作 把操作数OPRD的有效地址传送到REG
合法值 OPRD:存储器操作数
REG:16/32位通用寄存器
注意 此指令不影响标志寄存器
此指令是取地址,和mov有本质区别
  • 示例1:基本操作
1
2
3
4
5
MOV   EDI, 51234H                 //EDI=00051234H
MOV EAX, 6 //EAX=00000006H
LEA ESI, [EDI+EAX] //ESI=0005123AH
LEA ECX, [EAX*4] //ECX=00000018H
LEA EBX, [EDI+EAX*4+300H] //EBX=0005154CH
  • 示例2:指针的实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include  <stdio.h>
char chx, chy; //全局字符变量
int main( )
{
char *p1, *p2; //两字符型指针变量
//嵌入汇编代码之一
_asm {
LEA EAX, chx //取变量chx的存储单元有效地址
MOV p1, EAX //送到指针变量p1, p1 = &chx
LEA EAX, chy //取变量chy的存储单元有效地址
MOV p2, EAX //送到指针变量p2, p2 = &chy
}

printf("Input:"); //提示
scanf("%c", p1); //键盘输入一个字符

//嵌入汇编代码之二
_asm {
MOV ESI, p1 //取回变量chx的有效地址
MOV EDI, p2 //取回变量chy的有效地址
MOV AL, [ESI] //取变量chx之值
MOV [EDI], AL //送到变量chy中
}
printf("ASCII:%02XH\n", *p2); //显示之
return 0;
}
  • 示例三:取一个double数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
int iarr[5] = {55, 87, -23, 89, 126};
double darr[5] = {9.8, 2.77, 3.1415926, 1.414, 1.73278};

int main()
{ int ival; //整型变量
double dval; //双精度浮点
//嵌入汇编
_asm {
LEA EBX, iarr //把整型数组首元素的有效地址送EBX
MOV ECX, 3
MOV EDX, [EBX+ECX*4] //取出iarr的第4个元素
MOV ival, EDX
;
LEA ESI, darr //把浮点数组首元素的有效地址送ESI
LEA EDI, dval //把变量dval的有效地址送EDI
MOV ECX, 2
MOV EAX, [ESI+ECX*8] //取darr的第3个元素的低双字
MOV EDX, [ESI+ECX*8+4] //取darr的第3个元素的高双字
MOV [EDI], EAX //保存低双字
MOV [EDI+4], EDX //保存高双字
}
printf("iVAL=%d\n",ival); //显示为iVAL=89
printf("dVAL=%.8f\n",dval); //显示为dVAL=3.14159260
return 0;
}
  • 示例四:妙用LEA和存储器取值,进行多项式计算
1
2
3
4
5
6
7
8
9
10
11
12
//待翻译的c函数
int _fastcall cf212(int x, int y) //由寄存器传参数
{
return ( 3 * x + 7 * y + 200 );
}

//翻译成汇编后的核心代码(ECX传递x,EDX传递y)
lea eax, DWORD PTR [ecx+ecx*2] //eax=3*x
lea ecx, DWORD PTR [edx*8] //ecx=8*y
sub ecx, edx //ecx=7*y
lea eax, DWORD PTR [eax+ecx+200] //eax=3*x+7*y+200
ret