前言

贡献者:yfm

基础知识

基础知识:

  1. 计算机体系结构:

    • 中央处理器 (CPU): 负责执行计算机程序的主要组件。
    • 寄存器: 用于在CPU内部存储和操作数据的小而快速的存储器单元。
    • 内存: 用于存储程序和数据的区域,包括RAM (随机存取存储器)。
    • 指令集体系结构 (ISA): 定义CPU能够执行的指令的集合。
  2. 汇编语言基础:

    • 汇编语言 vs 高级语言: 汇编语言是一种低级语言,更接近计算机硬件。高级语言提供更抽象的方式来编写程序。
    • 指令: 汇编语言的基本单元是指令,它们告诉计算机执行特定的操作。
    • 寻址模式: 指定如何访问内存中的操作数的规则。
    • 寄存器: 汇编语言使用寄存器来存储和处理数据。
  3. x86架构:

    • x86体系结构: 一种常见的CPU架构,广泛用于个人计算机。
    • 段寄存器和偏移地址: 内存地址由段寄存器和偏移地址组合而成。
    • 通用寄存器: 在x86体系结构中,有一组通用寄存器(例如,AX、BX、CX、DX等)。
  4. 汇编语言指令:

    • MOV指令: 用于将数据从一个位置复制到另一个位置。
    • INT指令: 触发软中断,例如调用操作系统的功能。
    • AND、ADD、SUB等算术和逻辑指令: 用于执行各种算术和逻辑操作。
  5. DOS中断服务:

    • INT 21H: 是DOS中断服务的一部分,提供了许多基本的输入和输出功能。
  6. 段与偏移地址:

    • 段寄存器: 在x86架构中,物理地址由一个段寄存器和一个偏移地址组成。
  7. 程序结构:

    • 段定义: 汇编程序通常分为代码段和数据段,每个段有其特定的用途。
    • 程序开始和结束: 汇编程序有一个入口点(通常是START标签),并通过END伪指令结束。
  8. DOS中断调用:

    • INT 21H功能: DOS提供了一系列中断服务,通过INT 21H进行调用,不同的功能由AH寄存器中的不同值确定。

代码中的关键字:

  1. DSEG SEGMENT / DSEG ENDS:

    • DSEG SEGMENTDSEG ENDS 是数据段的开始和结束定义。在这个段中,程序可以定义和存储数据。
  2. CSEG SEGMENT / CSEG ENDS:

    • CSEG SEGMENTCSEG ENDS 是代码段的开始和结束定义。在这个段中,程序包含执行的指令。
  3. ASSUME CS:CSEG, DS:DSEG:

    • ASSUME 语句用于指定段寄存器的默认段寄存器值。在这里,它指定了代码段寄存器 CS 的默认值为 CSEG,数据段寄存器 DS 的默认值为 DSEG
  4. START:

    • START 是程序的入口点标签,指示程序从这里开始执行。
  5. MOV AX, DSEG / MOV DS, AX:

    • MOV 指令用于将数据段 DSEG 的地址加载到 AX 寄存器中,然后将 AX 寄存器的值传递给数据段寄存器 DS
  6. MOV AH, 01H / INT 21H:

    • MOV 指令将 01H 存储在 AH 寄存器中,表示要进行键盘输入。 INT 21H 是 DOS 中断服务,用于执行 AH 中指定的功能,这里是等待从键盘输入一个字符,并在显示器上回显。
  7. MOV BL, AL:

    • MOV 指令用于将 AL 寄存器中的值(即从键盘输入的字符)复制到 BL 寄存器中。
  8. MOV DL, 0DH / MOV AH, 02H / INT 21H:

    • 这组指令将回车符(0DH)和换行符(0AH)的 ASCII 值加载到 DL 寄存器,然后通过 INT 21H 中断来在屏幕上显示回车和换行,实现光标移动到下一行。
  9. MOV DL, BL / MOV AH, 02H / INT 21H:

    • 这组指令将从键盘输入的字符的 ASCII 值加载到 DL 寄存器,并通过 INT 21H 中断在屏幕上显示该字符。
  10. MOV AH, 4CH / INT 21H:

    • MOV 指令将 4CH 存储在 AH 寄存器中,表示程序将要退出。然后通过 INT 21H 中断来结束程序的执行。
  11. CSEG ENDS / END START:

    • CSEG ENDS 表示代码段的结束。 END START 表示程序的结束,其中 START 是程序的入口点标
  12. MOV AX, 4C00H

输入输出

DSEG SEGMENT 
DSEG ENDS ;数据段段定义

CSEG SEGMENT ;代码段段定义
ASSUME CS:CSEG,DS:DSEG ;明确段和段寄存器操作
START:
MOV AX,DSEG
MOV DS,AX ;把段地址装入相应段寄存器中

MOV AH,01H ;输入
INT 21H ;DOS调用INT 21H的功能 1 ,等待从键盘输入一个字符,并在显示器上回显

MOV BL,AL ;将输入的数据备份至BL中

MOV DL,0DH ;查表得回车的十六进制为0DH
MOV AH,02H ;显示输出,输出的字符在DL中
INT 21H ;中断

MOV DL,0AH ;查表得换行的十六进制为0AH
MOV AH,02H
INT 21H ;中断
;以上要实现将输入的字符回显至显示器,同时光标换行,为输入与输出不同行做准备
MOV DL,BL ;将输入的字符送至DL中,准备输出
MOV AH,02H ;调用功能 2 ,将DL中值显示出来
INT 21H ;中断

MOV AH,4CH ;带返回码[AL]结束
INT 21H

CSEG ENDS ;代码段结束
END START ;源程序结束伪操作

自己写的

DSEG SEGMENT     ; 数据段定义
DSEG ENDS ; 数据段结束

CSEG SEGMENT ; 代码段定义
ASSUME CS:CSEG,DS:DSEG ; 明确段和段寄存器操作
START: ; 程序入口标签
MOV AX,DSEG ; 将数据段地址加载到 AX 寄存器
MOV DS,AX ; 将 DS 寄存器设置为数据段地址

; 从键盘获取一个字符
MOV AH,01H ; 调用 DOS 功能 01H,从标准输入获取一个字符
INT 21H ; 中断 21H,调用 DOS 功能
MOV BL,AL ; 将输入的字符备份至 BL 寄存器

;; 把这个东西存入CL里面
; 从键盘获取一个字符
MOV AH,01H ; 调用 DOS 功能 01H,从标准输入获取一个字符
INT 21H ; 中断 21H,调用 DOS 功能
MOV CL,AL ; 将输入的字符备份至 BL 寄存器

; 存入回车符
MOV DL,0DH ; 将回车符的 ASCII 码存入 DL 寄存器
MOV AH,02H ; 调用 DOS 功能 02H,输出一个字符
INT 21H ; 中断 21H,调用 DOS 功能

; 输出换行符
MOV DL,0AH ; 将换行符的 ASCII 码存入 DL 寄存器
MOV AH,02H ; 调用 DOS 功能 02H,输出一个字符
INT 21H ; 中断 21H,调用 DOS 功能

; 输出备份的字符
MOV DL,BL ; 将备份的字符送至 DL 寄存器,准备输出
MOV AH,02H ; 调用 DOS 功能 02H,输出一个字符
INT 21H ; 中断 21H,调用 DOS 功能

;; 想要显示的话,把CL,赋值给DL,进行输出中断即可
MOV DL,CL
MOV AH,02H
INT 21H

; 程序退出
MOV AH,4CH ; 调用 DOS 功能 4CH,程序退出
INT 21H ; 中断 21H,调用 DOS 功能

CSEG ENDS ; 代码段结束
END START ; 程序结束伪操作

  1. **回车符 (CR - Carriage Return, ASCII 0x0D)**:在计算机中,回车符通常表示光标移动到当前行的开头。当输出字符时,光标会回到行首,但不会移动到下一行。在老式打字机中,回车的含义是将打印头移至纸张的开头位置。
  2. **换行符 (LF - Line Feed, ASCII 0x0A)**:换行符表示光标向下移动到下一行,但保持在当前列。它将光标从当前行移到下一行的相同列。在老式打字机中,换行的含义是将纸张向上移动一行。

拆字

DSEG    SEGMENT         ;数据段定义
DATA DB 34H ;定义DB(字节 8bit)类型的变量DATA,其值为34H
HEX DB 0,0 ;定义DB类型的变量HEX,即其后连续的一个变量HEX+1,初始值都为0
DSEG ENDS ;数据段定义结束

CSEG SEGMENT ;代码段定义
ASSUME CS:CSEG,DS:DSEG ;将段与段寄存器关联
START:
MOV AX,DSEG
MOV DS,AX ;将数据段首地址送入DS中

MOV AL,DATA ;将待拆字的变量DATA送入AL中(DATA仅一个字节)
MOV BL,AL ;将AL中值送入BL备份

AND AL,0F0H ;将待拆字值高四位与F0H进行与(&)操作,获得第一个数字字符
MOV CL,4 ;需要将高四位移位至第四位,由于位移运算符仅能配合操作数 1,而此次要位移四次,故将移动次数4送入CL中
ROR AL,CL ;ROR(循环右移)配合CL使用,达到高四位变低四位的目的,至此,拆出来的第一个“字”已获得
MOV HEX,AL ;将第一个字符送入要求的HEX中
ADD AL,30H ;为方便后期输出,将寄存器中数值加30H(数字零在十六进制ASCII中为30H)

MOV DL,AL ;将要输出字符送至DL中
MOV AH,02H ;调用系统中断输出显示
INT 21H

MOV DL,0DH ;为达到换行输出,突出效果的目的,换行输出
MOV AH,02H
INT 21H
MOV DL,0AH
MOV AH,02H
INT 21H

AND BL,0FH ;将BL中的备份与0FH相与,达到仅提取出低四位的目的
MOV HEX+1,BL ;BL中已然是拆出来的第二个“字”,送入目标HEX+1中
ADD BL,30H ;为方便后期输出,将寄存器中数值加30H
MOV DL,BL ;将带输出值送入DL
MOV AH,02H ;调用系统中断输出显示
INT 21H

MOV AH,4CH ;将返回码带回结束
INT 21H
CSEG ENDS ;代码段结束
END START ;源程序结束

自己写:

DSEG SEGMENT
DATA DB 34H
HEX DB 0,0
DSEG ENDS

CSEG SEGMENT
ASSUME CS:DSEG,DS:DSEG
START:
MOV AX,DSEG
MOV DS,AX

MOV AL,DATA
MOV BL,AL
AND AL,0F0H
MOV CL,4
ROR AL,CL
ADD AL,30H
MOV HEX,AL


MOV AL,BL
AND AL,0FH
ADD AL,30H
MOV HEX+1,AL

MOV DL,HEX
MOV AH,02H
INT 21H
MOV DL,HEX+1
MOV AH,02H
INT 21H

MOV AH,4CH
INT 21H

CSEG ENDS
END START

循环

DSEG    SEGMENT         ; 数据段开始
DATA DB -1,2,-3,4,-5 ; 字节数组,存储有符号字节数据
COUNT DB 5 ; 元素个数
RLT DB 0 ; 存储结果的字节变量
DSEG ENDS ; 数据段结束

CSEG SEGMENT ; 代码段开始
ASSUME CS:CSEG,DS:DSEG ; 明确段与段寄存器关系
START: ; 程序入口标签
MOV AX,DSEG ; 将数据段地址加载到AX
MOV DS,AX ; 将数据段地址送入DS
MOV BX,OFFSET DATA ; 将数据数组DATA的偏移地址送入BX
MOV CL,COUNT ; 将元素个数送入CL
MOV DX,0 ; 初始化负数个数计数器
AGAIN: ; 循环标签
MOV AL,[BX] ; 将BX指向的内存地址中的值加载到AL
AND AL,AL ; 检查AL的符号位
JNS PLUS ; 如果AL的符号位为正(不为负),跳转到PLUS标签
INC DL ; 如果AL的符号位为负,增加负数个数计数器(DL)

PLUS: ; PLUS标签
INC BX ; 将BX寄存器的值加1,指向下一个数据
LOOP AGAIN ; 循环,如果CL不为零,跳转到AGAIN标签
MOV RLT,DL ; 将负数个数存储在RLT变量中

SHOW: ; 显示标签
ADD DL,30H ; 将DL寄存器中的值加上ASCII数字字符 '0'
MOV AH,02H ; 调用DOS中断服务,显示结果
INT 21H
MOV AX,4C00H ; 通过DOS中断服务结束程序
INT 21H
CSEG ENDS ; 代码段结束
END START ; 程序结束伪操作

LOOP 指令是一个带条件的循环指令,其工作原理是将 CX 寄存器的值减 1,然后检查 CX 是否为零。

LOOP 指令用于执行循环,根据 CX 寄存器中的计数值进行条件跳转。

MOV BX, OFFSET DATA 将数据数组 DATA 的偏移地址加载到 BX 寄存器。在汇编语言中,OFFSET 操作符用于获取标号(如数组、变量等)的偏移地址。在这里,DATA 是一个字节数组,使用 OFFSET DATA 将其偏移地址加载到 BX 寄存器,以便后续访问数组中的元素。

具体而言,BX 寄存器将包含数组 DATA 的起始地址,这样就可以通过 BX 寄存器访问数组中的不同元素。在这个程序中,BX 被用于遍历数组,从而对数组中的元素进行处理。

所以,JNS PLUS 的意思是,如果 AL 寄存器中的值为非负数(符号标志位为 0),则跳转到 PLUS 标签。如果是负数,则继续执行下一条指令。

在这个程序中,CL 寄存器被用作循环计数器。在循环开始之前,CL 寄存器被初始化为 COUNT 变量的值,即元素的个数。然后,在循环的每次迭代中,LOOP 指令会将 CL 寄存器的值减一,并检查结果。如果 CL 不为零,则跳转到指定的标签。

在这个特定的程序中,CL 寄存器的值在每次循环迭代中递减,直到为零。这是通过 LOOP AG 指令实现的,它将 CL 寄存器的值减一,然后检查结果。如果 CL 不为零,就会跳转到 AG 标签,继续循环。

自己写的:

DSEG SEGMENT
DATA DB -1,-2,3,4,5
COUNT DB 5
RLT DB 0
DSEG ENDS

CSEG SEGMENT
ASSUME CS:CSEG,DS:DSEG
START:
MOV AX,DSEG
MOV DS,AX

MOV BX,OFFSET DATA
MOV CL,COUNT
MOV DL,0

AGAIN:
MOV AL,[BX]
AND AL,AL
JNS PLUS
INC DL

PLUS:
INC BX
LOOP AGAIN


SHOW:
ADD DL,30H
MOV RLT,DL
MOV AH,02H
INT 21H
MOV AX,4CH
INT 21H

CSEG ENDS
END START

蜂鸣器

CSEG SEGMENT           ; 代码段开始
ASSUME CS:CSEG ; 段寄存器关联
START: ; 程序入口标签
XOR AX, AX ; 清零 AX 寄存器
XOR CX, CX ; 清零 CX 寄存器
MOV AH, 01H ; 调用 DOS 中断服务功能 01H,等待从键盘输入一个字符,并在显示器上回显
INT 21H

AND AL, AL ; AL 寄存器与自己进行 AND 操作,相当于将 AL 寄存器清零
MOV CL, AL ; 将 AL 寄存器的值复制到 CL 寄存器中

RING: ; 循环标签
MOV DL, 07H ; 将 ASCII 字符 '7' 加载到 DL 寄存器
MOV AH, 02H ; 调用 DOS 中断服务功能 02H,将 DL 寄存器中的字符显示在屏幕上
INT 21H

DEC AL ; AL 寄存器的值减 1
JNZ RING ; 如果 AL 寄存器的值不为零,则跳转到 RING 标签

LOOP RING ; 循环指令,指定循环标签为 RING,CX 寄存器的值减 1,直到 CX 为零

MOV AX, 4C00H ; 通过 DOS 中断服务功能 4C00H 结束程序
INT 21H
CSEG ENDS ; 代码段结束
END START ; 程序结束

JNZ 是 Jump if Not Zero(如果不为零则跳转)的缩写。这条指令会检查 AL 寄存器的值,如果它不为零,则跳转到 RING 标签处,实现循环的目的。

在这里,DEC ALAL 寄存器的值减 1,然后通过 JNZ RING 指令检查 AL 是否为零。如果 AL 不为零,程序将跳转回 RING 标签,继续执行循环的下一轮。这样,程序将循环显示字符 ‘7’ 直到 AL 寄存器的值为零。

这段代码使用了DOS中断21h的功能01h,该功能用于从标准输入(通常是键盘)获取一个字符。下面是这个过程的大致步骤:

  1. MOV AH, 01H:将01H加载到AH寄存器,表示要调用DOS功能01H。
  2. INT 21H:通过执行中断21H指令,程序进入DOS的中断服务例程。

在DOS的中断服务例程中,根据AH寄存器中的值(这里是01H),DOS知道这是一个”等待字符输入”的请求。DOS会等待用户在键盘上输入一个字符,并将该字符放入AL寄存器中。

所以,这两行代码的作用是等待用户从键盘输入一个字符,然后将该字符的ASCII码存储在AL寄存器中。

无bug版:

CSEG SEGMENT           ; 代码段开始
ASSUME CS:CSEG ; 段寄存器关联
START: ; 程序入口标签
XOR AX, AX ; 清零 AX 寄存器
XOR CX, CX ; 清零 CX 寄存器
MOV AH, 01H ; 调用 DOS 中断服务功能 01H,等待从键盘输入一个字符,并在显示器上回显
INT 21H
SUB AL,30H
MOV CL,AL

RING: ; 循环标签
MOV DL, 07H ; 声音字符
MOV AH, 02H ; 调用 DOS ,发出声音
INT 21H

LOOP RING ; 循环指令,指定循环标签为 RING,CX 寄存器的值减 1,直到 AL 为零

MOV AX, 4CH ; 通过 DOS 中断服务功能 4C00H 结束程序
INT 21H

CSEG ENDS ; 代码段结束
END START ; 程序结束

从用户输入的是8,那么存储的是这个数字的ascii值(38H),实际计算需要减掉

LOOP 指令的执行过程如下:

  1. CX 寄存器的值减 1。
  2. 如果 CX 寄存器的值不为零,则跳转到 目标 处执行循环体。
  3. 如果 CX 寄存器的值为零,则继续执行下一条指令。

求方根

DSEG SEGMENT         ; 数据段开始
DATA DW 25 ; 一个字的有符号整数,初始化为25
ROOT DW 0 ; 存储计算结果的变量,初始化为0
DSEG ENDS ; 数据段结束

CSEG SEGMENT ; 代码段开始
ASSUME CS:CSEG, DS:DSEG ; 关联段寄存器
START:
MOV AX, DSEG ; 将数据段地址加载到AX
MOV DS, AX ; 将数据段地址送入DS

XOR CX, CX ; CX 寄存器清零,作为计数器
XOR BX, BX ; BX 寄存器清零,用于计算 1.3.5.7...
XOR AX, AX ; AX 寄存器清零,用于计算
MOV DX, DATA ; 将 DATA 的值加载到 DX 寄存器中

AGAIN:
AND DX, DX ; 将 DX 寄存器与自己进行 AND 操作,检测是不是0
JZ SAVE ; 如果 DX 寄存器为零,则跳转到 SAVE 标签

MOV BX, AX ; 将 AX 寄存器的值复制到 BX 寄存器中
SHL BX, 1 ; 将 BX 寄存器中的值左移一位,相当于乘以2
INC BX ; 将 BX 寄存器中的值加1
SUB DX, BX ; 将 BX 寄存器中的值从 DX 寄存器中减去
INC CX ; CX 寄存器加1
INC AX ; AX 寄存器加1
JMP AGAIN ; 无条件跳转到 AGAIN 标签

SAVE:
MOV ROOT, CX ; 将计算结果存储在 ROOT 变量中

SHOW:
MOV DX, CX ; 将 CX 寄存器的值复制到 DX 寄存器中
ADD DX, 30H ; 将 DX 寄存器的值加上 ASCII 数字字符 '0'
MOV AH, 02H ; 调用 DOS 中断服务,显示结果
INT 21H

MOV AX, 4C00H ; 通过 DOS 中断服务结束程序
INT 21H

CSEG ENDS ; 代码段结束
END START ; 程序结束伪操作

在x86汇编中,DWDB 主要用于声明不同大小的数据单元。选择使用 DW 还是 DB 取决于你要存储的数据的大小。

  • DB(Define Byte)用于声明一个字节(8位)的数据单元。如果你要存储的数据是一个字符、一个字节的整数或其他占用8位的数据,可以使用 DB

  • DW(Define Word)用于声明一个字(16位)的数据单元。如果你要存储的数据是一个16位的整数,可以使用 DW

在给定的例子中,DATA DW 25 表示声明一个16位的整数变量 DATA,并将其初始化为 25。如果你用 DB 来声明,它会被解释为两个独立的字节,而不是一个16位的整数。因此,选择使用 DW 是为了保证正确的数据大小。

显示器会显的话,必须是16进制进行回显,不然达不到自己想要的结果。这就是为什么有时候加减30H的原因。