文章目录
- x86 STOS指令详细介绍
- STOS指令概述
- 基本语法格式
- 操作原理
- 执行过程
- 具体操作
- 方向标志的影响
- 寄存器要求
- 基本使用示例
- 示例1:基本STOSB使用
- 示例2:不同数据大小的STOS
- REP前缀与STOS结合
- 示例3:使用REP STOSB填充内存
- 示例4:使用REP STOSD快速清零
- 高级应用示例
- 示例5:创建位图模式
- 示例6:内存初始化函数
- 示例7:反向填充内存
- 性能优化技巧
- 1. 对齐访问
- 2. 循环展开
- 注意事项
- 1. 段寄存器(实模式)
- 2. 保护模式/平坦模式
- 3. 地址溢出
- 4. 性能考虑
- 与其他指令的比较
- 总结
x86 STOS指令详细介绍
STOS指令概述
STOS(Store String)是x86汇编中的字符串存储指令,用于将累加器(AL/AX/EAX)的值存储到内存中,并自动更新目标指针。
基本语法格式
| 指令 | 操作 | 数据大小 |
|---|---|---|
| STOSB | 存储字节 | 8位 |
| STOSW | 存储字 | 16位 |
| STOSD | 存储双字 | 32位 |
| STOSQ | 存储四字 | 64位(x86-64) |
操作原理
执行过程
; 伪代码表示STOS指令的操作 [ES:EDI] ← AL/AX/EAX ; 将累加器的值存储到内存 EDI ← EDI ± 1/2/4/8 ; 根据数据大小调整指针具体操作
- STOSB:
[ES:EDI] = AL; EDI = EDI ± 1 - STOSW:
[ES:EDI] = AX; EDI = EDI ± 2 - STOSD:
[ES:EDI] = EAX; EDI = EDI ± 4 - STOSQ:
[ES:EDI] = RAX; RDI = RDI ± 8(64位)
方向标志的影响
STOS指令受方向标志(DF)控制:
- DF=0(CLD指令清除):EDI递增
- DF=1(STD指令设置):EDI递减
cld ; 清除方向标志(向前,递增) std ; 设置方向标志(向后,递减)寄存器要求
| 寄存器 | 作用 |
|---|---|
| AL/AX/EAX/RAX | 要存储的值 |
| EDI/RDI | 目标内存地址指针(32位使用EDI,64位使用RDI) |
| ES | 段寄存器(实模式/保护模式) |
| ECX/RCX | 与REP前缀一起使用时指定重复次数 |
基本使用示例
示例1:基本STOSB使用
#include<iostream>usingnamespacestd;intmain(){charbuffer[10]={0};_asm{lea edi,buffer;将buffer地址加载到EDI mov al,'A';要存储的字符 cld;正向移动 stosb;存储一个字节 mov al,'B'stosb;存储下一个字节 mov al,'C'stosb;存储第三个字节}cout<<"Buffer内容: "<<buffer<<endl;// 输出: ABCreturn0;}示例2:不同数据大小的STOS
#include<iostream>usingnamespacestd;intmain(){unsignedcharbuffer1[10]={0};unsignedshortbuffer2[10]={0};unsignedintbuffer3[10]={0};_asm{;存储字节 lea edi,buffer1 mov al,0xFFcld stosb;存储字 lea edi,buffer2 mov ax,0x1234stosw;存储双字 lea edi,buffer3 mov eax,0x12345678stosd}cout<<hex;cout<<"STOSB: "<<(int)buffer1[0]<<endl;// 输出: FFcout<<"STOSW: "<<buffer2[0]<<endl;// 输出: 1234cout<<"STOSD: "<<buffer3[0]<<endl;// 输出: 12345678return0;}REP前缀与STOS结合
REP前缀使STOS指令重复执行,这是STOS最常见的用法。
示例3:使用REP STOSB填充内存
#include<iostream>usingnamespacestd;intmain(){charbuffer[100]={0};_asm{lea edi,buffer;目标地址 mov al,'*';要填充的字符 mov ecx,50;填充50次 cld;正向 rep stosb;重复填充}buffer[50]='\0';// 字符串结束符cout<<"填充后: "<<buffer<<endl;// 输出50个*return0;}示例4:使用REP STOSD快速清零
#include<iostream>usingnamespacestd;intmain(){intarray[100]={0};// 初始化为其他值for(inti=0;i<100;i++){array[i]=i+1;}cout<<"清零前array[0]: "<<array[0]<<endl;cout<<"清零前array[99]: "<<array[99]<<endl;_asm{lea edi,array;目标地址xoreax,eax;EAX=0mov ecx,100;100个双字 cld rep stosd;快速清零}cout<<"清零后array[0]: "<<array[0]<<endl;cout<<"清零后array[99]: "<<array[99]<<endl;return0;}高级应用示例
示例5:创建位图模式
#include<iostream>#include<iomanip>usingnamespacestd;intmain(){unsignedcharbitmap[64]={0};// 8x8位图_asm{lea edi,bitmap;创建棋盘模式 mov al,0xAA;10101010mov ecx,4;重复4次 cld pattern_loop:stosb;存储第一行模式 mov al,0x55;01010101stosb;存储第二行模式 mov al,0xAA;切换回第一个模式 loop pattern_loop}cout<<"棋盘位图模式:"<<endl;for(inti=0;i<8;i++){for(intj=7;j>=0;j--){cout<<((bitmap[i]>>j)&1?"█":"░");}cout<<endl;}return0;}示例6:内存初始化函数
#include<iostream>usingnamespacestd;// 使用STOS实现memset功能voidasm_memset(void*dest,intvalue,size_t count){_asm{mov edi,dest;目标地址 mov al,value;填充值 mov ecx,count;字节数 cld rep stosb}}// 使用STOSD实现快速memset(对齐版本)voidasm_memset_fast(void*dest,intvalue,size_t dword_count){_asm{mov edi,dest;目标地址;将字节值扩展到双字 mov al,value mov ah,al mov bx,ax shl eax,16mov ax,bx mov ecx,dword_count;双字数 cld rep stosd}}intmain(){charbuffer1[100];intbuffer2[100];asm_memset(buffer1,'X',50);buffer1[50]='\0';cout<<"buffer1: "<<buffer1<<endl;asm_memset_fast(buffer2,0x42,25);// 填充25个双字cout<<"buffer2[0]: "<<hex<<buffer2[0]<<endl;cout<<"buffer2[24]: "<<hex<<buffer2[24]<<endl;return0;}示例7:反向填充内存
#include<iostream>usingnamespacestd;intmain(){charbuffer[10]="012345678";cout<<"原始: "<<buffer<<endl;_asm{lea edi,buffer[9];指向缓冲区末尾 mov al,'!';要填充的字符 mov ecx,5;填充5次 std;设置方向标志(向后) rep stosb;从后向前填充 cld;恢复方向标志}cout<<"反向填充后: "<<buffer<<endl;// 输出: 01234!!!!!return0;}性能优化技巧
1. 对齐访问
// 使用STOSD需要地址对齐(4字节边界)_asm{lea edi,buffer test edi,3;检查是否4字节对齐 jnz unaligned;如果不对齐,使用字节操作;对齐部分使用STOSD mov eax,value mov ecx,count_dwords rep stosd jmp done unaligned:;不对齐部分使用STOSB mov al,value mov ecx,count_bytes rep stosb done:}2. 循环展开
// 手动展开循环,减少REP开销_asm{lea edi,buffer mov eax,pattern mov ecx,count shr ecx,3;每次迭代处理8个双字 cmp ecx,0je remainder unrolled_loop:stosd;存储8次 stosd stosd stosd stosd stosd stosd stosd loop unrolled_loop remainder:;处理剩余部分}注意事项
1. 段寄存器(实模式)
在实模式下,必须正确设置ES段寄存器:
mov ax, @data mov es, ax ; 设置ES段 lea di, buffer2. 保护模式/平坦模式
在现代操作系统中,通常使用平坦内存模型,ES寄存器通常指向与DS相同的段。
3. 地址溢出
注意EDI指针不要超出缓冲区边界:
// 错误示例:可能溢出charsmall[5];_asm{lea edi,small mov al,'X'mov ecx,10;超过缓冲区大小! rep stosb;缓冲区溢出!}4. 性能考虑
- 对于大块内存操作,STOSD比STOSB快得多
- REP STOS在CPU内部有优化实现
- 在SSE/AVX可用时,SIMD指令可能更快
与其他指令的比较
| 操作 | STOS | 手动循环 | SIMD指令 |
|---|---|---|---|
| 内存填充 | 快速,硬件优化 | 慢 | 最快 |
| 代码简洁性 | 最简洁 | 冗长 | 中等 |
| 灵活性 | 有限 | 最高 | 中等 |
| 兼容性 | 所有x86 | 所有x86 | 需要特定扩展 |
总结
STOS指令是x86架构中高效的内存填充工具,具有以下特点:
- 简单易用:一行指令完成内存存储和指针更新
- 高性能:与REP前缀结合可实现高速内存操作
- 灵活:支持不同数据大小和方向
- 广泛应用:常用于内存初始化、缓冲区清零、模式填充等场景
掌握STOS指令对于编写高效的低级代码和优化性能关键的代码段非常重要。