📌 汇编语言是很多相关课程(如数据结构、操作系统、微机原理)的重要基础。但仅仅从课程的角度出发就太片面了,其实学习汇编语言可以深入理解计算机底层工作原理,提升代码效率,尤其在嵌入式系统和性能优化方面有重要作用。此外,它在逆向工程和安全领域不可或缺,帮助分析软件运行机制并增强漏洞修复能力。 本专栏的汇编语言学习章节主要是依据王爽老师的《汇编语言》来写的,和书中一样为了使学习的过程容易展开,我们采用以8086CPU为中央处理器的PC机来进行学习。
这一章,我们讨论如何有效合理地组织数据,以及相关的编程技术。
本章中,我们要用到这种标号,先进行如下介绍。
前面的内容中,我们一直在代码段中使用标号来标记指令、数据、段的起始地址。
比如:下面的程序将code 段中的a标号处的8个数据累加,结果存储到b标号处的字中。
assume cs:code
code segment
a : db 1,2,3,4,5,6,7,8
b : dw 0
start: mov si,offset a
mov bx,offset b
mov cx,8
s : mov al,cs:[si]
mov ah,0
add cs:[bx],ax
inc si
loop s
mov ax,4c00h
int 21h
code ends
end start程序中,code、a、b、start、s都是标号。 这些标号仅仅表示了内存单元的地址。
但是,我们还可以使用一种标号,这种标号不但表示内存单元的地址,还表示了内存单元的长度,即表示在此标号处的单元,是一个字节单元,还是字单元,还是双字单元。
上面的程序我们还可以写成这样:→→→→
assume cs:code
code segment
a db 1,2,3,4,5,6,7,8
b dw 0
start: mov si,0
mov cx,8
s: mov al,a[si]
mov ah,0
add b,ax
inc si
loop s
mov ax,4c00h
int 21h
code ends
end start我们在code 段中使用的标号a、b后面没有“:”,因此它们是可以同时描述内存地址和单元长度的标号。
因为这种标号包含了对单元长度的描述,所以,在指令中,它可以代表一个段中的内存单元。
比如,对于上面程序中的“b dw 0”:
mov ax,b —— 相当于:mov ax,cs:[8]
mov b,2 —— 相当于:mov word ptr cs:[8],2
inc b —— 相当于:inc word ptr cs:[8]
在这些指令中,标号b代表了一个内存单元,地址为code:8 ,长度为2字节。
下面的指令会引起编译错误:
mov al,b为什么?
没错啦,聪明的你肯定意识到了。
因为b代表的内存单元是字单元,而al是8位寄存器。
因此,如果我们将程序中的指令:add b,ax ,写为 add b,al,将出现同样的编译错误。
对于程序中的“a db 1,2,3,4,5,6,7,8”:
mov al,a[si] —— 相当于:mov al,cs:0[si]
mov al,a[3] —— 相当于:mov al,cs:0[3]
mov al,a[bx+si+3] —— 相当于:mov al,cs:0[bx+si+3]
可见,使用这种包含单元长度的标号,可以使我们以简洁的形式访问内存中的数据。
以后,我们将这种标号称为数据标号。它标记了存储数据的单元的地址和长度。它不同于仅仅表示地址的地址标号。
一般来说,我们不会在代码段中定义数据,而是将数据定义到其他段中。在其他段中,我们也可以使用数据标号来描述存储数据的单元的地址和长度。
❗注意:在后面加有“:”的地址标号,只能在代码段中使用,不能在其他段中使用。
下边程序将data段中a标号处的8个数据累加,结果存储到b标号处的字中。
assume cs:code,ds:data
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
data ends
code segment
start: mov ax,data
mov ds,ax
mov si,0
mov cx,8
s: mov al,a[si]
mov ah,0
add b,ax
inc si
loop s
mov ax,4c00h
int 21h
code ends
end start❗注意,如果想在代码段中,直接用数据标号访问数据,则需要用伪指令assume 将标号所在的段和一个段寄存器联系起来。 否则编译器在编译的时候,无法确定标号的段地址在哪一个寄存器中。
当然,这种联系是编译器需要的,但绝对不是说,我们因为编译器的工作需要,用 assume 指令将段寄存器和某个段相联系,段寄存器中就会真的存放该段的地址。 比如:在上面的程序中,我们要在代码段code中用data段中的数据标号 a、b 访问数据,则必须用 assume 将一个寄存器和data 段相联。
在程序中,我们用ds寄存器和data段相联,则编译器对相关指令的编译如下:
mov al,a[si] —— 编译为:mov al,[si+0]
add b,ax —— 编译为:add [8],ax
因为这些实际编译出的指令,都默认所访问单元的段地址在ds中,而实际要访问的段为data,所以,若要访问正确,在这些指令执行前,ds 中必须为 data 段的段地址。
则,我们在程序中使用指令:
mov ax,data
mov ds,ax设置ds指向data段。
我们可以将标号当作数据来定义,此时,编译器将标号所表示的地址当作数据的值。
比如:
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
c dw a,b
data ends数据标号c处存储的两个字型数据为标号a、b 的偏移地址。
相当于:
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
c dw offset a, offset b
data ends再比如:
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
c dd a,b
data ends数据标号c处存储的两个双字型数据为标号a的偏移地址和段地址、标号b 的偏移地址和段地址。
相当于:
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
c dw offset a, seg a, offset b, seg b
data endsseg操作符,功能为取得某一标号的段地址。