# 第 7 章：数据写出、预留空间与对齐

## 语法摘要

| 形式 | 语法 | 结果 |
| --- | --- | --- |
| 固定宽度整数 | `emit.u8(value)` 至 `emit.u64(value)` | 按小端顺序写出一个整数。 |
| 固定宽度浮点 | `emit.f32(value)`、`emit.f64(value)` | 按小端顺序写出一个 IEEE-754 值。 |
| 数据序列 | `db`/`dw`/`dd`/`dp`/`dq`/`dt`/`ddq`/`dqq`/`ddqq` | 写出 1 至 64 字节宽度的整数元素。 |
| 字节序列 | `emit.bytes(value)` | 原样写出字符串或 `bytes` 值。 |
| 文件字节 | `emit.file(path, offset?, count?)` | 写出完整的源文件相对文件或精确范围。 |
| 按字节预留 | `reserve(count)` | 预留 `count` 个逻辑字节。 |
| 按元素预留 | `rb`/`rw`/`rd`/`rp`/`rq`/`rt`/`rdq`/`rqq`/`rdqq` | 预留 1 至 64 字节宽度的元素。 |
| 固定填充 | `pad(count, fill?)` | 恰好写出 `count` 个填充字节。 |
| 绝对位置填充 | `pad_to(position, fill?)` | 持续写出字节，直到活动输出区域到达 `position`。 |
| 对齐 | `align(boundary, fill?)` | 前进到下一个对齐位置。 |

## 固定宽度整数写出

`emit.u*` 过程接受一个无符号整数，并按小端顺序写出：

| 调用 | 接受的数值范围 | 写出字节数 |
| --- | --- | --- |
| `emit.u8(value)` | `0` 至 `0xff` | 1 |
| `emit.u16(value)` | `0` 至 `0xffff` | 2 |
| `emit.u32(value)` | `0` 至 `0xffffffff` | 4 |
| `emit.u64(value)` | `0` 至 `0xffffffffffffffff` | 8 |

```asm
// 依次写出 1、2、4、8 字节宽度的整数，以展示各宽度的小端顺序。
emit.u8(0x11)
emit.u16(0x2233)
emit.u32(0x44556677)
emit.u64(0x8899aabbccddeeff)
```

输出内容的开头为：

```text
11 33 22 77 66 55 44 ff ee dd cc bb aa 99 88
```

超出所选宽度数值范围的值会被拒绝，而不会被截断。

## 数据序列

数据写出简写接受一个或多个实参：

| 调用 | 元素宽度 | 接受的实参 |
| --- | --- | --- |
| `db(values...)` | 1 字节 | 整数、字符串和 `bytes` 值 |
| `dw(values...)` | 2 字节 | 整数 |
| `dd(values...)` | 4 字节 | 整数 |
| `dp(values...)` | 6 字节 | 整数 |
| `dq(values...)` | 8 字节 | 整数 |
| `dt(values...)` | 10 字节 | 从 `u64` 零扩展的整数 |
| `ddq(values...)` | 16 字节 | 从 `u64` 零扩展的整数 |
| `dqq(values...)` | 32 字节 | 从 `u64` 零扩展的整数 |
| `ddqq(values...)` | 64 字节 | 从 `u64` 零扩展的整数 |

```asm
// db 原样组合整数字节、字符串和字节序列；其余调用按固定宽度写出整数。
db(0x10, "AZ", b"BC")
dw(0x1234, 0xabcd)
dd(0x10203040)
dq(0x0102030405060708)
```

传给 `db` 的字符串和字节序列会逐字节复制。宽度更大的简写只接受整数，并按小端顺序编码每个元素。调用数据写出简写时不提供实参是无效的。

小于八字节的宽度会检查范围且绝不截断。`dt` 是 10 字节原始数据，不是 `f80` 或 x87 值。精确的宽位模式应使用 `emit.bytes`。

## 浮点写出

带小数部分或指数的十进制字面量属于 `f64`。`f32(value)` 显式窄化有限的 `f64`；`f64(value)` 扩宽 `f32` 或保留 `f64`。

```asm
const compact: f32 = f32(1.5)
emit.f32(compact)
emit.f64(-0.0)
```

`emit.f32` 和 `emit.f64` 要求实参类型完全匹配，并以小端顺序写出 IEEE-754 位模式。语言不提供隐式整数转换或 `f80` 表面。NaN、Infinity 和溢出会被拒绝；有限下溢与有符号零会被保留。

## 字节序列

`emit.bytes(value)` 接受一个字符串或 `bytes` 值：

```asm
// 保存三个原始字节，再与字符串一起原样写入输出内容。
const signature = b"XIR"
emit.bytes(signature)
emit.bytes("ASM")
```

该调用不会添加终止符、长度字段，也不会执行编码转换。如果一次调用需要组合文本、字节序列和整数形式的字节值，请使用 `db`。

## 预留空间

`reserve(count)` 会让逻辑位置前进 `count` 个字节。以下简写使用固定宽度元素表示数量：

| 调用 | 预留的逻辑字节数 |
| --- | --- |
| `rb(count)` | `count` |
| `rw(count)` | `count * 2` |
| `rd(count)` | `count * 4` |
| `rp(count)` | `count * 6` |
| `rq(count)` | `count * 8` |
| `rt(count)` | `count * 10` |
| `rdq(count)` | `count * 16` |
| `rqq(count)` | `count * 32` |
| `rdqq(count)` | `count * 64` |

```asm
// 在两个已初始化字节之间预留三个逻辑字节。
db(0xaa)
reserve(3)
db(0xbb)

// 使用两个 8 字节元素定义尾部预留空间，并验证其逻辑大小。
tail_start:
rq(2)
tail_end:

assert(tail_end - tail_start == 16)
```

预留空间后继续写出已初始化内容时，中间的间隙会以零填充。位于输出区域末尾的预留空间可以只保留为逻辑空间，而不向文件添加字节。第 8 章会介绍决定这一区别的实际文件游标和潜在文件游标。

预留空间的算术运算会经过检查。如果按元素宽度换算后的字节数无法表示，该数量会被拒绝。

## 填充与对齐

填充始终写出已初始化字节。可选的填充值默认为零，并且必须能放入一个字节。

```asm
// 依次按固定数量、绝对位置和边界执行填充与对齐，再写出末尾数据。
db(0x11, 0x22)
pad(2, 0xaa)
pad_to(8, 0xbb)
align(16, 0xcc)
db(0x33)
```

`pad(count, fill)` 恰好写出 `count` 个字节。

`pad_to(position, fill)` 把 `position` 视为相对于活动输出区域的文件位置。计算填充量时，从下一个需要实际写出的输出位置开始，因此前面的预留空间只会计算一次。目标位置不能位于该起点之前。

`align(boundary, fill)` 会让逻辑位置和文件位置前进到 `boundary` 的下一个整数倍。边界必须是非零的二次幂。如果当前位置已经对齐，则不会写出任何字节。

需要逻辑上的未初始化空间时，应使用预留空间；当填充字节属于文件格式本身时，应使用填充或对齐。
