尚未解决:输入的姓名、号码不能超过11位,否则将会覆盖到后面结构体分配的内存
头文件
include windows.inc include msvcrt.inc ;包含C语言的库 includelib msvcrt.lib ;包含C语言库对应的lib文件 include kernel32.inc includelib kernel32.lib .data ;定义结构体、声明全局变量 CONTACTSSTRUCT struct szName BYTE 25 dup(0) ;名字 szPhNumber BYTE 12 dup(0) ;电话号码 CONTACTSSTRUCT ends ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> PCONTACTSSTRUCT TYPEDEF PTR CONTACTSSTRUCT ;取别名(指针类型) ;[新的类型名] TYPEDEF PTR [原来的类型名] ;表示取一个指针类型的类型 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ;声明全局变量 g_stContacts CONTACTSSTRUCT 100 dup(<'0','0'>) ;定义结构体数组 g_nCount DWORD 0 ;元素个数 g_nCountMax DWORD 100 ;最大存放元素 g_strTemContacts CONTACTSSTRUCT <'0','0'> ;接收输入信息 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ;定义格式控制符 用于输入输出时用到的格式控制符 g_szScanfFormat BYTE '%s %s',0 g_szScanName BYTE '%s',0 g_nChoose dword 100 ;菜单选择 g_numScanfFormat byte '%d',0 ;菜单选择格式化输入符 ;文件操作 g_szFileWB BYTE 'wb',0 g_szFileRB byte 'rb',0 g_pFile dword 0 g_szFile byte 'data.text',0 ;system功能 g_szCls byte 'cls',0 ;清屏 g_szPause byte 'pause',0 ;暂停 g_szZero byte ' ',0dh,0ah,0 ;用于换行 ;提示 g_szAddStr byte '请输入:用户名 电话号码',0dh,0ah,0 g_szFindInfo byte "请输入姓名:",0dh,0ah,0 g_szInputError byte '输入错误!',0dh,0ah,0 g_szTipNewName byte '请输入新用户名 电话号码',0dh,0ah,0 g_szTipNoExist byte '用户不存在...',0dh,0ah,0 g_szTipNoDate byte '没有数据...',0dh,0ah,0 g_szTipListOver byte '查询完成...',0dh,0ah,0 g_szOK byte '操作成功!',0 g_szMenu byte '请根据选项输入序号',0dh,0ah, '1 - 查看用户',0dh,0ah, '2 - 添加用户',0dh,0ah, '3 - 搜索用户',0dh,0ah, '4 - 修改用户',0dh,0ah, '5 - 删除用户',0dh,0ah, '0 - 退出',0dh,0ah,0 g_szTipSizeError byte '输入数据超出限制',0dh,0ah,0 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .code ;添加用户信息 ADD_USER proc ;无参数 push eax push ebx ;下面是用到该寄存器,先保存一下原来寄存器的值 lea eax, g_szAddStr ;表示第二个操作数的地址,放入第一个操作数中 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ;1.通过push给函数传参(4字节) ;2.调用函数 这是调用的是C语言的库函数,所以要包含头文件: ; include msvcrt.inc ; includelib msvcrt.lib ; 库函数的格式为:crt_xxx ;3.C库函数的是由调用者平衡堆栈 ; 每个参数的大小为4字节,所以传入几个参数则在之后esp加回来 ; add esp,[参数个数*4] ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> push eax ;push r/m32(imu32) 可以是寄存器或内存或立即数 call crt_printf add esp,4 ;根据ecx的值找到下一个结构体名字数组的地址 lea esi,[g_stContacts] ;保存数据的结构体数组 mov ecx,g_nCount ;获取当前已插入的用户个数 mov eax,sizeof(CONTACTSSTRUCT) ;计算结构体的大小(sizeof宏) imul eax,ecx add esi,eax ;移动结构体数组的指针(用户个数*结构体的大小) ;调用crt_scanf函数接收输入的数据 lea eax,[esi+CONTACTSSTRUCT.szPhNumber] ;第一个参数(电话号码) lea edx,[esi+CONTACTSSTRUCT.szName] ;第一个参数(姓名) push eax push edx push offset g_szScanfFormat ;格式控制符 call crt_scanf add esp,0ch ;平衡堆栈 inc g_nCount ;用户个数加1 ;保存数据 call SaveDate ;输入成功 push offset g_szOK ;操作成功 call crt_printf add esp,4 ;暂停 push offset g_szPause call crt_system add esp,4 pop eax ;函数开始push指令对应 pop ebx ret ADD_USER endp ;查询用户信息 FindData proc ;1.输入数据 ;输入查找信息 push offset g_szFindInfo call crt_printf add esp,4 ;清空存放临时数据的结构体 lea edi,[g_strTemContacts] ;保存结构体的地址 mov ebx,sizeof(CONTACTSSTRUCT) push ebx push 0h push edi call crt_memset add esp,12 lea edi,[g_strTemContacts.szName] ;保存结构体中名字的地址 push edi push offset g_szScanName ;格式控制符 call crt_scanf add esp,8 ;平衡堆栈 ;2.开始查询 mov ecx,0 ;初始化循环次数(默认从0开始循环) CYCLE_MARK: cmp ecx,g_nCount ;判断是否结束循环 jz END_F ;2.1根据ecx的值找到下一个结构体名字数组的地址 mov eax,sizeof(CONTACTSSTRUCT) ;计算结构体的大小(sizeif宏) imul eax,ecx lea esi,[g_stContacts] ;保存数据的结构体数组 add esi,eax ;2.2比较字符串 mov eax,ecx ;保存外层循环的次数 mov ecx,6 ;初始化串操作的循环操作(4字节比较) lea edi,[g_strTemContacts.szName] ;保存当前插入的用户名字的地址 repe cmpsd dword ptr[esi],dword ptr[edi] ;查看repe系列指令的使用原理 je CARRIEDOUT_MARK ;如果找到则跳转(输出信息) mov ecx,eax ;如果没有找到则继续外层循环 inc ecx ;层循环次数加1 jmp CYCLE_MARK ;无条件跳转到外层循环开始位置 CARRIEDOUT_MARK: ;输出信息 mov ecx,eax lea esi,[g_stContacts] mov ebx,sizeof(CONTACTSSTRUCT) imul ebx,ecx add esi,ebx lea eax,[esi+CONTACTSSTRUCT.szPhNumber] push eax push offset g_szScanName call crt_printf add esp,8 ;换行 push offset g_szZero call crt_printf add esp,4 push offset g_szOK ;操作成功 call crt_printf add esp,4 ;暂停 push offset g_szPause call crt_system add esp,4 ret END_F: push offset g_szTipNoExist call crt_printf add esp,4 ;暂停 push offset g_szPause call crt_system add esp,4 ret FindData endp ;修改用户信息 ModifyData proc ;因为修改信息的第一步也是先要将当前输入的信息在已保存的数组中查询 ;1.输入数据 ;输入查找信息 push offset g_szFindInfo ;push offset g_szScanName call crt_printf add esp,4 ;清空存放临时数据的结构体 lea edi,[g_strTemContacts] ;保存结构体的地址 mov ebx,sizeof(CONTACTSSTRUCT) push ebx push 0h push edi call crt_memset add esp,12 lea edi,[g_strTemContacts.szName] ;保存结构体中名字的地址 push edi push offset g_szScanName ;格式控制符 call crt_scanf add esp,8 ;平衡堆栈 ;2.开始查询 mov ecx,0 ;初始化循环次数(默认从0开始) CYCLE_MARK: ;标号 cmp ecx,g_nCount ;判断是否结束循环 jz END_M ;2.1根据ecx的值找到下一个结构体名字数组的地址 lea esi,[g_stContacts] ;保存数据的结构体数组 lea edi,[g_strTemContacts.szName] ;获取当前输入要查询的用户名字地址 mov eax,sizeof(CONTACTSSTRUCT) ;计算结构体的大小(sizeof宏) imul eax,ecx add esi,eax ;2.2比较字符串 mov eax,ecx ;保存外层循环的次数 mov ecx,6 ;初始化串串操作的循环次数(4字节比较) repe cmpsd dword ptr[esi],dword ptr[edi] je CARRIEDOUT_MARK ;如果找到则跳转(修正信息) mov ecx,eax ;如果没有找到则继续外层循环 inc ecx ;层循环次数加1 jmp CYCLE_MARK ;无条件跳转到外层循环开始位置 CARRIEDOUT_MARK: ;修改信息 mov ecx,eax lea esi,[g_stContacts] mov ebx,sizeof(CONTACTSSTRUCT) imul ebx,ecx add esi,ebx ;输入新信息 push offset g_szTipNewName call crt_printf add esp,4 lea ebx,[esi+CONTACTSSTRUCT.szName] lea eax,[esi+CONTACTSSTRUCT.szPhNumber] push eax push ebx push offset g_szScanfFormat call crt_scanf add esp,0Ch ;保存数据 call SaveDate push offset g_szOK ;操作成功 call crt_printf add esp,4 ;暂停 push offset g_szPause call crt_system add esp,4 ret END_M: push offset g_szTipNoExist call crt_printf add esp,4 ;暂停 push offset g_szPause call crt_system add esp,4 ret ModifyData endp ;删除用户信息 RemoveDate proc ;1.输入数据 ;输入查找信息 push offset g_szFindInfo ;push offset g_szScanName call crt_printf add esp,4 ;清空存放临时数据的结构体 lea edi,[g_strTemContacts] ;保存结构体的地址 mov ebx,sizeof(CONTACTSSTRUCT) push ebx push 0h push edi call crt_memset add esp,12 lea edi,[g_strTemContacts.szName] ;保存结构体中名字的地址 push edi push offset g_szScanName ;格式控制符 call crt_scanf add esp,8 ;平衡堆栈 ;2.开始查询 mov ecx,0 ;初始化循环次数(默认从0开始循环) CYCLE_MARK: cmp ecx,g_nCount ;判断是否结束循环 jz END_M ;2.1根据ecx的值找到下一个结构体名字数组的地址 lea esi,[g_stContacts] ;保存数据的结构体数组 lea edi,[g_strTemContacts.szName] ;获取当前输入的要查询用户名字地址 mov eax,sizeof(CONTACTSSTRUCT) ;计算结构体的大小 imul eax,ecx add esi,eax ;2.2比较字符串 mov eax,ecx ;保存外层循环的次数 mov ecx,6 ;初始化串操作的循环次数(4字节比较) repe cmpsd dword ptr[esi],dword ptr[edi] je CARRIEDOUT_MARK mov ecx,eax ;没有找到则继续外层循环 inc ecx ;层循环次数加1 jmp CYCLE_MARK ;无条件跳转到外层循环开始位置 CARRIEDOUT_MARK: ;删除 ;将esi设置为当前要删除的结构体数组的首地址 mov ecx,eax ;eax是在上面获取到的表示当前找到的数组的位置 lea edi,[g_stContacts] mov ebx,sizeof(CONTACTSSTRUCT) imul ebx,ecx add edi,ebx ;edi此时保存的是当前要删除的结构体数组的首地址 mov esi,edi mov ebx,sizeof(CONTACTSSTRUCT) add esi,ebx ;esi指向要删除的结构体数组的下一个元素的首地址 add ecx,1 ;因为要保存数组时是从数组0开始的,所以加1用于计算 ;需要移动多少个元素,中间某一个元素被删除了,后面 ;的元素向前移动 mov eax,g_nCount sub eax,ecx ;需要移动的次数 mov ebx,sizeof(CONTACTSSTRUCT) imul ebx,eax ;计算需要移动的字节 mov ecx,ebx rep movs BYTE ptr[edi],BYTE ptr[esi] ;开始移动(以一个字节的大小移动) ;移动完成后删除最后一个结构体中的信息 mov ebx,sizeof(CONTACTSSTRUCT) push ebx ;大小 push 0 ;内容 push edi ;删除的首地址 call crt_memset ;调用初始化函数 add esp,12 dec g_nCount ;保存数据 call SaveDate push offset g_szOK ;操作成功 call crt_printf add esp,4 ;暂停 push offset g_szPause call crt_system add esp,4 ret END_M: push offset g_szTipNoExist call crt_printf add esp,4 ;暂停 push offset g_szPause call crt_system add esp,4 ret RemoveDate endp ;菜单 Menu proc ;打印菜单 push offset g_szMenu call crt_printf add esp,4 ;平衡堆栈 ret Menu endp ;菜单选项分支跳转 MenuJump proc ;比较两个操作数,操作数1-操作数2 cmp g_nChoose,0 jz OP0 cmp g_nChoose,1 jz OP1 cmp g_nChoose,2 jz OP2 cmp g_nChoose,3 jz OP3 cmp g_nChoose,4 jz OP4 cmp g_nChoose,5 jz OP5 ;输入错误 push offset g_szInputError call crt_printf add esp,4 ;暂停 push offset g_szPause call crt_system add esp,4 ;INVOKE GetStdHandle, STD_INPUT_HANDLE ;读取输入句柄,STD_INPUT_HANDLE是win32常数 ;mov consoleInHandle,eax ;保存 ;INVOKE FlushConsoleInputBuffer,consoleInHandle ;清除输入(通常是键盘)缓冲函式 jmp EndMenu_Mark ;退出 OP0: ;exitProcess(0) push 0 call ExitProcess add esp,4 jmp EndMenu_Mark ;查询 OP1: call ListAll jmp EndMenu_Mark ;添加 OP2: call ADD_USER jmp EndMenu_Mark ;搜索 OP3: call FindData jmp EndMenu_Mark ;修改 OP4: call ModifyData jmp EndMenu_Mark ;删除 OP5: call RemoveDate jmp EndMenu_Mark EndMenu_Mark: ret MenuJump endp ;查看所有用户 ListAll proc xor ecx,ecx ;1.对比当前容量 cmp g_nCount,0 je NODATE jne CYCLE_lIST ;无数据 NODATE: push offset g_szTipNoDate call crt_printf add esp,4 jmp EndList_Mark ;列出所有 CYCLE_lIST: push ecx ;2.1地址 lea esi,[g_stContacts] mov ebx,sizeof(CONTACTSSTRUCT) imul ebx,ecx add esi,ebx ;第ecx个结构体地址 ;printf("%s%s",a,b) lea eax,[esi+CONTACTSSTRUCT.szPhNumber] push eax lea eax,[esi+CONTACTSSTRUCT.szName] push eax push offset g_szScanfFormat call crt_printf add esp,0ch push offset g_szZero call crt_printf add esp,4 pop ecx inc ecx ;第一个联系人ecx是0,所以比较前需要加1 cmp dword ptr [g_nCount],ecx ;比较当前输出位置ecx 通讯录人数g_nCount jle EndList_Mark ;相等代表全部打印完,跳到查询结束 ;还有联系人没有打印 ;inc ecx ;前面已经加1了 ,这里不需要再加 jmp CYCLE_lIST ;查询结束 EndList_Mark: ;查询完成 push offset g_szTipListOver call crt_printf add esp,4 ;暂停 push offset g_szPause call crt_system add esp,4 ret ListAll endp ;保存 SaveDate proc push ebp mov ebp,esp ;重置文件指针 mov g_pFile,0 ;打开文件 push offset g_szFileWB push offset g_szFile call crt_fopen add esp ,04h mov dword ptr [g_pFile],eax ;size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream); ;-- buffer:指向数据块的指针 ;-- size:每个数据的大小,单位为Byte(例如:sizeof(int)就是4) ;-- count:数据个数 ;-- stream:文件指针 ;存储联系人数量 push g_pFile ;文件指针 push 1 ;数据个数 push 4 ;每个数据的大小 这里是dword 4字节 lea ebx,[g_nCount] ;数据指针 push ebx call crt_fwrite add esp,10h ;存储文件结构体数组 ;文件指针 push g_pFile ;文件指针 ;数据个数 lea eax,g_nCount push eax ;数据大小 mov ebx,sizeof(CONTACTSSTRUCT) push ebx ;结构体数组首地址 lea esi,[g_stContacts] push esi call crt_fwrite add esp,10h ;关闭文件 push g_pFile call crt_fclose add esp,4 mov dword ptr [g_pFile],0 mov esp,ebp pop ebp ret SaveDate endp LoadDate proc push ebp mov ebp,esp ;重置文件指针 mov g_pFile,0 ;打开文件 push offset g_szFileRB push offset g_szFile call crt_fopen add esp,08h mov g_pFile,eax ;打开文件返回值eax,eax为0打开失败,成功为1 cmp g_pFile,0 je End_Load ;打开失败,跳转 ;size_t fread(void *buffer, size_t size, size_t count, FILE *stream); ;-- buffer:指向数据块的指针 ;-- size:每个数据的大小,单位为Byte(例如:sizeof(int)就是4) ;-- count:数据个数 ;-- stream:文件指针 ;读取联系人数量 push g_pFile ;文件指针 push 1 push 4 lea esi,[g_nCount] ;数据地址 push esi call crt_fread add esp,10h ;读取联系人结构体数组数据 push g_pFile mov eax,g_nCount push eax mov ebx,sizeof(CONTACTSSTRUCT) push ebx lea esi,[g_stContacts] push esi call crt_fread add esp,10h End_Load: ;关闭文件 push g_pFile call crt_fclose add esp,4 mov g_pFile,0 mov esp,ebp pop ebp ret LoadDate endp
主函数
.386 ;汇编语言的伪指令,在80386及以后的处理器中使用该指令集 .model flat,stdcall ;模式定义“model内存模式[,调用模式]” option casemap:none ;选项模式(设定为对大小写敏感) include Contacts.inc .code ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> start: ;主函数 main proc push ebp ;备份ebp mov ebp,esp call LoadDate CYCLE_MAIN: ;清屏 push offset g_szCls call crt_system add esp,4 ;显示菜单 call Menu ;重置g_nChoose lea ebx,[g_numScanfFormat] mov dword ptr[g_nChoose],0h push offset g_nChoose ;输入整型格式控制符 ;push offset g_numScanfFormat push ebx call crt_scanf add esp,8 cmp eax,1 jnz @T1 ;菜单跳转 call MenuJump ;循环 jmp CYCLE_MAIN @T1: call crt_getchar cmp eax,0ah jnz @T1 jmp CYCLE_MAIN MAIN_END: mov esp,ebp pop ebp ret main endp end start end