概要
两种实现方法——分支指令实现和专门的循环语句实现以及有关循环的优化。
分支指令实现
倒计数
……
MOV ECX,循环次数
LOOPA:……
……
DEC ECX
JNE LOOPA
正计数
……
MOV ECX,0
LOOPA:
……
INC ECX
CMP ECX, n
JNE LOOPA
循环次数不固定
比如要求一个以0为结束符的字符串的长度,需要通过指令来测试条件是否成立,决定继续循环还是结束循环。
下面例子(AX)中 1 出现的次数 -> CL
MOV CL, 0
L: AND AX , AX
JZ EXIT
SAL AX , 1
JNC L
INC CL
JMP L
EXIT:
或者这样
MOV CL, 0
MOV BX, 16
L: SAL AX , 1
JNC NEXT
INC CL
NEXT: DEC BX
JNZ L
专门的循环指令
LOOP 标号
LOOPE 标号
LOOPNE 标号
JECXZ 标号
LOOP
① (ECX) -1 ➡ ECX,
②若 (ECX) 不为0, 则转标号处执行。
基本等价于:
DEC ECX
JNZ 标号
(但LOOP指令对标志位无影响!)
LOOPE / LOOPZ
①(ECX) - 1 ➡ ECX,
② 若(ECX)不为0, 且ZF=1,则转标号处执行。
(等于或为0循环转移指令, 本指令对标志位无影响)
32位段用 ECX, 16位段用 CX
例:判断以BUF为首址的10个字节中是否有非0字节。
有,则置ZF为0, 否则ZF置为1。
MOV ECX, 10
MOV EBX, OFFSET BUF -1
L3 : INC EBX
CMP BYTE PTR [EBX], 0
LOOPE L3
LOOPNE / LOOPNZ
①(ECX) -1 ➡ ECX
②若(ECX)≠0, 且ZF=0,则转标号处执行。
例:判断以MSG为首址的10个字节中的串中是否有空格字符。无空格字符,置ZF为0,否则为1。
MOV ECX, 10
MOV EBX, OFFSET MSG -1
L4 : INC EBX
CMP BYTE PTR [EBX],‘ ’
LOOPNE L4
JECXZ 标号
若 (ECX) 为0, 则转标号处执行。
(先判断,后执行循环体时,可用此语句,标号为循环结束处)
有关循环的优化方法
循环展开
int i = 0, sum = 0, a[5];
……
for (i = 0; i < 5; i++) sum += a[i];
Debug版本:
00D71750 mov dword ptr [i],0
00D71757 jmp f+62h (0D71762h)
00D71759 mov eax,dword ptr [i]
00D7175C add eax,1
00D7175F mov dword ptr [i],eax
00D71762 cmp dword ptr [i],5
00D71766 jge f+77h (0D71777h)
00D71768 mov eax,dword ptr [i]
00D7176B mov ecx,dword ptr [sum]
00D7176E add ecx,dword ptr a[eax*4]
00D71772 mov dword ptr [sum],ecx
00D71775 jmp f+59h (0D71759h)
00D71777 // 循环结束
Release版本:
mov eax,dword ptr [ebp-8]
add eax,dword ptr [ebp-0Ch]
add eax,dword ptr [ebp-10h]
add eax,dword ptr [ebp-14h]
add eax,dword ptr [ebp-18h]
mov sum, eax
若循环变量从5改为n
mov edi, sum ; edi来存放 和
xor eax, eax ; eax 对应 i
mov edx, n ; edx 对应 n
jmp main+0C5h (05E1145h)
005E1140 add edi,dword ptr [ebp+eax*4-18h]
005E1144 inc eax
005E1145 cmp eax,edx
005E1147 jl main+0C0h (05E1140h)
变量与寄存器绑定,语句数量大幅减少,循环部分由 10 条语句减为 4 条语句
传输优化
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void main()
{
char buf1[20];
char buf2[20];
int i;
scanf("%s", buf1);
for (i = 0;i < 20;i++)
buf2[i] = buf1[i];
printf("%s\n", buf2);
return;
}
for (i = 0;i < 20;i++)
buf2[i] = buf1[i];
printf("%s\n", buf2);
0025109E mov eax,dword ptr [ebp-8]
002510A1 movups xmm0,xmmword ptr [buf1]
002510A5 mov dword ptr [ebp-1Ch],eax
002510A8 lea eax,[buf2]
002510AB push eax
002510AC push offset string "%s\n" (0252104h)
002510B1 movups xmmword ptr [buf2],xmm0
002510B5 call printf (0251020h)
可以看到,ebp - 8就是buf1+16
buf1 的前16个字节拷贝到 xmm0,后4个字节拷贝到 eax,再分别送到 buf2 相应位置
但是像这种,就不好优化
void main()
{
char buf1[20];
char buf2[20];
int i;
scanf("%s", buf1);
……
printf("%s\n", buf2);
return;
}
void fcopy(char* dst, char* src)
{
int i;
for (i = 0;i < 20;i++)
{
*dst = *src;
dst++;
src++;
}
}
scanf("%s", buf1);
003D1090 lea eax,[ebp-18h]
003D1093 push eax
003D1094 push 3D2100h
003D1099 call 003D1050
003D109E add esp,8
003D10A1 xor eax,eax
fcopy(buf1-20, buf1);
003D10A3 mov cl,byte ptr [ebp+eax-18h]
003D10A7 mov byte ptr [ebp+eax-2Ch],cl
003D10AB inc eax
003D10AC cmp eax,14h
003D10AF jl 003D10A3
printf("%s\n", buf2);
总结
编译优化
- 循环展开:消除了循环
- 与寄存器绑定:减少访存操作,减少指令
- 用XMM寄存器、成组运算等,减少指令