IA-32:过程调用

TITLE Integer Summation Program
INCLUDE Irvine32.inc
INTEGER_COUNT = 3							; 定义常量:整数个数为3

.data
str1 BYTE "Enter a signed integer:",0
str2 BYTE "The sum of the integers is:",0
array DWORD INTEGER_COUNT DUP(?)			; 定义DWORD类型数组,预留3个整数的存储空间

.code
main PROC
	call Clrscr					; 清空控制台屏幕
	mov esi,OFFSET array		; ESI指向数组首地址(数组操作常用ESI作为源变址寄存器)
	mov ecx,INTEGER_COUNT		; ECX赋值为3(LOOP指令的循环计数器)
	call PromptForIntegers		; 调用子程序:提示并读取用户输入的整数
	call ArraySum				; 调用子程序:计算数组元素和(结果存EAX)
	call DisplaySum				; 调用子程序:显示求和结果
	exit						 ; 程序退出(Irvine32库的退出指令)
main ENDP
;--------------------------------------------
PromptForIntegers PROC USES ecx edx esi
	mov edx,OFFSET STR1			; EDX指向输入提示字符串(WriteString要求字符串地址存EDX)
L1:
	call WriteString			; 输出提示字符串
	call ReadInt				; 读取用户输入的有符号整数(结果存EAX)
	call Crlf					 ; 换行
	mov [esi],eax					; 将输入的整数存入数组当前位置
	add esi,TYPE DWORD			; ESI偏移4字节(DWORD占4字节),指向下一个数组元素
	loop L1
	RET
PromptForIntegers ENDP
;---------------------------------------------
ArraySum PROC USES esi ecx
	mov eax,0
L1:
	add eax,[esi]
	add esi,TYPE DWORD
	loop L1
	ret
ArraySum ENDP
;------------------------------------------------
DisplaySum PROC USES edx
	mov edx,OFFSET STR2
	call WriteString
	call WriteInt
	call Crlf
	ret
DisplaySum ENDP
END main

关键知识点解析:

TYPE指令

在 x86 汇编(尤其是 MASM/TASM/Irvine32 环境)中,TYPE 是一个汇编器伪指令(运算符),用于返回单个数据类型 / 变量 占用的字节数,核心作用是简化内存偏移计算、提高代码可读性。

一、TYPE 核心定义

TYPE 的计算规则:返回指定标识符(数据类型 / 变量 / 数组元素)的基本存储单元字节数,本质是汇编器在编译阶段计算的常量值(非运行时计算)。

语法格式
TYPE 标识符  ; 标识符可以是:数据类型(如DWORD、BYTE)、变量、数组名、数组元素

二、TYPE DWORD 具体含义

DWORD(Double Word,双字)是 x86 汇编中最常用的 32 位数据类型,固定占用 4 字节,因此:

TYPE DWORD  ; 汇编器直接解析为常量 4

在你的代码中:

add esi,TYPE DWORD  ; 等价于 add esi,4

作用是让 ESI 寄存器(数组指针)偏移 4 字节,指向数组的下一个 DWORD 类型元素(因为每个数组元素是 4 字节)。

三、TYPE 对不同数据类型的返回值

数据类型含义TYPE 返回值(字节)
BYTE字节(8 位)1
WORD字(16 位)2
DWORD双字(32 位)4
QWORD四字(64 位)8
FWORD远字(48 位)6
TBYTE十字节(80 位浮点数)10
REAL4单精度浮点数4
REAL8双精度浮点数8
REAL10扩展精度浮点数10

四、TYPE 对变量 / 数组的使用场景

1. 对普通变量使用

如果定义变量:

var1 BYTE 10h    ; 字节变量
var2 WORD 2000h  ; 字变量
var3 DWORD 30000000h ; 双字变量

则:

TYPE var1  ; 返回 1(BYTE占1字节)
TYPE var2  ; 返回 2(WORD占2字节)
TYPE var3  ; 返回 4(DWORD占4字节)
2. 对数组使用

数组的本质是 “相同类型数据的连续存储”,TYPE 对数组名的返回值 = 数组单个元素的字节数:

array DWORD 3 DUP(?)  ; 定义3个DWORD元素的数组
TYPE array            ; 返回 4(单个DWORD元素占4字节)

这也是你的代码中 add esi,TYPE array 等价于 add esi,4 的原因(和 TYPE DWORD 效果一致)。

USES指令

一、USES 指令核心定义

USESMASM/TASM(Microsoft/Turbo Assembler) 专有的伪指令(非 x86 硬件指令),用于简化 IA-32 汇编过程中非易失性寄存器的保存与恢复,本质是编译器级别的语法糖,无需手动编写 push/pop 指令。

❗ 重要限制:

  • 仅支持 MASM/TASM,NASM/GAS 无此伪指令;
  • 仅能在 PROC 定义行使用(PROCUSES 之间空格分隔);
  • 作用域仅限当前过程,过程结束时自动恢复寄存器。

二、USES 语法格式

过程名 PROC [NEAR/FAR] USES 寄存器1 寄存器2 ... 寄存器n
    ; 过程体
过程名 ENDP
  • NEAR/FAR:可选,指定过程调用类型(IA-32 平展内存模型下默认 NEAR);
  • 寄存器列表:空格分隔,支持 IA-32 通用寄存器(eax/ebx/ecx/edx/esi/edi/ebp/ebx 等);
  • 无逗号分隔(常见错误:USES ecx,edx ❌ → USES ecx edx ✔️)。

三、USES 底层实现原理

USES 会在过程入口处自动插入 push 指令,在过程返回前自动插入 pop 指令,且 pop 顺序与 push 相反(栈 “后进先出” 特性)。

示例 1:基础用法
MyProc PROC USES ecx edx esi
    ; 过程体
MyProc ENDP

等价于手动编写:

MyProc PROC
    ; 入口处自动插入(保存寄存器)
    push ecx
    push edx
    push esi

    ; 过程体(你的代码)

    ; 返回前自动插入(恢复寄存器)
    pop esi
    pop edx
    pop ecx
    ret
MyProc ENDP
示例 2:带栈帧的完整等价
MyProc PROC USES ecx edx esi
    push ebp
    mov ebp, esp
    ; 过程体
    pop ebp
    ret
MyProc ENDP

等价于:

MyProc PROC
    ; USES 自动插入(先保存寄存器)
    push ecx
    push edx
    push esi

    ; 手动栈帧
    push ebp
    mov ebp, esp

    ; 过程体

    ; 手动销毁栈帧
    pop ebp

    ; USES 自动插入(后恢复寄存器)
    pop esi
    pop edx
    pop ecx
    ret
MyProc ENDP

四、USES 核心使用规则

1. 寄存器选择原则
寄存器类型是否需要用 USES 保存原因(IA-32 调用约定,如 CDECL/stdcall)
易失性寄存器可选(通常无需)调用者不依赖其值(eax/ecx/edx
非易失性寄存器必须(推荐用 USES调用者期望值不变(esi/edi/ebx/ebp

示例:若过程中修改 esi(非易失性),必须保存 → USES esi;若仅修改 eax(易失性),无需 USES

2. 禁止操作的寄存器
  • ebp:若过程中手动建立栈帧(push ebp; mov ebp, esp),不要放入 USES(会导致栈帧错乱);
  • esp:栈指针寄存器,USES 不支持,也绝对不能手动 push esp(破坏栈结构)。
3. 与 LOOP 指令的兼容

LOOP 指令依赖 ecx 作为计数器,若过程中使用 LOOPecx 需保存,USES ecx 仍有效(push ecx 会保存初始值,pop ecx 恢复):

asm

CountProc PROC USES ecx
    mov ecx, 10    ; 初始化计数器
LoopStart:
    ; 循环体
    loop LoopStart ; ecx--,直到0退出
    ret
CountProc ENDP

→ 过程结束后 ecx 会恢复为调用前的值(而非 0)。

posted @ 2025-12-17 13:30  chenlight  阅读(4)  评论(0)    收藏  举报  来源