汇编语言非双字局部变量

在声明不同大小的局部变量时,LOCAL 伪指令的操作会变得很有趣。每个变量都按照其大小来分配空间:8 位的变量分配给下一个可用的字节,16 位的变量分配给下一个偶地址(字对齐),32 位变量分配给下一个双字对齐的地址。

现在来看几个例子。首先,Example 过程含有一个局部变量 var1,类型为 BYTE:
Example1 PROC
    LOCAL var1:byte
    mov al,var1       ;[EBP-1]
    ret
Example1 ENDP
由于 32 位模式中,堆栈偏移量默认为 32 位,因此,var1 可能被认为会存放于 EBP-4 的位置。实际上,如下图所示,MASM 将 EBP 减去 4,但是却把 var1 存放在 EBP-1,其下面的三个字节并未使用(用 nu 标记,表示没有使用)。图中,每个方块表示一个字节。

为局部变量保留空间

过程 Example2 含一个双字局部变量和一个字节局部变量:

Example2 PROC
    local temp:dword, SwapFlag:BYTE
    ...
    ret
Example2 ENDP

汇编器为 Example2 生成的代码如下所示。ADD 指令将 ESP 加 -8,在 ESP 和 EBP 之间为这两个局部变量预留了空间:
push ebp
mov ebp, esp
add esp,0FFFFFFF8h     ; ESP+(-8)
mov eax,[ebp-4]        ; temp
mov bl,[ebp-5]         ; SwapFlag
leave
ret
虽然 SwapFlag 只是一个字节变量,但是 ESP 还是会下移到堆栈中下一个双字的位置。下图以字节为单位详细展示了堆栈的情况:SwapFlag 确切的位置以及位于其下方的三个没有使用的空间(用 nu 标记)。图中,每个方块表示一个字节。

Example2中为局部变量保留空间

如果要创建超过几百字节的数组作为局部变量,那么一定要确保为运行时堆栈预留足够的空间。此时可以使用 STACK 伪指令。比如,在 Irvine32 链接库中,要预留 4096 个字节的堆栈空间:

.stack 4096

对嵌套调用来说,不论程序执行到哪一步,运行时堆栈都必须大到能够容纳下全部的活跃局部变量。比如在下面的代码中,Sub1 调用 Sub2,Sub2 调用 Sub3,每个过程都有一个局部数组变量:

Sub1 PROC
local array1 [50]:dword        ; 200 字节
callSub2
...
ret
Sub1 ENDP

Sub2 PROC
local array2 [80]:word         ; 160 字节
callSub3
...
ret
Sub2 ENDP

Sub3 PROC
local array3 [300]:dword       ; 1200 字节
...
ret
Sub3 ENDP

当程序进入 Sub3 时,运行时堆栈中有来自 Sub1、Sub2 和 Sub3 的全部局部变量。那么堆栈总共需要:1560 个字节保存局部变量,加上两个过程的返回地址(8 字节),还要加上在过程中入栈的所有寄存器占用的空间。若过程为递归调用,则堆栈空间大约为其局部变量与参数总的大小乘以预计的递归次数。