0 前言
这里给出两种思路,都比王爽老师书上的做法要简单高效,事实上,理解指令的本质,就能达到灵活应用,这样才能打破规则
题目:将内存ffff:0 - ffff:b的数据,复制到内存ffff:10 - ffff:1b中
备注:使用8086汇编语言
本篇的核心是:分别使用8086、x86-32和C语言,理解内存空间的复制。
1 思路一:使用push、pop指令
push,pop指令的本质,是内存传送指令,它也是送数据的。
例如pop DS:[EA]
本质是mov DS:[EA],SS:[SP]
; 思路一:使用push,pop内存转移指令,注意是字型数据,2个2个搬运
assume cs:code
code segment
start:
; DS:[bx]指向0:200
mov ax,0
mov ds,ax
mov bx,0200h
; 设置栈顶
mov ax,0ffffh
mov ss,ax
mov sp,0
; 设置循环
mov cx,6
s:
pop [bx]
add bx,2 ; 一次copy一个字
loop s
mov ax,04c00h
int 021h
code ends
end start
2 思路二:使用段前缀
完全没有必要一个字节一个字节传送,这样需要字节扩展为字再使用,效率低下。
; 思路二:使用段前缀
assume cs:code
code segment
start:
mov ax,0ffffh
mov ds,ax
mov ss,ax
mov bx,0
mov cx,6
s:
mov dx,ds:[bx]
mov ss:[bx+10h],dx
add bx,2
loop s
mov ax,04c00h
int 021h
code ends
end start
3 结合C语言和x86-32汇编语言
这个题目,本质上对应于C语言的基本模型:赋值语句,例如int a = b;
int a = 2;
int b;
b = a;
对应汇编语言
int a = 2;
mov dword ptr [a],2
int b
b = a;
mov eax,dword ptr [a]
mov dword ptr [b],eax
这里也可以看出int b;
变量声明,是不会生成汇编代码的,它的本质就是给某个内存段做个标记,叫b,且它占连续的4个字节。
4 第3部分的扩展:连续内存空间的copy
先来看一个基本数组
int a[10] = { 1,23,4,4,5,6,77,0 };
对应的汇编
00184198 mov dword ptr [a],1
0018419F mov dword ptr [ebp-28h],17h
001841A6 mov dword ptr [ebp-24h],4
001841AD mov dword ptr [ebp-20h],4
001841B4 mov dword ptr [ebp-1Ch],5
001841BB mov dword ptr [ebp-18h],6
001841C2 mov dword ptr [ebp-14h],4Dh
001841C9 mov dword ptr [ebp-10h],0
001841D0 xor eax,eax
001841D2 mov dword ptr [ebp-0Ch],eax
001841D5 mov dword ptr [ebp-8],eax
这里有一个值得注意的地方,就是寄存器的清零,这里使用了xor eax,eax
,一个数跟自己异或,再赋值给自己,必然是0,这个用法比较新奇。当然也可以使用基本语句mov eax,0
。
另外,你可以知道
- 数组被分配了一段连续的内存空间,共40字节
- 地址顺序与数组标号顺序一致,都是从小到大
- 没有被赋值的,默认赋值为0
如果想要将整个数组的内容,复制到另外一个数组int b[10]
怎么办?
最容易想到的做法是,直接循环递增复制
int a[10] = { 1,23,4,4,5,6,77,0 };
int b[10];
for (int i = 0; i < 10; i++) {
b[i] = a[i];
}
对应的汇编语言
1: for (int i = 0; i < 10; i++) {
00DB4D21 mov dword ptr [ebp-78h],0
00DB4D28 jmp 00DB4D33
00DB4D2A mov eax,dword ptr [ebp-78h]
00DB4D2D add eax,1
00DB4D30 mov dword ptr [ebp-78h],eax
00DB4D33 cmp dword ptr [ebp-78h],0Ah
00DB4D37 jge 00DB4D49
2: b[i] = a[i];
00DB4D39 mov eax,dword ptr [ebp-78h]
00DB4D3C mov ecx,dword ptr [ebp-78h]
3: b[i] = a[i];
00DB4D3F mov edx,dword ptr [ebp+ecx*4-30h]
00DB4D43 mov dword ptr [ebp+eax*4-6Ch],edx
4: }
00DB4D47 jmp 00DB4D2A
这种拷贝方式,就是一个个拷贝,很容易想到,但是在C语言中,这一点都不酷,不过这种做法与最开始8086汇编的思路是一致的,一点点转移过去,应该有更酷的内容被使用。
此外,应该了解小知识点是 【寻址方式】,对于语句mov edx,dword ptr [ebp+ecx*4-30h]
中的[ebp+ecx*4-30h]
,地址形成的方式是 ebp + ecx*常数 + 常数。
更酷的方式是memcpy函数(memory copy),也就是内存复制。
int a[10] = { 1,23,4,4,5,6,77,0 };
int b[10];
memcpy(&b, &a,sizeof(a));
这样,就能够直接将整个内存空间拷贝到另外一段内存空间了,不过,这种做法其实就是直接使用封装的函数,本质还是一点点拷贝过去。但是对于高级语言来说,应该更多地使用已经封装好的常用函数,以提高效率。
对于memcoy
的用法,简单说明,(1,2,3)
- 1放的是目标的内存地址空间的首地址,这里是
&b
- 2放的是源内存地址空间的首地址,这里是
&a
- 3放的是需要拷贝过去地址空间的大小,这里是
sizeof(a)
,也就是40个字节