# 五、数据段

数据段是程序在内存中存储其全局和静态数据的地方。该数据在编译时定义。数据段不保存运行时分配的变量(堆用于此目的)或子进程本地的变量(栈用于保存这些变量)。这里提供的大部分信息将用于任何细分市场。例如，变量可以在未初始化的数据段()中声明。数据)或常量数据段(。常数)。

| ![](img/note.png) | 注意:在数据段中声明的所有变量在实际中都会变成字节。exe 文件。它们不是在程序运行时生成的；它们是从。exe 文件。创建一个包含 150 兆字节变量的数据段将生成一个 150 兆字节的。并且需要很长时间来编译。 |

## 标量数据

数据段中定义的标量数据被赋予名称、大小和可选的初始值。在数据段中声明一个变量就是命名一个从数据段开始的偏移量。所有的变量名实际上都是指针；它们是一个数字，指的是内存中数据段的偏移量，因此程序员不必将偏移量记为数字。

| ![](img/note.png) | 注意:汇编中的变量有时被称为标签，但是为了避免与代码段中的标签混淆，我将数据段标签称为变量。 |

要在数据段中定义变量，一般布局如下:

`[VarName] [Type] [Initial Value]`

其中`[VarName]`是任何合法的变量名，并且将是您希望用作引用的数据点的名称。

| ![](img/note.png) | 注意:变量名的规则与 C++ 相同。它们不能以数字开头，可以包含字母、数字和下划线。还可以使用一些 C++ 中不合法的附加符号，比如@和？。 |

`[Type]`是数据类型，可以是 [*基础数据类型*](2.html#_Data_Types_Table) 表的 ASM 列中的任何一种数据类型或短版本。

初始值可以是文字值，也可以是“？”。问号表示数据没有初始值。实际上，即使数据未初始化，也会给它一个值。“？”的意义声明程序员不关心数据最初被设置为什么值，并且推测程序将在使用数据之前设置一些其他的初始值。

以下是在数据段中定义简单标量数据类型的一些示例。

```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

```

| ![](img/note.png) | 注意:第一个变量放在 DS:0 处(它放在数据段的第一个字节处)，第二个放在其后(变量之间没有填充间距)。如果第一个变量是 1 字节，那么第二个变量将是 DS:1。如果第一个变量是一个单词，那么第二个变量就是 DS:2。连续变量存储在内存中的方式称为对齐，这对于性能很重要，因为一些最快的数据处理指令要求数据对齐到 16 个字节。 |

## 阵列

在标量数据类型之后，下一个最基本的数据类型可能是数组。数组是连续内存中相同数据类型的元素列表。在汇编中，数组只是一个内存块，数组的第一个元素被赋予一个名称。

### 用逗号声明的数组

您可以声明由逗号分隔的数组元素。

```asm
    MyWord dw 1, 2, 3, 4 ; Makes a 4 word array with 1, 2, 3, and 4 as elements

```

如果需要使用多行，请用逗号结束一行，然后继续下一行。

```asm
    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]`是 [*基础数据类型*](2.html#_Data_Types_Table) 表中的数据类型之一，`[n]`是数组中的项数。`DUP`是重复的缩写，它复制的数据在括号中。要在名为 **MyArray** 的数组中将 50 个单词全部设置为 25，使用重复语法的数组声明如下:

```asm
    MyArray word 50 dup (25)

```

您可以将简单的逗号分隔数组定义语法与重复语法结合起来，生成重复模式的数组。

```asm
    MyArray byte 50 dup (1, 6, 8)

```

这将定义一个 150 字节长(50×3)的数组，其重复模式为 1、6、8、1、6、8、1、6、8....

您可以嵌套重复的指令来创建多维数组。例如，要创建一个 10×25 维字节数组并将所有元素设置为 A，可以使用以下方法:

```asm
    MyMArray byte 10 dup (25 dup ('A'))

```

| ![](img/note.png) | 注意:内存是线性的。样本代码实际定义的是 10×25 还是 25×10 数组，必须由程序员决定。对 CPU 来说，它只是一块线性 RAM，并不存在多维数组这种东西。 |

对于三维数组，您可以使用类似这样的东西:

```asm
    My3dArray byte 10 dup (25 dup (100 dup (0)))

```

这将创建一个 10×25×100 的三维字节数组，所有字节都设置为 0。从中央处理器的角度来看，这个三维阵列与以下完全相同:

```asm
    My3dArray byte 25000 dup (0)

```

### 获取关于数组的信息

定义后，MASM 有一些指令来检索关于阵列的信息:

`lengthof`:以元素为单位返回数组的长度。

`sizeof`:以字节为单位返回数组的长度。

`type`:以字节为单位返回数组的元素大小。

例如，如果您有一个名为 **myArray** 的数组，并且想要将关于它的信息移动到 AX 中，您可以执行以下操作:

```asm
    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

```

| ![](img/note.png) | 注意:在汇编文件之前，这些指令被转换为即时值；因此，“mov lengthof myArray，200”实际上被翻译为“mov 16，200”。将值移动到文字常量中没有任何意义(即使在汇编中，我们也不能更改 16 的含义)，因此该行是非法的。 |

### 定义字符串

在 MASM，单引号和双引号完全相同。它们用于字符串和单个字符。

| ![](img/note.png) | 注意:C 和 C++ 中的字符串是一个字节数组，通常在末尾有一个空值 0。这些类型的字符串称为零分隔字符串。许多 C++ 函数被设计为使用零分隔字符串。 |

要定义文本字符串，可以使用以下字符串语法:

```asm
    errMess db 'You do not have permission to do this thing, lol', 0

```

这相当于定义一个字节数组，其值设置为字符串中字符的 ASCII 数字。开始处的 Y 将是数组的第一个(最低有效)字节。

| ![](img/note.png) | 注意:末尾的逗号和零是最后的空值。这使得该字符串成为 cout 和其他 C++ 函数所理解的空终止字符串。当字符串到达 0 字符时，Cout 停止写入。在 C++ 中，当我们使用双引号时，会自动添加 0；在程序集中，它必须是显式的。 |

要定义不适合单行的字符串，可以使用以下方法:

```asm
    myLongString db "This is a ",
    "string that does not ",
    "fit on a single lion!", 0

```

在前面的每个示例中，单引号也可以用于相同的效果:

```asm
    myLongString db 'This is a ',
    'string that does not',
    'fit on a single lion!', 0

```

如果需要在单引号数组中使用单引号或在双引号数组中使用双引号，请将其中两个放在一起:

```asm
    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 指令。

| ![](img/note.png) | 注意:可以使用 typedef 为用户定义的类型、基本类型、结构、联合和记录定义新名称。 |

## 结构和联盟

要定义一个结构(类似于 C++ 结构)，可以使用 struc(或 struct)指令。

```asm
    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。

前面的例子是原型。要创建先前结构原型的实例，可以使用以下方法:

```asm
    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)。您可以初始化的值少于结构和其他部分自动给出的值。根据结构的原型，这些是它们的默认值。

```asm
    person2 ExampleStructure { 10 } ; Declares person2 with 10 for x
    ; but the rest are as per the
    ; structure's prototype

```

通过原型声明，您可以通过不包含任何值来创建该结构的实例，其中一些值已初始化，而另一些值具有默认值。只需在空格处加一个逗号来表示值的位置。

```asm
    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++ 中访问结构元素的方式。

```asm
    mov person1.X, 25 ; Moves 25 into person1's X
    mov person2.ID, 90 ; Moves 90 into person2's ID

```

| ![](img/note.png) | 注意:当结构从 C++ 传递给函数时，它们不是通过引用传递的。根据结构的大小，它们被复制到寄存器和栈中。如果一个结构有两个整数，那么这个结构的整个实例将被复制到 RCX(因为两个 32 位的 dwords 适合 64 位的 RCX)。这很尴尬，因为当结构的各个元素在寄存器中时，不能引用它们。例如，没有办法参考 RCX 的顶级 dword。因此，将 C++ 中的结构作为指针传递可能更容易。 |

您可以使用 LEA 指令(加载有效地址)加载先前实例化结构的有效地址。要使用寄存器(本例中为 RCX)作为指向结构实例的指针，您必须告诉 MASM 地址、指向的结构类型和字段。

```asm
    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++ 的方式将结构的数据复制到寄存器和栈要快得多。

```asm
    ; 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

```

### 结构的结构

若要定义具有较小子结构作为其成员变量的结构，请先声明较小的子结构。然后将子结构的实例放在更大结构的声明中。

```asm
    ; 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

```

若要声明包含数据段中子结构的结构实例，可以使用嵌套的大括号。

```asm
    MySquare Square { { 9, 8 }, { ?, 7 } }

```

| ![](img/note.png) | 注意:如果不想设置结构的任何值，可以使用{}来表示所有值的默认值，即使结构中有子结构。 |

若要设置结构子结构的值，请附加一个句点来指定要更改的变量。

```asm
    mov MySquare.cnr1.Y, 5

```

您可以使用寄存器作为指针，并引用嵌套结构的元素，如下所示:

```asm
    mov word ptr [rcx].Square.cnr1.X, 10

```

### 工会

联合类似于结构，只是联合中每个元素使用的实际内存物理上位于内存中的相同位置。联合是一种在内存中引用同一地址作为多种数据类型的方式。

```asm
    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]`是字段的大小(以位为单位)。

```asm
    color RECORD blBit:1, hueNib:4

```

数据段中的样本代码是名为**颜色**的记录的原型。然后可以通过以下方式访问该记录:

```asm
    mov cl, blBit

```

这将把 4 移入 CL，因为 blBit 被定义为记录中的第 4 位。hueNib 取位 0、1、2 和 3，blBit 在此之后。

您不能使用记录直接访问位。

```asm
    mov [rax].color.blBit, 1 ; Won't change the 4th bit from RAX to 1

```

记录只是指令的一种形式；它定义了一组用于按位运算的常数。常数是位索引。您可以使用唱片进行旋转。

```asm
    mov cl, blBit ; Move the index of the record's blBit into cl
    rol rax, cl ; Use this to rotate the bits in RAX

```

您可以在数据段中定义记录，并初始化位字段的值，就像使用结构一样。这是唯一一次不使用位操作就可以设置记录的每个元素。

```asm
  .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

```

| ![](img/note.png) | 注:前一条记录中的 qlBit、blBit 和 hueBit 成为其位索引的常数:hueBit = 0，blBit = 4，qlBit = 5。 |

通过使用 MASM 的`WIDTH`指令，可以得到记录中字段的宽度(以位为单位)。

```asm
  mov ax, WIDTH color.hueNib

```

您可以使用 MASM 的`MASK`指令获得记录字段的位掩码。

```asm
  and al, mask myCol.blBit; AND contents of AL with bit mask
  of defined color 
                          ; record

```

您可以在`MASK`指令之前指定`NOT`来翻转位掩码。

```asm
  and al, NOT MASK myCol.blBit

```

## 使用等于的常数

您可以使用`=`符号定义数值常数，也可以使用`equ`指令定义数值和文本常数。这是“等同于”的缩写。

```asm
  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`指令通过用三角形大括号包围该值来定义文本常量。

```asm
  quickMove equ <mov eax, 23>
  quickMove         ; Translates to “mov eax, 23”

```

通过在值中使用`db`(定义字节)，可以使用 equality 指令来定义机器代码。

```asm
  NoOperation equ <db 90h>     ; 90h is machine code
  for the NOP instruction
  NoOperation                    ; Translates to NOP or 90h

```

在代码段中使用`db`没有问题，因为`db`只是将您指定的精确字节值放在文件中的位置。在代码段中使用`db`有效地使我们能够用纯机器代码编程。

```asm
  ; 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]`和任何其他符号是参数，因为它们在宏的主体中被引用。

```asm

  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 不允许重复标签，并且仍然知道跳转到哪里，因此您可以在宏定义中将标签定义为本地标签。定义为本地的标签实际上将被自动生成的唯一标签替换。

```asm
  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

```

| ![](img/note.png) | 注意:你可能已经注意到了”；;"宏正文中的注释；这些是宏观评论。它们在生成列表文件时很有用，因为这些注释不会每次在代码中引用宏函数时都出现，只会在宏的定义中出现一次。如果使用单个“；”注释相同的注释将在整个生成的列表文件中反复出现。 |

在您的宏定义中，您可以为任何参数指定默认值，允许您在不指定每个参数的情况下调用宏(将`:=`和默认值放在参数名称后，`somevariable:=<eax>`)。您也可以指出需要特定的参数(在冒号后面加上`req, somevariable:req`)。

| ![](img/note.png) | 注意:当指定默认值时，语法类似于“equ”指令；您必须使用“<eax>”来代替“eax”。</eax> |

```asm
  SomeMacro macro p1:=<eax>, p2:req, p3:=<49>
         ;; Macro body
  Endm

```

示例代码中的宏定义允许我们省略第一个和第三个参数的值。只需要第二个，其他的可以默认。

```asm
  ; 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

```