对于基础汇编的趣味认识
汇编语言

机器指令
机器语言是机器指令的集合
机器指令展开来讲就是一台机器可以正确执行的命令
电子计算机的机器指令是一列二进制数字 (计算机将其转变为一列高低电平,使得计算机的电子器件受到驱动,进行运算

寄存器:微处理器(CPU)中可以存储数据的器件
- 存储单元的地址(地址信息)===》地址线
- 器件的选择 读写命令(控制信息)===》控制线
- 读写数据(数据信息)===》数据线
cpu读取数据流程
。。。。。
。。。。。
要使得CPU工作
- 应该向它输入能够驱动它进行工作的电频信息(机器码)

地址总线
- CPU通过地址总线来指定存储单元
数据总线
- CPU与内存的或者其他器件的数据传送是通过数据总线来进行的
- 数据总线的宽度决定了CPU和外界的数据传送速度
- 八根总线可以一次性传送一个八位的二进制的数据(一个字节)
控制总线
- CPU对外部器件的控制是通过控制总线来进行的,控制总线是一些不同控制线的集合,有多少跟控制总线就意味着CPU提供了对外部器件的多少种控制。====》进而 控制总线的宽度决定了CPU对外
- 部器件的控制能力。
内存地址空间
- 举例说明:一个CPU的地址总线的宽度为10,那么可以寻址1024个内存的单元,这个1024个可以寻址的内存单元 就构成了这个CPU的内存地址空间。
- 存储器都和CPU的总线相连
- CPU对它们进行读写的时候都通过控制线发出内存读写命令

寄存器
- 典型的架构:CPU(运算器,控制器,寄存器。。)
-
- 运算器进行信息处理
- 寄存器进行信息存储
- 控制器控制各种器件进行工作
- 内部总线连接各种器件 在它们之间进行数据传送
物理地址
- 存储空间是一个一维的线性空间 每一个内存单元在这个空间中都有唯一的地址,====》物理地址。
一般来讲:CPU的寻址:基础地址+偏移地址=物理地址
寄存器(内存访问)
CPU要读写一个内存单元的时候,必须先给出这个内存单元的地址====》该地址由段地址和偏移地址组成。
CPU栈操作

在x86架构中,SS 和 SP 寄存器的用途如下:
-
SS (Stack Segment): 这是一个段寄存器,用来指向当前堆栈段的基地址。它与堆栈指针(SP)结合使用,以确定堆栈中数据的确切位置。
SS指向的是堆栈段的起始地址。 -
SP (Stack Pointer): 这是一个16位寄存器(在32位模式下是ESP,在64位模式下是RSP),用来指向堆栈顶的地址。堆栈遵循后进先出(LIFO)的原则,
SP总是指向堆栈中最上面的空闲位置。当数据被压入堆栈时,SP会减小;当数据从堆栈中弹出时,SP会增加。

此外,还有一个相关的寄存器:
- BP (Base Pointer) 或者在32位模式下的
EBP以及64位模式下的RBP: 通常用作帧指针(Frame Pointer)。它用于在函数调用过程中保持对局部变量和参数的引用。在进入一个函数时,通常会将当前的SP值保存到BP中,这样即使SP在函数执行过程中发生变化,也可以通过BP来访问函数的参数和局部变量。
示例
假设我们有一个简单的函数调用过程,来看看这些寄存器是如何工作的:
-
函数调用前:
SS指向堆栈段的基地址。SP指向当前堆栈顶的位置。
-
函数调用时:
- 将返回地址压入堆栈:
push [return address]。 - 将当前
BP的值压入堆栈:push BP。 - 更新
BP为当前SP的值:mov BP, SP。 - 调整
SP以分配局部变量空间:sub SP, n(其中n是局部变量所需的字节数)。
- 将返回地址压入堆栈:
-
函数执行中:
- 使用
BP访问函数的参数和局部变量。例如,如果第一个参数位于BP + 4处,局部变量位于BP - 4处。
- 使用
-
函数返回前:
- 恢复
SP到原来的值:add SP, n。 - 将
BP的值恢复:pop BP。 - 返回到调用点:
ret。
- 恢复
总结
- SS: 指向堆栈段的基地址。
- SP: 指向当前堆栈顶的位置,随着数据的压入和弹出而变化。
- BP/EBP/RBP: 通常用作帧指针,用于在函数调用过程中保持对局部变量和参数的引用。
理解这些寄存器的作用对于编写汇编代码或进行低级编程非常重要。如果你有具体的代码示例或更详细的问题,可以进一步讨论。
案例解析
mov ax, [bx]
功能:
bx 中存放的数据作为一个偏移地址 EA,段地址 SA 默认在 ds 中,
将 SA:EA 处的数据送入 ax 中。
即:(ax)=((ds)*16+(bx))。
这段文字描述了汇编指令mov ax, [bx]的功能。它表示将存储在寄存器BX中的值作为偏移地址,从默认的数据段(DS)中取出对应位置的数据,并将其放入AX寄存器中。
这个指令是汇编语言中的一个基本操作,用于将内存中某个地址的数据复制到寄存器中。
具体来说,这条指令的功能是:
- 将存储在寄存器BX中的值作为偏移地址;
- 默认使用数据段(DS)的段地址作为基址;
- 计算出实际的物理地址(基址+偏移地址),即DS*16+BX;
- 将该物理地址处的数据读取出来,并将其放入AX寄存器中。
换句话说,这条指令的作用是从内存中获取数据并将其保存到AX寄存器中。其中,数据的位置由BX寄存器指定,而数据所在的段则默认为当前的数据段(DS)。通过这种方式,我们可以方便地访问内存中的数据,并进行各种计算或处理。
这张图片中的内容是一条汇编指令及其功能解释:
mov [bx], ax
功能:
bx 中存放的数据作为一个偏移地址 EA,段地址 SA 默认在 ds 中,
将 ax 中的数据送入内存 SA:EA 处。
即:((ds)*16+(bx))=(ax)。
这段文字描述了汇编指令mov [bx], ax的功能。它表示将存储在寄存器AX中的值写入内存中,默认使用数据段(DS)的段地址作为基址,将存储在寄存器BX中的值作为偏移地址,计算出实际的物理地址(基址+偏移地址),并将AX寄存器中的数据写入该物理地址处。
您提到的两条汇编指令分别是 mov ax, [bx] 和 mov [bx], ax。这两条指令在功能上是相反的,它们各自执行的是数据从内存到寄存器以及从寄存器到内存的传输。
-
mov ax, [bx]- 这条指令的作用是从内存中读取数据并将其放入AX寄存器。
- 具体来说,它使用BX寄存器中的值作为偏移地址(EA),结合默认的数据段寄存器DS中的值作为基址(SA),计算出实际的内存地址 (SA*16 + EA)。
- 然后将这个内存地址处的数据复制到AX寄存器中。
- 可以理解为:从内存位置 (DS:BX) 读取一个字(2字节)到AX寄存器。
-
mov [bx], ax- 这条指令的作用是将AX寄存器中的数据写入内存。
- 同样地,它使用BX寄存器中的值作为偏移地址(EA),结合默认的数据段寄存器DS中的值作为基址(SA),计算出实际的内存地址 (SA*16 + EA)。
- 然后将AX寄存器中的数据写入该内存地址。
- 可以理解为:将AX寄存器的内容写入内存位置 (DS:BX)。
总结来说,两者的区别在于数据流动的方向:
mov ax, [bx]是从内存到寄存器的数据移动。mov [bx], ax是从寄存器到内存的数据移动。
- 程序取得所需的空间的方法:在加载中申请空间
案例解析
这是一段用8086汇编语言编写的程序代码。以下是代码的逐行解释:
assume cs:code
此行声明cs段寄存器指向code段。
code segment
定义一个新的名为code的段。
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
定义了一个包含8个十六进制数的字型数据数组。这些数字分别为0123h, 0456h, 0789h, 0abch, 0defh, 0fedh, 0cbah, 0987h。
mov bx,0
mov ax,0
mov cx,8
分别初始化bx、ax和cx寄存器的值。bx设置为0,ax也设置为0,cx设置为8。
s:add ax,cs:[bx]
add bx,2
loop s
这是一个循环结构,其目的是累加数组中的所有数值。首先,将ax寄存器与cs段内的[bx]数据相加,然后增加bx的值2。loop指令会重复执行s标签下的代码块,直到cx寄存器的值变为0为止。
mov ax,4c00h
int 21h
这是退出程序的代码。mov ax,4c00h将ax寄存器设置为特定的DOS中断返回码,int 21h调用DOS中断来结束程序运行。
code ends
end
最后关闭code段,并结束整个程序的定义。
图中是一个8086汇编语言程序,主要实现的功能是在栈中对数据进行压栈和弹栈的操作。下面是详细的代码解析:
assume cs:codesgcodesg segment
声明cs段寄存器指向codesg段。
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
定义了8个字型数据,每个字占用两个字节。这些数据将会被压入堆栈。
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
; 用 dw 定义 16 个字型数据,在程序加载后,将取得 16 个字的
; 内存空间,存放这 16 个数据。在后面的程序中将这段
; 空间当作栈来使用
定义了16个字型数据,每个字占用两个字节。这部分数据用来模拟栈空间。
start:mov ax,csmov ss,axmov sp,30h ;将设置栈顶ss:sp指向cs:30
在start标签下,将cs寄存器的值赋给ss寄存器,这样就使得栈指针ss:sp指向了codesg段的开始位置。同时,将栈指针sp设为30h,也就是栈顶位于codesg段的第30H单元。
mov bx,0mov cx,8
初始化bx和cx寄存器,bx置零,cx置八,因为后面要依次压入8个数据。
s: push cs:[bx]add bx,2loop s ;以上将代码段 0~15 单元中的 8 个字型数据依次入栈
这里使用push指令将cs段内[bx]的值压入栈中,然后将bx加2,再利用loop指令重复这一过程,直到cx减至0为止。这样就完成了前8个数据的压栈操作。
mov bx,0mov cx,8
再次初始化bx和cx寄存器,准备进行弹栈操作。
s0: pop cs:[bx]add bx,2loop s0 ;以上依次出栈 8 个字型数据到代码段 0~15 单元中
这里使用pop指令将栈顶元素弹出并放到cs段内[bx]的位置,然后将bx加2,再利用loop指令重复这一过程,直到cx减至0为止。这样就完成了8个数据的弹栈操作。
mov ax,4c00hint 21h
退出程序的代码。mov ax,4c00h将ax寄存器设置为特定的DOS中断返回码,int 21h调用DOS中断来结束程序运行。
codesg ends
end start ;指明程序的入口在 start 处
关闭codesg段,并说明程序的入口在start处。
编码规范:一套规则 它约定了用什么样的信息来表示现实对象
ASCII规范实例
这段代码是一个简单的8086汇编语言程序,它定义了一些数据并在代码段中进行了少量的处理。以下是代码的逐行解释:
assume cs:code,ds:data
声明cs段寄存器指向code段,ds段寄存器指向data段。
data segment
db 'unix'
db 'fork'
data ends
定义了一个名为data的数据段,包含了两个字符串:“unix"和"fork”。db指令用于定义字节型数据。
code segment
定义了一个名为code的代码段。
start: mov al,'a' ;将字母'a'的ASCII码(0x61)传给almov bl,'b' ;将字母'b'的ASCII码(0x62)传给bl
这两个指令将字符’a’和’b’的ASCII码分别赋值给al和bl寄存器。
mov ax,4c00h
int 21h
退出程序的代码。mov ax,4c00h将ax寄存器设置为特定的DOS中断返回码,int 21h调用DOS中断来结束程序运行。
code ends
end start
关闭code段,并说明程序的入口在start处。
大小写转换
这段代码是一个8086汇编语言程序,它定义了一些数据并在代码段中进行了转换操作。以下是代码的逐行解释:
assume cs:codesg,ds:datasg
声明cs段寄存器指向codesg段,ds段寄存器指向datasg段。
datasg segment
db 'Basic'
db 'information'
datasg ends
定义了一个名为datasg的数据段,包含了两个字符串:“Basic"和"information”。db指令用于定义字节型数据。
codesg segment
start: mov ax,datasgmov ds,axmov bx,0
在start标签下,将datasg段的段地址赋给ax,然后将ax的值赋给ds寄存器,这样ds就指向了数据段。接着将bx清零。
mov cx,5
将计数器cx设置为5,表示接下来的循环次数。
s:mov al,[bx]if (al)>61H,则为小写字母的ASCII码,则: sub al,20Hmov [bx],alinc bxloop s
这是一个循环结构,每次循环都会将ds段内[bx]的值读入al寄存器。如果al中的值大于61H(即对应的小写字母’a’的ASCII码),那么就减去20H(相当于将小写字母转换成大写字母)。然后将al的值写回[bx],并将bx加一。loop指令会重复这一过程,直到cx减至0为止。
codesg ends
end start
关闭codesg段,并说明程序的入口在start处。
这段代码的主要作用是对datasg段中的字符串进行大小写转换,即将小写字母转换为大写字母。具体而言,它遍历了字符串中的前五个字符,如果遇到小写字母,则将其转换为大写字母。
使用异或的方式变换大小写
左侧的代码:
mov cx,5
s:mov al,[bx]if (al)>61H,则为小写字母的ASCII码,则: sub al,20Hmov [bx],alinc bxloop s
这段代码的作用是将字符串中的小写字母转换为大写字母。首先,它设置了循环次数cx=5,这是因为字符串“Basic”有5个字符。然后进入循环,通过mov al,[bx]取出ds段中bx所指单元的ASCII码。如果这个ASCII码大于61H(小写字母’a’的ASCII码),则执行sub al,20H将它转换为大写字母。之后,再将al的值写回到原单元,然后增加bx,继续循环。
右侧的代码:
mov bx,5
mov cx,11
s0:mov al,[bx]or al,00100000Bmov [bx],alinc bxloop s0
这段代码的作用是将字符串中的大写字母转换为小写字母。首先,它设置了循环次数cx=11,这是因为字符串“Information”有11个字符。然后进入循环,通过mov al,[bx]取出ds段中bx所指单元的ASCII码。由于大写字母的第5位(从右数起,也就是二进制表示中的第6位)为0,而小写字母的第5位为1,所以这里使用or al,00100000B将大写字母的第5位置1,使其变为小写字母。然后将al的值写回到原单元,增加bx,继续循环。
右侧的代码:
mov bx,5
mov cx,11
s0:mov al,[bx]and al,11011111Bmov [bx],alinc bxloop s0
这段代码的作用是将字符串中的小写字母转换为大写字母。首先,它设置了循环次数cx=11,这是因为字符串“Information”有11个字符。然后进入循环,通过mov al,[bx]取出ds段中bx所指单元的ASCII码。由于小写字母的第5位(从右数起,也就是二进制表示中的第6位)为1,而大写字母的第5位为0,所以这里使用and al,11011111B将小写字母的第5位置0,使其变为大写字母。然后将al的值写回到原单元,增加bx,继续循环。
右侧的代码:
mov bx,5
mov cx,11
s0:mov al,[bx]or al,00100000Bmov [bx],alinc bxloop s0
这段代码的作用是将字符串中的大写字母转换为小写字母。首先,它设置了循环次数cx=11,这是因为字符串“Information”有11个字符。然后进入循环,通过mov al,[bx]取出ds段中bx所指单元的ASCII码。由于大写字母的第5位(从右数起,也就是二进制表示中的第6位)为0,而小写字母的第5位为1,所以这里使用or al,00100000B将大写字母的第5位置1,使其变为小写字母。然后将al的值写回到原单元,增加bx,继续循环。
右侧的代码:
mov bx,5
mov cx,11
s0:mov al,[bx]and al,11011111Bmov [bx],alinc bxloop s0
这段代码的作用是将字符串中的小写字母转换为大写字母。首先,它设置了循环次数cx=11,这是因为字符串“Information”有11个字符。然后进入循环,通过mov al,[bx]取出ds段中bx所指单元的ASCII码。由于小写字母的第5位(从右数起,也就是二进制表示中的第6位)为1,而大写字母的第5位为0,所以这里使用and al,11011111B将小写字母的第5位置0,使其变为大写字母。然后将al的值写回到原单元,增加bx,继续循环。
右侧的代码:
mov bx,5
mov cx,11
s0:mov al,[bx]or al,00100000Bmov [bx],alinc bxloop s0
这段代码的作用是将字符串中的大写字母转换为小写字母。首先,它设置了循环次数cx=11,这是因为字符串“Information”有11个字符。然后进入循环,通过mov al,[bx]取出ds段中bx所指单元的ASCII码。由于大写字母的第5位(从右数起,也就是二进制表示中的第6位)为0,而小写字母的第5位为1,所以这里使用or al,00100000B将大写字母的第5位置1,使其变为小写字母。然后将al的值写回到原单元,增加bx,继续循环。
右侧的代码:
mov bx,5
mov cx,11
s0:mov al,[bx]and al,11011111Bmov [bx],alinc bxloop s0
这段代码的作用是将字符串中的小写字母转换为大写字母。首先,它设置了循环次数cx=11,这是因为字符串“Information”有11个字符。然后进入循环,通过mov al,[bx]取出ds段中bx所指单元的ASCII码。由于小写字母的第5位(从右数起,也就是二进制表示中的第6位)为1,而大写字母的第5位为0,所以这里使用and al,11011111B将小写字母的第5位置0,使其变为大写字母。然后将al的值写回到原单元,增加bx,继续循环。
类似的C语言程序
'''`'''char a[5]="BaSiC"; // 定义一个长度为5的字符数组a,并初始化为"BASIC"`
`char b[5]="MinIX"; // 定义一个长度为5的字符数组b,并初始化为"MINIX"``main() // 主函数`
`{`
`int i; // 定义一个整型变量i`
`i=0; // 初始化i为0` `do { // 开始一个do-while循环`
`a[i]=a[i]&0xDF; // 对于a数组中的每个字符,将小写字母转换为大写字母`
`b[i]=b[i]|0x20; // 对于b数组中的每个字符,将大写字母转换为小写字母`
`i++; // 增加i的值`
`} while(i<5); // 当i小于5时,继续循环`
`}`'''
这段C语言代码的功能是将字符串中的小写字母转换为大写字母。下面是详细的分析:
char a[5]="BaSiC";
char b[5]="MinIX";
这两行定义了两个字符数组a和b,分别初始化为字符串 “BaSiC” 和 “MinIX”。
main()
{int i;i=0;
这是主函数main()的开始,定义了一个整型变量i并将其初始化为0。
do {a[i]=a[i]&0xDF; // 将a[i]中的小写字母转换为大写字母b[i]=b[i]|0x20; // 将b[i]中的大写字母转换为小写字母i++;
}
while(i<5);
这部分是一个do...while循环,循环条件是i < 5,意味着循环体将会被执行至少一次,然后再根据条件判断是否继续执行。循环体内有两个语句:
-
a[i]=a[i]&0xDF;:这里的&是按位与运算符,0xDF的二进制形式是11011111,它代表的是大写字母的特征。通过将a[i]与0xDF进行按位与运算,可以保留a[i]中的大写字母部分,同时清除小写字母的第5位(从右数起,也就是二进制表示中的第6位),从而实现将小写字母转换为大写字母。 -
b[i]=b[i]|0x20;:这里的|是按位或运算符,0x20的二进制形式是00100000,它代表的是小写字母的特征。通过将b[i]与0x20进行按位或运算,可以在b[i]的大写字母部分添加一个小写字母的标志,从而实现将大写字母转换为小写字母。
最后,i++递增i的值,使得循环能够访问下一个字符。
整个循环结束后,a数组中的所有小写字母都被转换为了大写字母,b数组中的所有大写字母都被转换为了小写字母。
规律
这段文字描述了如何利用ASCII码的二进制形式来区分字母的大小写,并提供了一种新的方法来转换字母的大小写。总结起来,规律如下:
-
大写字母和小写字母除了第5位(从右数起,也就是二进制表示中的第6位)不同外,其他各位都一样。大写字母的ASCII码的第5位为0,小写字母的第5位为1。
-
要将一个字母由小写转为大写,只需要将它的第5位置0即可。因为大写字母的第5位本来就是0,所以不需要改变;而对于小写字母,将第5位置0后,它就会变成大写字母。
-
要将一个字母由大写转为小写,只需要将它的第5位置1即可。对于大写字母,将第5位置1后,它就会变成小写字母;而对于小写字母,第5位已经是1,所以也不需要改变。
这种转换方法的优点在于,我们不再需要先判断字母的大小写,而是直接通过修改特定的位置来完成转换。例如,要将大写字母转换为小写字母,只需将该字母的ASCII码与0x20(二进制形式为00100000)做按位或操作(|),就可以将第5位置1;要将小写字母转换为大写字母,只需将该字母的ASCII码与0xDF(二进制形式为11011111)做按位与操作(&),就可以将第5位清零。
实际案例
以下是一些具体的例子和对应的C语言代码示例,用于说明如何利用上述规律来转换字母的大小写:
将小写字母转换为大写字母
假设有一个小写字母 ‘a’ 的 ASCII 码为 97(十进制),其二进制形式为 01100001。要将其转换为大写字母 ‘A’,我们需要将第5位(从右数起,也就是二进制表示中的第6位)置0,得到 01000001,即 65(十进制)。在 C 语言中,我们可以使用按位与操作(&)来实现这一点:
int small = 'a'; // 小写字母 'a'
small &= 0xDF; // 按位与操作,将第5位清零
将大写字母转换为小写字母
假设有一个大写字母 ‘B’ 的 ASCII 码为 66(十进制),其二进制形式为 01000010。要将其转换为小写字母 ‘b’,我们需要将第5位(从右数起,也就是二进制表示中的第6位)置1,得到 01100010,即 98(十进制)。在 C 语言中,我们可以使用按位或操作(|)来实现这一点:
int capital = 'B'; // 大写字母 'B'
capital |= 0x20; // 按位或操作,将第5位置1
下面是一个完整的 C 语言程序,演示如何将一个字符串中的字母转换为相反的大小写:
#include <stdio.h>int main(void) {char str[] = "BaSiC"; // 包含大写字母和小写字母的字符串int len = sizeof(str) - 1; // 字符串长度减一,因为'\0'不计入for (int i = 0; i < len; ++i) {if ((str[i] >= 'a') && (str[i] <= 'z')) { // 如果当前字符是小写字母str[i] &= 0xDF; // 将小写字母转换为大写字母} else if ((str[i] >= 'A') && (str[i] <= 'Z')) { // 如果当前字符是大写字母str[i] |= 0x20; // 将大写字母转换为小写字母}}printf("转换后的字符串: %s\n", str);return 0;
}
在这个程序中,我们遍历字符串中的每一个字符,检查它们是否为小写字母或大写字母。如果是小写字母,就用按位与操作将其转换为大写字母;如果是大写字母,就用按位或操作将其转换为小写字母。最终输出转换后的字符串。
在ASCII编码中,大写字母和小写字母的二进制表示之间有一个关键的区别:第5位(从右数起,也就是二进制表示中的第6位)。
- 大写字母A到Z的ASCII码范围是65到90(十六进制为41到5A),其二进制形式的第5位始终是0。
- 小写字母a到z的ASCII码范围是97到122(十六进制为61到7A),其二进制形式的第5位始终是1。
例如:
- ‘A’ 的ASCII码是 65 (01000001)
- ‘a’ 的ASCII码是 97 (01100001)
可以看到,除了第5位不同外,其他位都是一样的。为了将一个小写字母转换成对应的大写字母,我们需要做的就是将这个字母的ASCII码的第5位清零(即设置为0)。
0xDF 在二进制下表示为 11011111。当我们使用按位与操作符 & 对一个字符的ASCII码进行运算时,只有当两边的相应位都是1时,结果位才是1;否则结果位是0。因此,0xDF 的每一位都会影响对应的字符位:
- 如果字符的第5位是1(小写字母),那么
0xDF & 字符的结果将是该字符的第5位被清零,其他位保持不变,从而得到对应的大写字母。 - 如果字符的第5位已经是0(大写字母),那么
0xDF & 字符不会改变字符,因为它原本的第5位就是0。
举个例子,如果我们要将小写字母 ‘a’ (01100001) 转换为大写字母 ‘A’ (01000001),我们可以这样操作:
char a = 'a'; // 01100001
a &= 0xDF; // 01100001 & 11011111 = 01000001
在这个过程中,0xDF 的作用就像是一个掩码,它保留了所有位,除了需要清零的第5位。因此,通过与 0xDF 进行按位与操作,我们可以有效地将小写字母转换为其对应的大写字母。
持续输出案例
这段代码是用汇编语言编写的,运行在Intel x86架构上。这是一个简单的程序,用于将数据段中的前8个字节的数据复制到内存地址0000:0010处。接下来我将逐行解释代码。
codesg segment
start:
mov ax, datasg
mov ds, ax
这里设置了代码段寄存器CS指向代码段,并且将数据段寄存器DS也设为代码段的基址,这是因为数据段和代码段在这里是相同的。
mov si, 0
mov cx, 8
设置SI指针指向数据段的起始位置,CX计数器设置为8,表示要复制8个字节的数据。
s: mov ax, [si]
mov 16[si], ax
add si, 2
loop s
这是一个循环结构,每次循环取数据段中SI指向的两个字节(一个字)数据放入AX寄存器,然后将这些数据存储到内存地址0000:0010之后的地址中。每次循环后,SI增加2,直到CX计数器变为0为止。
mov ax, 4c00h
int 21h
退出程序,调用DOS中断INT 21H,传入参数AH=4Ch,表示正常退出程序。
codesg ends
end start
代码段结束标记和程序结束标记。
总的来说,这段代码的作用是从数据段中读取8个字节的数据,并将它们复制到内存地址0000:0010之后的地址中。注意,由于没有指定数据段,所以默认的数据段也是代码段。
8.4 寻址方式
绝大部分机器指令都是在进行数据处理的指令:读取 写入 运算这段文本介绍了三种不同的数据类型及其在汇编语言中的表示方式:
(1) 立即数(idata):立即数是指直接包含在机器指令中的数据,通常以十六进制或二进制的形式出现。例如,mov ax,1 或 add bx,2000h 中的数字 1 和 2000h 是立即数。or bx,00010000b 中的 00010000b 也是一个立即数,它是二进制形式的数值。
(2) 寄存器:指令要处理的数据在寄存器中,汇编指令中给出相应的寄存器名。例如,mov ax,bx 表示将寄存器 BX 的内容移动到 AX 寄存器中。push bx 则将寄存器 BX 的内容压入堆栈。mov ds:[0],bx 将寄存器 BX 的内容放到数据段的偏移地址为 0 的地方。
(3) 段地址(SA)和偏移地址(EA):指令要处理的数据在内存中,汇编指令可用[X]格式给出 EA,SA 在某个段寄存器中。例如,mov ss,ax 将 AX 寄存器的内容赋给 SS 寄存器,mov sp,ax 将 AX 寄存器的内容赋给 SP 寄存器。push ds 将数据段寄存器 DS 的内容压入堆栈。
总之,这段文本主要讲述了汇编语言中数据的不同来源和表示方式,包括立即数、寄存器和内存中的数据。
补充汇编除法运算代码
在8086汇编语言中,除法运算使用DIV和IDIV指令来执行。这两个指令分别用于无符号数(unsigned)和有符号数(signed)的除法操作。下面详细介绍这两个指令及其用法:
无符号数除法 - DIV
DIV指令用于执行无符号数的除法。它通常将AX寄存器中的值除以一个操作数,并将结果存储在AX和DX寄存器中。如果被除数超过16位,则需要使用DX:AX组合来存放被除数。
-
格式:
DIV r/m8: AX / r/m8, 商 -> AL, 余数 -> AHDIV r/m16: DX:AX / r/m16, 商 -> AX, 余数 -> DX
-
示例:
- 对于8位除法:
mov ax, 255 ; 被除数为255 mov bl, 3 ; 除数为3 div bl ; 执行除法 ; 结果: AL = 85 (商), AH = 0 (余数) - 对于16位除法:
mov ax, 255 ; 低16位被除数 mov dx, 0 ; 高16位被除数 mov bx, 3 ; 除数 div bx ; 执行除法 ; 结果: AX = 85 (商), DX = 0 (余数)
- 对于8位除法:
有符号数除法 - IDIV
IDIV指令用于执行有符号数的除法。它的操作方式与DIV类似,但处理的是有符号整数。同样地,它会将结果存储在AX和DX寄存器中,对于更大的数值则使用DX:AX组合。
-
格式:
IDIV r/m8: AX / r/m8, 商 -> AL, 余数 -> AHIDIV r/m16: DX:AX / r/m16, 商 -> AX, 余数 -> DX
-
示例:
- 对于8位除法:
mov ax, 127 ; 被除数为127 mov bl, -3 ; 除数为-3 idiv bl ; 执行除法 ; 结果: AL = -42 (商), AH = 1 (余数) - 对于16位除法:
mov ax, 127 ; 低16位被除数 mov dx, 0 ; 高16位被除数 mov bx, -3 ; 除数 idiv bx ; 执行除法 ; 结果: AX = -42 (商), DX = 1 (余数)
- 对于8位除法:
注意事项
- 在进行除法之前,确保被除数不会导致溢出。如果商太大以至于无法放入目标寄存器中,或者如果除数是0,那么
DIV或IDIV指令将会触发中断。 - 对于16位除法,必须先将高16位(DX)清零或设置适当的值,然后才能执行除法。
- 余数的符号总是与被除数相同。
- 在实际编程时,应该检查除数是否为0,以避免运行时错误。
这些指令在处理数学计算、数据转换等任务时非常有用,尤其是在没有硬件浮点支持的情况下。
## 九章:转移指令的原理
- 可以修改IP 或者同时修改CS IP的指令统称为转移指令
- 就是可以控制CPU执行内存中某处代码的指令
-
jmp指令为无条件转移指令 可以只修改IP 也可以同时修改CS IP
- jmp转移到的目的地址
- 转移的距离(段内短转移,段内近迁移,段间转移)
中断程序
- CPU通过八位的中断类型码通过中断类型向量表找到相应的中断程序的入口地址
小结
- 汇编指令是机器指令的助记符号
- 每一种CPU都有自己的汇编指令集
- CPU可以直接使用的信息在存储器中存放
- 在存储器中 指令和数据都是二进制信息
- 一个存储单元可以存储八个比特
- 每一个CPU芯片都有许多管脚 这些管脚和总线相连 也可以说这些管脚引出CPU总线 一个CPU可以引出三种总线的宽度标志了这CPU的不同性能
-
地址总线宽度:决定了CPU能够寻址的最大空间。地址总线宽度决定了CPU可以访问多少个内存单元。比如,一个16位的地址总线可以寻址216(65,536)个独立的内存单元,而一个32位的地址总线可以寻址232(约4GB)个内存单元。
-
数据总线宽度:决定了CPU与其他部件间一次数据传输的能力。数据总线宽度决定了CPU和其他部件之间一次能传递多少位数据。例如,一个8位的数据总线一次只能传输8位(一个字节)的数据,而一个32位的数据总线一次可以传输32位(四个字节)的数据。
-
控制总线宽度:决定了CPU对系统中其他部件的控制能力。控制总线负责发送各种信号,如读/写命令、中断请求等,控制总线的宽度决定了CPU能够发出多少种不同的控制信号,从而控制系统的其他部件。
简而言之,这三个总线宽度共同决定了CPU的功能和性能。地址总线宽度决定了可寻址的内存容量,数据总线宽度决定了数据传输速度,而控制总线宽度则决定了CPU对整个系统的控制能力。
附加:主存以及显存
主存(也称为RAM,即随机访问存储器)和显存(也称为VRAM,Video RAM或图形内存)是计算机系统中两种不同类型的存储器,它们各自承担不同的角色。
主存 (RAM)
- 功能:主存是计算机的主要工作内存,用于暂时存储程序代码、数据以及操作系统正在使用的其他信息。它允许CPU快速读取和写入数据。
- 类型:常见的有DDR3、DDR4等。
- 速度:通常比硬盘快得多,但比处理器的缓存慢。
- 容量:现代计算机的主存容量从几GB到几十GB不等。
- 用途:支持所有运行中的应用程序和服务,包括但不限于操作系统、浏览器、游戏、办公软件等。
- 访问方式:CPU可以直接访问主存,以获取指令和数据。
显存 (VRAM)
- 功能:显存是专为图形处理单元(GPU)设计的专用内存,用于存储图像、纹理、帧缓冲区等图形相关的数据。它允许GPU高效地处理大量并行的数据。
- 类型:GDDR5、GDDR6等,这些通常是专门为图形卡优化的高速内存。
- 速度:通常比主存更快,因为它需要能够跟上GPU处理图形的速度。
- 容量:显存的容量取决于显卡型号,从几百MB到几十GB不等。
- 用途:主要用于图形渲染、视频编辑、3D建模等对图形性能要求较高的任务。
- 访问方式:显存主要由GPU直接访问,不过在某些情况下,如通过DirectX或OpenGL API,CPU也可以间接地与显存进行交互。
区别
- 目的:主存服务于整个系统的数据需求,而显存专门用于图形处理。
- 访问者:主存主要由CPU访问,显存则由GPU访问。
- 速度:显存通常具有更高的带宽和更低的延迟,以适应图形处理的需求。
- 容量:虽然两者都可以有不同的容量,但显存的容量往往小于主存,因为它的用途更专注于图形相关任务。
交互
尽管主存和显存是分开的,但在实际使用中,两者之间经常会有数据交换。例如,当一个应用程序需要显示图形时,相关的数据可能先被加载到主存中,然后再复制到显存供GPU处理。这种数据传输可以通过DMA(直接内存访问)来加速,减少CPU的工作负担。
补充:通用寄存器 存放一般性的数据
相关文章:
对于基础汇编的趣味认识
汇编语言 机器指令 机器语言是机器指令的集合 机器指令展开来讲就是一台机器可以正确执行的命令 电子计算机的机器指令是一列二进制数字 (计算机将其转变为一列高低电平,使得计算机的电子器件受到驱动,进行运算 寄存器:微处理器…...
网络基础知识笔记(一)
什么是计算机网络 1.计算机网络发展的第一个阶段:(60年代) 标志性事件:ARPANET 关键技术:分组交换 计算机网络发展的第二个阶段:(70-80年代) 标志性事件:NSFNET 关键技术:TCP/IP 计算机网络发展的第三个阶段ÿ…...
fatal: urdf 中的 CRLF 将被 LF 替换
git add relaxed_ik_ros2 fatal: relaxed_ik_ros2/relaxed_ik_core/configs/urdfs/mobile_spot_arm.urdf 中的 CRLF 将被 LF 替换 这个错误信息表示 Git 在处理文件 mobile_spot_arm.urdf 时发现它使用了 CRLF(回车换行符,常见于 Windows 系统࿰…...
构建electron项目
1. 使用electron-vite构建工具 官网链接 安装构建工具 pnpm i electron-vite -g创建electron-vite项目 pnpm create quick-start/electron安装所有依赖 pnpm i其他 pnpm -D add sass scss1. 启动项目 2. 配置 package.json "dev": "electron-vite dev --…...
Stable Diffusion绘画 | 插件-Deforum:动态视频生成(中篇)
本篇文章重点讲解参数最多的 关键帧 模块。 「动画模式」选择「3D」: 下方「运动」Tab 会有一系列参数: 以下4个参数,只有「动画模式」选择「2D」才会生效,可忽略: 运动 平移 X 让镜头左右移动: 大于0&a…...
STM32中断——外部中断
目录 一、概述 二、外部中断(Extern Interrupt简称EXTI) 三、实例-对射式红外传感器 1、配置中断: 2 、完整代码 一、概述 中断:在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当…...
LeetCode78 子集
题目: 给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的 子集(幂集)。 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 示例 1: 输入:nums [1,2,3] 输出:[[…...
《python语言程序设计》2018版第8章19题几何Rectangle2D类(下)-头疼的几何和数学
希望这个下集里能有完整的代码 一、containsPoint实现 先从网上找一下Statement expected, found Py:DEDENTTAB还是空格呢??小小总结如何拆分矩形的四个点呢.我们来小小的测试一下这个函数结果出在哪里呢???修改完成variable in function should be lowercase 函数变量应该…...
【C++】入门基础介绍(上)C++的发展历史与命名空间
文章目录 1. 前言2. C发展历史2. 1 C版本更新特性一览2. 2 关于C23的一个小故事: 3. C的重要性3. 1 编程语言排行榜3. 2 C在工作领域中的应用 4. C学习建议和书籍推荐4. 1 C学习难度4. 2 学习书籍推荐 5. C的第一个程序6. 命名空间6. 1 namespace的价值6. 2 namespace的定义6. …...
dll动态库加载失败导致程序启动报错以及dll库加载失败的常见原因分析与总结
目录 1、问题说明 2、dll库的隐式加载与动态加载 2.1、dll库的隐式加载 2.2、dll库的显式加载 3、使用Process Explorer查看进程加载的dll库信息以及动态加载的dll库有没有加载成功 3.1、使用Process Explorer查看进程加载的dll库信息 3.2、使用Process Explorer查看动态…...
SAP MM学习笔记 - 豆知识10 - OMSY 初期化会计期间,ABAP调用MMPV/MMRV来批量更新会计期间(TODO)
之前用MMRV,MMPV来一次一个月来修改会计期间。 如果是老的测试机,可能是10几年前的,一次1个月,更新到当前期间,搞个100多次,手都抖。 SAP MM学习笔记 - 错误 M7053 - Posting only possible in periods 2…...
Pytorch实现RNN实验
一、实验要求 用 Pytorch 模块的 RNN 实现生成唐诗。要求给定一个字能够生成一首唐诗。 二、实验目的 理解循环神经网络(RNN)的基本原理:通过构建一个基于RNN的诗歌生成模型,学会RNN是如何处理序列数据的,以及如何在…...
四、Drf认证组件
四、Drf认证组件 4.1 快速使用 from django.shortcuts import render,HttpResponse from rest_framework.response import Response from rest_framework.views import APIView from rest_framework.authentication import BaseAuthentication from rest_framework.exception…...
C++:静态成员
静态成员涉及到的关键字尾static 静态成员变量要在类外初始化 去掉static关键字类型类名::变量名 静态成员变量不属于任何对象 所有对象共享一份 静态成员可以不通过对象直接访问 类名::成员名 静态成员依旧受访问修饰符的约束 …...
28 Vue3之搭建公司级项目规范
可以看到保存的时候ref这行被提到了最前面的一行 要求内置库放在组件的前面称为auto fix,数组new arry改成了字面量,这就是我们配置的规范 js规范使用的是airbnb规范模块使用的是antfu 组合prettier&eslint airbnb规范: https://github…...
【pytorch】张量求导3
再接上文,补一下作者未补完的矩阵运算的坑。 首先贴一下原作者的图,将其转化为如下代码: import torch import torch.nn as nn import torch.optim as optim# 定义一个简单的两层神经网络 class TwoLayerNet(nn.Module):def __init__(self):super(TwoLayerNet, self).__in…...
Servlet——springMvc底层原理
我们也先了解一下什么的动态资源,什么是静态资源。 静态资源:无需程序运行就可以获取的资源(照片、html、css、js等) 动态资源:需要通关程序运行才可以获得的资源。 (其实动态、静态的资源都与Servlet有…...
Json 在线可视化工具,分享几个
文章目录 1.json.cn2.json4u.cn3.jsonvisual.com4.jsoncrack5.altearius.github.io6.json.wanvb.com 前序:本文是对多种 Json 在线可视化工具 的介绍、分享。Json官网 https://www.json.org/json-en.html 个人比较中意第四款: https://jsoncrack.com/ed…...
LLM | llama.cpp 安装使用(支持CPU、Metal及CUDA的单卡/多卡推理)
1. 详细步骤 1.1 安装 cuda 等 nvidia 依赖(非CUDA环境运行可跳过) # 以 CUDA Toolkit 12.4: Ubuntu-22.04/24.04(x86_64) 为例,注意区分 WSL 和 Ubuntu,详见 https://developer.nvidia.com/cuda-12-4-1-download-archive?targ…...
矩阵求解复数(aniwoth求解串扰)
所以这种求解串扰的格式是因为,有串扰的共轭项在方程组中 复数共轭项的作用,但是这是二次方程,...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...
使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
golang循环变量捕获问题
在 Go 语言中,当在循环中启动协程(goroutine)时,如果在协程闭包中直接引用循环变量,可能会遇到一个常见的陷阱 - 循环变量捕获问题。让我详细解释一下: 问题背景 看这个代码片段: fo…...
Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)
概述 在 Swift 开发语言中,各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过,在涉及到多个子类派生于基类进行多态模拟的场景下,…...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...
SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题
分区配置 (ptab.json) img 属性介绍: img 属性指定分区存放的 image 名称,指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件,则以 proj_name:binary_name 格式指定文件名, proj_name 为工程 名&…...
【从零学习JVM|第三篇】类的生命周期(高频面试题)
前言: 在Java编程中,类的生命周期是指类从被加载到内存中开始,到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期,让读者对此有深刻印象。 目录 …...
Bean 作用域有哪些?如何答出技术深度?
导语: Spring 面试绕不开 Bean 的作用域问题,这是面试官考察候选人对 Spring 框架理解深度的常见方式。本文将围绕“Spring 中的 Bean 作用域”展开,结合典型面试题及实战场景,帮你厘清重点,打破模板式回答,…...
认识CMake并使用CMake构建自己的第一个项目
1.CMake的作用和优势 跨平台支持:CMake支持多种操作系统和编译器,使用同一份构建配置可以在不同的环境中使用 简化配置:通过CMakeLists.txt文件,用户可以定义项目结构、依赖项、编译选项等,无需手动编写复杂的构建脚本…...
