汇编语言
机器指令
机器语言是机器指令的集合
机器指令展开来讲就是一台机器可以正确执行的命令
电子计算机的机器指令是一列二进制数字 (计算机将其转变为一列高低电平,使得计算机的电子器件受到驱动,进行运算
寄存器:微处理器(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:codesg
codesg 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,cs
mov ss,ax
mov sp,30h ;将设置栈顶ss:sp指向cs:30
在start
标签下,将cs
寄存器的值赋给ss
寄存器,这样就使得栈指针ss:sp
指向了codesg
段的开始位置。同时,将栈指针sp
设为30h,也就是栈顶位于codesg
段的第30H单元。
mov bx,0
mov cx,8
初始化bx
和cx
寄存器,bx
置零,cx
置八,因为后面要依次压入8个数据。
s: push cs:[bx]
add bx,2
loop s ;以上将代码段 0~15 单元中的 8 个字型数据依次入栈
这里使用push
指令将cs
段内[bx]
的值压入栈中,然后将bx
加2,再利用loop
指令重复这一过程,直到cx
减至0为止。这样就完成了前8个数据的压栈操作。
mov bx,0
mov cx,8
再次初始化bx
和cx
寄存器,准备进行弹栈操作。
s0: pop cs:[bx]
add bx,2
loop s0 ;以上依次出栈 8 个字型数据到代码段 0~15 单元中
这里使用pop
指令将栈顶元素弹出并放到cs
段内[bx]
的位置,然后将bx
加2,再利用loop
指令重复这一过程,直到cx
减至0为止。这样就完成了8个数据的弹栈操作。
mov ax,4c00h
int 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)传给al
mov 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,datasg
mov ds,ax
mov bx,0
在start
标签下,将datasg
段的段地址赋给ax
,然后将ax
的值赋给ds
寄存器,这样ds
就指向了数据段。接着将bx
清零。
mov cx,5
将计数器cx
设置为5,表示接下来的循环次数。
s:mov al,[bx]
if (al)>61H,则为小写字母的ASCII码,则: sub al,20H
mov [bx],al
inc bx
loop 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,20H
mov [bx],al
inc bx
loop 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,00100000B
mov [bx],al
inc bx
loop 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,11011111B
mov [bx],al
inc bx
loop 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,00100000B
mov [bx],al
inc bx
loop 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,11011111B
mov [bx],al
inc bx
loop 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,00100000B
mov [bx],al
inc bx
loop 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,11011111B
mov [bx],al
inc bx
loop 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的工作负担。