1. 数据标号:
1) 往往有这种需求,想在db/dw/dd等数据定义之前加个标号以便于对定义的数据区的访问,但是汇编语法不支持这样做(会直接报错的!),但MASM提供了一种更加便捷的方式来实现这种内存的访问方式,并且比想象中的功能还要强大;
2) 以上的这种标号在MASM中叫做数据标号,和普通地址标号不同的地方在于定义它不需要使用冒号:,而是直接在db/dw/dd之前写标号:tag db/dw/dd .....
a dw 1, 2, 3 b db 10 dup(?)*但是数据标号也只能用在db/dw/dd之前以定义数据区,不能用在其它地方;
3) 数据标号的双重功能:
i. 支持索引式的随机访问:比如tag[index]就表示以tag开头的低index字节处的内存单元(索引以0开始,索引为0就代表tag处的内存单元),并且[index]的形式和之前讲过的内存访问方式相同,比如tag[bx]、tag[bx + si + 5]等,注意!立即数不要在作为前缀了,但是也支持tag[bx][si][5]的写法;
ii. 包含有类型信息:可以根据dX判断数据区中元素的类型(是字节、字还是双字),这在访问内存空间的时候可以体现
a db 10 dup(?) mov a[2], 5 ; 正确!a本身包含了元素是字节的类型信息,因此可以正常执行 add al, a[1] ; 正确!al也是字节类型的,对号入座 and ax, a[5] ; 错误!ax是字类型的,和a[5]的类型冲突!iii. 数据标号本身也可以表示内存单元,tag就表示tag[0]:mov a, 5; add al, a等都是正确的!
4) 要想正确使用数据标号进行内存访问的大前提——使用assume和mov定位数据标号所在的段:
i. 数据标号的地址:在使用数据标号进行内存访问时汇编器实际上在背后将该标号翻译成其所在段中的偏移地址,比如访问a[2]的时候就是翻译成[2 + offset a];
ii. 针对i.问题就来了,那么其段地址是什么呢?理所当然了,肯定就是a所在段的段基咯!比如在以下情况中:
data segment a db 10 dup(?) data endsa的段基肯定就是data了,因此访问a[2]的时候必然是完整地翻译成data:[2 + offset a]了!
!注意:但是汇编编译器并不会做出这样的自动推导,MASM比较死板,规定数据标号的段地址必须由一个段寄存器给出,因此a[2]会被翻译成"某段寄存器:[2 + offset a]",而这个段寄存器应该选哪一个呢?MASM规定,该段寄存器默认就是使用assume中和数据标号所在段关联的那个寄存器了!是不是直到现在才明白assume伪指令的真正用途了吧!
!但是之前讲过,即使使用assume进行关联,也不会真正使上面的ds = data(除了cs之外),也就是说想正确地使用数据标号来访问内存还必须执行将段地址data传送进ds的mov指令了?对的,必须非常麻烦地执行之一步,必须手工执行完这一步之后才能无忧地使用数据标号访问内存!
?那为什么不直接在assume阶段就直接将关联的段地址赋给相应的段寄存器(比如在assume阶段直接ds = data)呢?那是因为CPU的寄存器资源非常有限,除了cs之外的其它段寄存器供不应求,除了给数据标号访问内存提供段基之外还可能需要应付其它大量的需求,因此编译器就给了这些段寄存器极大的自由而不在assume阶段直接将段地址赋值给它们,而仅仅是用于关联,这可以让程序员自由安排这些段寄存器如何使用;
*小结:在使用数据标号访问内存之前必须要手工地将标号所在段的段基赋值给assume关联的寄存器中!当然,使用代码段中的数据标号可以不必这样做,因为assume会对cs直接赋值的!
5) !!!数据标号的索引对类型不敏感!:虽然数据标号可以识别内存单元的类型,但是索引在自增后自减的时候并不伴随元素类型,即不管是db、dw还是dd,索引值+1或-1,则访问的地址也是+1字节或-1字节,并不会因为使用dw定义a[1]和a[2]的地址相差2个字节,永远都是1个字节的!
2. 直接定址表:
1) 可以将数据标号、普通标号作为dw/dd中定义的内容,如果是dw这些标号就会被翻译成偏移地址(16位),如果是dd就会被翻译成标号的段地址和偏移地址(高16位是段地址、低16位是偏移地址),比如:dw a, b就会被翻译成(a、b可以是数据标号也可以是普通标号,因此统称为标号)dw offset a, offset b,而dd a会被翻译成dd offset a, seg a;
2) 直接定址表:就是用数据标号定义一个数据区,该数据区内存放的都是地址,一般这些地址要么是其它数据区的地址(比如某个字符串的地址)要么就是一些子程序的地址,这样就定义了一张地址表,可以通过数据标号的随机访问特性快速定位想访问的内容(就表中存放的地址),在C语言中就相当于一个指针数组;
3) 利用数据标号打表的手段比比皆是,这里先演示一个普通的表(不是直接定址表):
以十六进制的形式在屏幕上输出给定的字节型数据
assume cs:code, ds:data data segment char_table db '0123456789ABCDEF' input db 1AH output db 0, 0, '$' data ends code segment start: mov ax, seg data mov ds, ax mov al, input mov ah, al mov cl, 4 shr ah, cl and al, 1111B mov bx, 0 mov bl, ah mov dl, char_table[bx] mov output[0], dl mov bl, al mov dl, char_table[bx] mov output[1], dl mov dx, offset output mov ah, 9 int 21H mov ax, 4C00H int 21H code ends end start4) 利用直接定址表计算sin(x),其中x只取0, 30, 60, 90, 120, 150, 180这几个值,输入的x存放在ax中,结果直接输出到屏幕上:
assume cs:code code segment start: mov ax, 30 call sin mov ax, 4C00H int 21H sin: jmp short _sin_start table dw ag0, ag30, ag60, ag90, ag120, ag150, ag180 ag0 db '0', '$' ag30 db '0.5', '$' ag60 db '0.866', '$' ag90 db '1', '$' ag120 db '0.866', '$' ag150 db '0.5', '$' ag180 db '0', '$' _sin_start: mov bl, 30 div bl mov ah, 0 mov bx, ax add bx, bx mov ax, seg table mov ds, ax mov dx, table[bx] mov ah, 9 int 21H ret code ends end start