TITLE Reversing a String include Irvine32.inc .data aName BYTE 'Abraham Lincoln',0 nameSize = ($-aName)-1 ; 字符串长度(减去结束符0) .CODE main PROC ;将aName中的每个字符压入栈 mov ecx,nameSize mov esi,0 L1: movzx eax,aName[esi] push eax inc esi loop L1 ;从堆栈中按反序弹出字符 ;并存储在aName数组中 mov ecx,nameSize mov esi,0 L2: pop eax mov aName[esi],al inc esi loop L2 ;显示aName mov edx,OFFSET aName call WriteString call Crlf exit main ENDP END main关键知识点分析:
一、$表示:当前地址
要理解汇编中$代表的「当前地址」,核心是把汇编过程拆成「汇编器编译代码→分配内存地址」的视角,以下用通俗的方式拆解:
1. 「当前地址」的本质:汇编器的「实时计数器」
汇编器在处理你的代码时,会从一个起始地址(比如程序的数据段起始地址00404000h)开始,逐行解析指令 / 数据,并为每一个字节的内容分配一个唯一的内存地址。
$就是汇编器的「实时地址计数器」—— 它代表汇编器当前正在处理的这一行代码 / 数据,即将被分配的内存地址(或上一行处理完后的下一个空闲地址)。
2. 结合你的代码实例理解
以数据段为例:
.data aName BYTE 'Abraham Lincoln',0 ; 行1 nameSize = ($-aName)-1 ; 行2我们一步步看$在这里的「当前地址」是多少:
步骤 1:汇编器处理行 1(定义 aName)
aName被分配为字符串的起始地址,假设是00404000h;- 字符串
'Abraham Lincoln',0共 16个字节(15 个有效字符 + 1 个结束符 0),因此:- 第 1 个字符 'A' →
00404000h - 第 2 个字符 'b' →
00404001h - ...
- 最后一个字符 0 →
0040400Fh(15 个字节,地址从 00 到 0F);
- 第 1 个字符 'A' →
- 处理完行 1 后,汇编器的「下一个空闲地址」是
00404010h—— 这就是行 2 中$的值。
步骤 2:汇编器处理行 2(计算 nameSize)
$-aName=00404010h - 00404000h= 16(即字符串 + 结束符的总字节数);- 减 1 后
nameSize = 15(有效字符数)。
下面通过VIsual studio 2022的调试功能可以看出,ECX=00 00 00 0F,所以nameSize=15;
二、aName[esi]解析
在汇编语言中,aName[esi]是基于寄存器的内存寻址方式,核心作用是定位字符串中具体的字符位置,以下是详细拆解:
1. 语法含义
aName[esi]等价于[aName + esi],表示:内存地址 = 字符串起始地址(aName) + 偏移量(esi)
aName:数据段中定义的字符串标签,汇编器会将其解析为该字符串在内存中的起始物理地址(比如00404000h);esi:32 位通用寄存器,此处用作「字符索引」,存储从起始地址到目标字符的字节偏移量;- 整体:最终指向字符串中第
esi个字符的内存单元(BYTE 类型,因为 aName 定义为 BYTE)。
三、.inc文件的解析
在汇编语言中,.inc是汇编包含文件(Include File)的扩展名,功能和 C/C++ 中的.h(头文件)完全类似 —— 本质是「预定义代码的集合」,通过include指令引入后,可直接使用其中定义的常量、宏、函数声明、寄存器别名等,无需重复编写。
1..inc文件的核心作用
以你代码中的include Irvine32.inc为例:
asm
include Irvine32.inc ; 引入Irvine32.inc包含文件这条指令会告诉汇编器:把Irvine32.inc文件中的所有内容,原样插入到当前代码的这个位置。
Irvine32.inc是针对 x86 汇编(MASM/TASM 编译器)的经典包含文件,专为《汇编语言程序设计》(Kip Irvine 著)配套,核心内容包括:
| 类别 | 具体内容 |
|---|---|
| 常量定义 | NULL=0、STD_OUTPUT_HANDLE=-11等,替代魔法数字; |
| 寄存器别名 | 简化寄存器写法(如eax可写为EAX,不区分大小写); |
| 函数声明 | WriteString、Crlf、ReadInt等 Irvine32 库函数的原型(告诉汇编器函数的调用规则); |
| 宏定义 | 常用操作的封装(如delay宏,简化延时功能); |
| 数据类型别名 | 兼容不同编译器的类型(如BYTE/WORD/DWORD的统一定义)。 |
2..inc文件的本质:纯文本的汇编代码
.inc文件不是编译后的二进制文件,而是纯文本文件,内容就是普通的汇编指令 / 定义。比如打开Irvine32.inc,能看到类似这样的代码:
; Irvine32.inc中的部分内容 BYTE EQU BYTE WORD EQU WORD DWORD EQU DWORD ; 函数声明:告诉汇编器WriteString的调用规则 WriteString PROTO Crlf PROTO ReadInt PROTO ; 常量定义 STD_INPUT_HANDLE EQU -10 STD_OUTPUT_HANDLE EQU -113. 为什么要用.inc文件?
- 代码复用:把多个程序共用的定义(如 Irvine32 库的函数声明)写在
.inc里,无需在每个程序中重复写; - 统一标准:避免手写常量 / 函数名出错(比如把
WriteString写成WriteStr); - 简化代码:直接用
call WriteString,无需关心函数底层的实现细节; - 跨平台 / 编译器兼容:通过
.inc封装不同编译器的差异(如 MASM 和 NASM 的语法差异)。
4..incvs.h(C 语言头文件)
| 特性 | 汇编.inc文件 | C/C++.h头文件 |
|---|---|---|
| 核心功能 | 引入预定义常量 / 宏 / 函数声明 | 引入宏 / 结构体 / 函数原型 |
| 处理阶段 | 汇编器「预处理」阶段(插入代码) | 编译器预处理阶段(#include) |
| 内容类型 | 汇编指令 / 定义 | C 语法的声明 / 宏 |
| 依赖文件 | 通常配套.lib(库文件) | 通常配套.c/.cpp(实现文件) |
5. 注意事项
include指令的路径:如果.inc文件不在当前代码目录,需要写绝对路径(如include C:\masm32\Irvine32.inc);- 不要重复引入:多次
include同一个.inc可能导致「重复定义」错误; - 配套库文件:
Irvine32.inc声明的函数(如WriteString),实际实现在Irvine32.lib中,链接时需要指定该库文件(否则会报「未定义符号」错误)。
总结
.inc是汇编的「包含文件」,核心是「代码复用 + 统一声明」,你代码中引入Irvine32.inc后,才能直接调用WriteString、Crlf等函数 —— 就像 C 语言中#include <stdio.h>后才能用printf一样。