数据段是程序在内存中存储其全局和静态数据的地方。该数据在编译时定义。数据段不保存运行时分配的变量(堆用于此目的)或子进程本地的变量(栈用于保存这些变量)。这里提供的大部分信息将用于任何细分市场。例如,变量可以在未初始化的数据段()中声明。数据)或常量数据段(。常数)。
| | 注意:在数据段中声明的所有变量在实际中都会变成字节。exe 文件。它们不是在程序运行时生成的;它们是从。exe 文件。创建一个包含 150 兆字节变量的数据段将生成一个 150 兆字节的。并且需要很长时间来编译。 |
数据段中定义的标量数据被赋予名称、大小和可选的初始值。在数据段中声明一个变量就是命名一个从数据段开始的偏移量。所有的变量名实际上都是指针;它们是一个数字,指的是内存中数据段的偏移量,因此程序员不必将偏移量记为数字。
| | 注意:汇编中的变量有时被称为标签,但是为了避免与代码段中的标签混淆,我将数据段标签称为变量。 |
要在数据段中定义变量,一般布局如下:
[VarName] [Type] [Initial Value]
其中[VarName]
是任何合法的变量名,并且将是您希望用作引用的数据点的名称。
| | 注意:变量名的规则与 C++ 相同。它们不能以数字开头,可以包含字母、数字和下划线。还可以使用一些 C++ 中不合法的附加符号,比如@和?。 |
[Type]
是数据类型,可以是 基础数据类型 表的 ASM 列中的任何一种数据类型或短版本。
初始值可以是文字值,也可以是“?”。问号表示数据没有初始值。实际上,即使数据未初始化,也会给它一个值。“?”的意义声明程序员不关心数据最初被设置为什么值,并且推测程序将在使用数据之前设置一些其他的初始值。
以下是在数据段中定义简单标量数据类型的一些示例。
.data
myByte db 0 ; Defines a byte set to 0 called myByte
patientID dw ? ; Defines a word, uninitialized called patientID
averageSpeed dt 0.0 ; Defines 10-byte real, reals must have a decimal
; point if initialized
totalCost sdword 5000 ; Defines signed dword set to 5000, called totalCost
| | 注意:第一个变量放在 DS:0 处(它放在数据段的第一个字节处),第二个放在其后(变量之间没有填充间距)。如果第一个变量是 1 字节,那么第二个变量将是 DS:1。如果第一个变量是一个单词,那么第二个变量就是 DS:2。连续变量存储在内存中的方式称为对齐,这对于性能很重要,因为一些最快的数据处理指令要求数据对齐到 16 个字节。 |
在标量数据类型之后,下一个最基本的数据类型可能是数组。数组是连续内存中相同数据类型的元素列表。在汇编中,数组只是一个内存块,数组的第一个元素被赋予一个名称。
您可以声明由逗号分隔的数组元素。
MyWord dw 1, 2, 3, 4 ; Makes a 4 word array with 1, 2, 3, and 4 as elements
如果需要使用多行,请用逗号结束一行,然后继续下一行。
MyWord dw 1, 2, 3, 4, ; Four words in an array
5, 6, 7, 8 ; Another four words in the same array!
这是合法的,因为你实际上根本不需要这个标签。变量的 MyWord 名称是完全可选的。
您可以使用重复语法在数据段中创建更大的数组,但是请记住,数据段中的每个字节都是最终文件中的一个字节。
要创建更大的数组,可以使用以下模式(重复语法)声明一个值数组:
[Name] [type] [n] [dup (?)]
其中[Name]
为数组名,任意合法变量名。[Type]
是 基础数据类型 表中的数据类型之一,[n]
是数组中的项数。DUP
是重复的缩写,它复制的数据在括号中。要在名为 MyArray 的数组中将 50 个单词全部设置为 25,使用重复语法的数组声明如下:
MyArray word 50 dup (25)
您可以将简单的逗号分隔数组定义语法与重复语法结合起来,生成重复模式的数组。
MyArray byte 50 dup (1, 6, 8)
这将定义一个 150 字节长(50×3)的数组,其重复模式为 1、6、8、1、6、8、1、6、8....
您可以嵌套重复的指令来创建多维数组。例如,要创建一个 10×25 维字节数组并将所有元素设置为 A,可以使用以下方法:
MyMArray byte 10 dup (25 dup ('A'))
| | 注意:内存是线性的。样本代码实际定义的是 10×25 还是 25×10 数组,必须由程序员决定。对 CPU 来说,它只是一块线性 RAM,并不存在多维数组这种东西。 |
对于三维数组,您可以使用类似这样的东西:
My3dArray byte 10 dup (25 dup (100 dup (0)))
这将创建一个 10×25×100 的三维字节数组,所有字节都设置为 0。从中央处理器的角度来看,这个三维阵列与以下完全相同:
My3dArray byte 25000 dup (0)
定义后,MASM 有一些指令来检索关于阵列的信息:
lengthof
:以元素为单位返回数组的长度。
sizeof
:以字节为单位返回数组的长度。
type
:以字节为单位返回数组的元素大小。
例如,如果您有一个名为 myArray 的数组,并且想要将关于它的信息移动到 AX 中,您可以执行以下操作:
mov ax, lengthof myArray ; Move length in elements of the array
mov ax, sizeof myArray ; Move the size in bytes of the array
mov ax, type myArray ; Move the element size into AX
| | 注意:在汇编文件之前,这些指令被转换为即时值;因此,“mov lengthof myArray,200”实际上被翻译为“mov 16,200”。将值移动到文字常量中没有任何意义(即使在汇编中,我们也不能更改 16 的含义),因此该行是非法的。 |
在 MASM,单引号和双引号完全相同。它们用于字符串和单个字符。
| | 注意:C 和 C++ 中的字符串是一个字节数组,通常在末尾有一个空值 0。这些类型的字符串称为零分隔字符串。许多 C++ 函数被设计为使用零分隔字符串。 |
要定义文本字符串,可以使用以下字符串语法:
errMess db 'You do not have permission to do this thing, lol', 0
这相当于定义一个字节数组,其值设置为字符串中字符的 ASCII 数字。开始处的 Y 将是数组的第一个(最低有效)字节。
| | 注意:末尾的逗号和零是最后的空值。这使得该字符串成为 cout 和其他 C++ 函数所理解的空终止字符串。当字符串到达 0 字符时,Cout 停止写入。在 C++ 中,当我们使用双引号时,会自动添加 0;在程序集中,它必须是显式的。 |
要定义不适合单行的字符串,可以使用以下方法:
myLongString db "This is a ",
"string that does not ",
"fit on a single lion!", 0
在前面的每个示例中,单引号也可以用于相同的效果:
myLongString db 'This is a ',
'string that does not',
'fit on a single lion!', 0
如果需要在单引号数组中使用单引号或在双引号数组中使用双引号,请将其中两个放在一起:
myArr1 db "This ""IS"" Tom's array!", 0 ; This "IS" Tom's array!
myArr2 db 'That''s good, who''s Tom?', 0 ; That's good, who's Tom?
您可以使用类型定义(typedef
)指令为数据类型声明自己的名称。
integer typedef sword ; Defines “integer” to mean sword
MyInteger integer ? ; Defines a new sword called MyInteger
您不能为您的 typedefs 使用保留字,因此尝试创建名为“int”的带符号 dword 类型将不起作用,因为“int”是调用中断的 x86 指令。
| | 注意:可以使用 typedef 为用户定义的类型、基本类型、结构、联合和记录定义新名称。 |
要定义一个结构(类似于 C++ 结构),可以使用 struc(或 struct)指令。
ExampleStructure struct ; Structure name followed by "struct" or "struc"
X word 0
Y word 0
Z word 0
ID byte 0
ExampleStructure ends ; The name followed by “ends” closes the definition
这将创建一个具有四个变量的结构;三个是设置为 0 的单词,称为 X、Y 和 Z,最后一个变量是一个名为 ID 的字节,默认情况下也设置为 0。
前面的例子是原型。要创建先前结构原型的实例,可以使用以下方法:
person1 ExampleStructure { } ; Declares person1 with default values
person2 ExampleStructure { 10, 25, 8, ? } ; Declares person2 with
; specific values
; and ID of ?, or 0 probably
结构实例的每个字段都可以用花括号中按顺序提供的值进行初始化。使用“?”初始化为 MASM 的默认值(0)。您可以初始化的值少于结构和其他部分自动给出的值。根据结构的原型,这些是它们的默认值。
person2 ExampleStructure { 10 } ; Declares person2 with 10 for x
; but the rest are as per the
; structure's prototype
通过原型声明,您可以通过不包含任何值来创建该结构的实例,其中一些值已初始化,而另一些值具有默认值。只需在空格处加一个逗号来表示值的位置。
MyStructure struct
x word 5
y word 7
MyStructure ends
InstanceOfStruct MyStructure { 9, } ; Change x to 9 but keep y
; as 5 as per prototype
要从代码中更改以前实例化的结构的值,可以使用句点,类似于在 C++ 中访问结构元素的方式。
mov person1.X, 25 ; Moves 25 into person1's X
mov person2.ID, 90 ; Moves 90 into person2's ID
| | 注意:当结构从 C++ 传递给函数时,它们不是通过引用传递的。根据结构的大小,它们被复制到寄存器和栈中。如果一个结构有两个整数,那么这个结构的整个实例将被复制到 RCX(因为两个 32 位的 dwords 适合 64 位的 RCX)。这很尴尬,因为当结构的各个元素在寄存器中时,不能引用它们。例如,没有办法参考 RCX 的顶级 dword。因此,将 C++ 中的结构作为指针传递可能更容易。 |
您可以使用 LEA 指令(加载有效地址)加载先前实例化结构的有效地址。要使用寄存器(本例中为 RCX)作为指向结构实例的指针,您必须告诉 MASM 地址、指向的结构类型和字段。
lea rcx, person1 ; Loads the address of person1 into RCX
mov [rcx].ExampleStructure.X, 200 ; Moves 200 into person1.X using
; RCX as a pointer
中央处理器不会检查以确保 RCX 实际上指向一个示例结构实例。RCX 可能指向任何东西。[RCX].ExampleStructure.X
简单的意思是找到 RCX 所指的东西,并把 X 在示例结构原型中的偏移量加到这个地址上。换句话说,[RCX].ExampleStructure.X
翻译成RCX+0
,因为在范例结构的原型中,X 的字节数是 0。[RCX].ExampleStructure.Y
翻译为RCX+2
,因为 Y 是两个字节单词 X 之后的第二个元素
要将结构的实例作为参数传递给函数,通常会传递其地址,并按照前面的观点对其进行操作。这是通过引用传递的,初始对象会发生变化,但这比用 C++ 的方式将结构的数据复制到寄存器和栈要快得多。
; This is the function that is initially called
Function1 proc
lea rcx, person2 ; Load *person2 into RCX to be passed to Fiddle
call Fiddle ; Call Fiddle with RCX param 1
ret
Function1 endp
; Fiddle, RCX = *ExampleStructure
Fiddle proc
mov [rcx].ExampleStructure.Y, 89 ; Change something
ret
Fiddle endp
若要定义具有较小子结构作为其成员变量的结构,请先声明较小的子结构。然后将子结构的实例放在更大结构的声明中。
; This is the smaller sub-structure
Point struct
X word 0
y word 0
Point ends
; This is the larger structure that owns a Point as one of its parameters:
Square struct
cnr1 Point { 7, 4 } ; This one uses 7 and 4
cnr2 Point { } ; Use default parameters!
Square ends
若要声明包含数据段中子结构的结构实例,可以使用嵌套的大括号。
MySquare Square { { 9, 8 }, { ?, 7 } }
| | 注意:如果不想设置结构的任何值,可以使用{}来表示所有值的默认值,即使结构中有子结构。 |
若要设置结构子结构的值,请附加一个句点来指定要更改的变量。
mov MySquare.cnr1.Y, 5
您可以使用寄存器作为指针,并引用嵌套结构的元素,如下所示:
mov word ptr [rcx].Square.cnr1.X, 10
联合类似于结构,只是联合中每个元素使用的实际内存物理上位于内存中的相同位置。联合是一种在内存中引用同一地址作为多种数据类型的方式。
MyUnion union
w1 word 0
d1 dword 0
MyUnion ends ; Note that it is ends, not endu!
这里明 w1 和明 w2 的地址完全一样。dword 版本为 4 字节长,字只有 2 字节,但两者的最低有效字节具有相同的地址。
记录是 MASM 的另一种复杂数据类型。它们就像结构一样,但它们是在位级别上工作和定义的。定义的语法如下:
[name] RECORD [fldName:sz], [fldName:sz]...
其中[name]
是记录的名称,[fldName]
是字段的名称,[sz]
是字段的大小(以位为单位)。
color RECORD blBit:1, hueNib:4
数据段中的样本代码是名为颜色的记录的原型。然后可以通过以下方式访问该记录:
mov cl, blBit
这将把 4 移入 CL,因为 blBit 被定义为记录中的第 4 位。hueNib 取位 0、1、2 和 3,blBit 在此之后。
您不能使用记录直接访问位。
mov [rax].color.blBit, 1 ; Won't change the 4th bit from RAX to 1
记录只是指令的一种形式;它定义了一组用于按位运算的常数。常数是位索引。您可以使用唱片进行旋转。
mov cl, blBit ; Move the index of the record's blBit into cl
rol rax, cl ; Use this to rotate the bits in RAX
您可以在数据段中定义记录,并初始化位字段的值,就像使用结构一样。这是唯一一次不使用位操作就可以设置记录的每个元素。
.data
color RECORD qlBit:3, blBit:1, hueNib:4 ;
Defines a record
; Following defines a new byte with the bits set as specified
; by the record declaration:
; qlBit gets 0, blBit gets 1 and the hueNib gets 2
; So MyColor will actually be a byte set to 00010010b
MyColor color { 0, 1, 2 } ; Declare a
color record with initializers
.code _text
Function1 proc
mov cl, MyColor ; Moves 000:1:0010b, or 18 in
decimal
ret
Function1 endp
| | 注:前一条记录中的 qlBit、blBit 和 hueBit 成为其位索引的常数:hueBit = 0,blBit = 4,qlBit = 5。 |
通过使用 MASM 的WIDTH
指令,可以得到记录中字段的宽度(以位为单位)。
mov ax, WIDTH color.hueNib
您可以使用 MASM 的MASK
指令获得记录字段的位掩码。
and al, mask myCol.blBit; AND contents of AL with bit mask
of defined color
; record
您可以在MASK
指令之前指定NOT
来翻转位掩码。
and al, NOT MASK myCol.blBit
您可以使用=
符号定义数值常数,也可以使用equ
指令定义数值和文本常数。这是“等同于”的缩写。
Somevar = 25 ; Somevar becomes a constant
immediate value 25
name equ 237 ; "name" is the symbol for
the constant
mov eax, name ; Translates to “mov eax, 237”
moc ecx, SomeVar ; Sets ECX to 25
您也可以使用EQU
指令通过用三角形大括号包围该值来定义文本常量。
quickMove equ <mov eax, 23>
quickMove ; Translates to “mov eax, 23”
通过在值中使用db
(定义字节),可以使用 equality 指令来定义机器代码。
NoOperation equ <db 90h> ; 90h is machine code
for the NOP instruction
NoOperation ; Translates to NOP or 90h
在代码段中使用db
没有问题,因为db
只是将您指定的精确字节值放在文件中的位置。在代码段中使用db
有效地使我们能够用纯机器代码编程。
; This procedure returns 1 if ECX is odd
; otherwise it returns 0, it is programmed
; in pure machine code using db.
IsOdd proc
db 83h, 0E1h, 01h, ; and ecx, 1
8Bh, 0C1h, ; mov eax, ecx
0C3h ; ret
IsOdd endp
使用纯机器码的意义在于,有时汇编程序可能无法理解一些 CPU 能够理解的指令。较老的汇编程序可能不理解 SSE 指令。通过以前面描述的方式使用EQU
和db
,程序员可以定义他或她自己指定 SSE 指令的方式,不管汇编程序是否自然理解它们。
您可以使用宏指令定义宏函数。
[name] MACRO [p1], [p2]...
; Macro body
ENDM
其中[name]
是与宏相关联的符号,MACRO
和ENDM
是关键字,[p1]
、[p2]
和任何其他符号是参数,因为它们在宏的主体中被引用。
Halve macro dest, input ; dest and input are the
parameters
mov dest, input ;; Refer to parameters in body
shr dest, 1
endm ; endm with no macro name
preceding
; And later in your code:
Halve ecx, 50 ; Moves 25 into ecx
Halve eax, ecx ; Moves 12 into eax
Halve ecx, ecx ; Moves 12 into ecx
Halve 25, ecx ; Error, ecx/2 cannot be stored in 25!
每当 MASM 在汇编时找到宏名时,符号名就被替换为相应的代码。这意味着,如果宏代码中有标签(如果代码跳转到其代码中的其他点),MASM 将一次又一次地写标签。每次使用宏时,标签都会出现。由于 MASM 不允许重复标签,并且仍然知道跳转到哪里,因此您可以在宏定义中将标签定义为本地标签。定义为本地的标签实际上将被自动生成的唯一标签替换。
SomeMacro macro dest, input
local label1, label2
test dest, 1
jnz label1
jz label2
label1: ;; Automatically renamed ??0000
mov eax, 3
label2: ;; Automatically renamed ??0001
mov ecx, 12 ;; Each label each time
SomeMacro is
;; called will increment the
counter,
;; next will be ??0002 the
??0003 etc.
Endm
| | 注意:你可能已经注意到了”;;"宏正文中的注释;这些是宏观评论。它们在生成列表文件时很有用,因为这些注释不会每次在代码中引用宏函数时都出现,只会在宏的定义中出现一次。如果使用单个“;”注释相同的注释将在整个生成的列表文件中反复出现。 |
在您的宏定义中,您可以为任何参数指定默认值,允许您在不指定每个参数的情况下调用宏(将:=
和默认值放在参数名称后,somevariable:=<eax>
)。您也可以指出需要特定的参数(在冒号后面加上req, somevariable:req
)。
| | 注意:当指定默认值时,语法类似于“equ”指令;您必须使用“”来代替“eax”。 |
SomeMacro macro p1:=<eax>, p2:req, p3:=<49>
;; Macro body
Endm
示例代码中的宏定义允许我们省略第一个和第三个参数的值。只需要第二个,其他的可以默认。
; Specify all parameters:
SomeMacro ecx, 389, 12 ; p1 = ecx
; p2 = 389
; p3 = 12
; Just specify parameter 2:
SomeMacro , ebx, ; p1 = eax from default
; p2 = ebx
; p3 = 49 from default