# 第2章：值和绑定

## 值只在编译时存在

XIRASM 的值都是编译时值。它们用来构建输出，但绑定本身不会写字节，也不会预留内存。

```asm
// 这里只创建编译期值，不会向输出写入任何字节。
const magic: u16 = 0x5a4d
```

这行把值赋给名字 `magic`，但不会把 `4d 5a` 写到输出里。要输出这个值，需要调数据接口：

```asm
// 保存 16 位魔数，再通过数据接口按对应宽度写出。
const magic: u16 = 0x5a4d
dw(magic);
```

同样的规则适用于可变值、字符串、字节序列、集合以及函数返回值。只有指令、数据调用、布局操作或格式接口使用了这些值，它们才会成为输出的一部分。

## 常量

常量把一个名字绑定到一个不可能重新赋值的值：

```asm
// 页面大小和文件头大小在整个汇编过程中保持不变。
const page_size: u64 = 4096
const header_size = 64
// 布尔常量可直接用于后续的编译期条件。
const enabled: bool = true
```

一般形式是：

```text
const name = expression
const name: type = expression
```

如果 XIRASM 能从初始值推断出类型，类型注解可以省略。当宽度或值类别属于二进制约定的一部分时，应该写类型。

常量声明末尾不加分号。给常量重新赋值会报错：

```text
const value = 1
value = 2
```

除非源文件在汇编时确实需要更新绑定，否则优先用 `const`。

## 可变绑定

编译时计算需要状态时用 `let`：

```asm
// 逐步增加偏移量，最后写出计算结果。
let offset: u32 = 0
offset = offset + 16
offset = offset + 4

dd(offset);
```

输出：

```text
14 00 00 00
```

声明和赋值都在汇编时发生，不会在最终程序中创建名为 `offset` 的运行时变量。

形式是：

```text
let name = expression
let name: type = expression
name = expression
```

和常量一样，赋值末尾不加分号。作为语句的调用仍然需要分号：

```asm
// 更新编译期绑定，然后把最终值写成一个字节。
let value = 1
value = value + 1
db(value);
```

## 类型注解和类型推断

绑定可以显式声明类型：

```asm
// 明确的类型让字段宽度和文本类别直接体现在源代码中。
const signature: u16 = 0x5a4d
const title: string = "XIRASM"
const marker: bytes = b"OK"
const enabled: bool = true
```

也可以靠推断：

```asm
// 这些值的类型都可以由字面量直接确定。
const count = 4
const name = "payload"
const raw = b"DATA"
```

常用的值类型：

| 类型 | 用途 | 示例 |
| --- | --- | --- |
| `integer` | 通用编译时整数 | `42` |
| `u8` | 8 位无符号值 | `0xff` |
| `u16` | 16 位无符号值 | `0x5a4d` |
| `u32` | 32 位无符号值 | `0x401000` |
| `u64` | 64 位无符号值 | `0x140000000` |
| `i8` | 8 位有符号值 | `-1` |
| `i16` | 16 位有符号值 | `-200` |
| `i32` | 32 位有符号值 | `-4096` |
| `i64` | 64 位有符号值 | `-0x100000000` |
| `f32` | IEEE-754 32 位浮点数 | `f32(1.5)` |
| `f64` | IEEE-754 64 位浮点数 | `1.5` |
| `bool` | 编译时条件 | `true` |
| `string` | 编译时文本 | `"kernel"` |
| `bytes` | 确切的字节序列 | `b"PE"` |

列表、映射、结构体、联合体和类型值在后面的章节介绍。

指定位宽的整数类型适合表示二进制字段和函数接口。如果一个函数只关心值是整数而不在意具体宽度，可以用 `integer` 类型。最终输出时写多少个字节由调用接口决定。

有符号固定位宽整数使用补码表示。创建绑定或结构字段时会检查声明范围；打包有符号字段时，会按字段宽度写出低 8、16、32 或 64 位。

带小数部分或指数的十进制字面量类型为 `f64`。用 `f32(value)` 显式窄化为 `f32`，用 `f64(value)` 把 `f32` 扩展为 `f64`。整数和浮点值之间不会隐式转换。

## 字符串和字节序列

字符串和字节序列是不同类型的值：

```asm
// 节名是文本，而文件签名表示精确的两个字节。
const section_name: string = ".text"
const signature: bytes = b"MZ"
```

文本名称、路径、生成的指令文本以及需要文本的接口，用字符串。需要精确字节序列时用 `bytes`。

有些输出接口两种类型都接受：

```asm
// 字符串和显式字节序列会按参数顺序连续写出。
db("AB", b"CD");
```

输出四个字节：

```text
41 42 43 44
```

其他接口只接受其中一种。在绑定中区分清楚，能让这些约定在源码里更明显。

## 块作用域

绑定属于它声明时所在的作用域。块会创建一个嵌套作用域：

```asm
// 外层常量在内部代码块结束后仍然可见。
const value = 1

{
    // 内层绑定只在这个代码块中遮蔽外层同名常量。
    let value = 2
    // 离开代码块后，再次使用外层的 value。
    db(value);
}

db(value);
```

输出：

```text
02 01
```

内部的 `value` 只在块内遮盖外部的常量。块结束后，外部的 `value` 重新可见。

函数参数和局部绑定也属于函数作用域。嵌套块可以引入临时名字，不影响外部绑定。

当嵌套操作确实需要一个自然的局部名字时，遮盖是有用的。但如果两个含义放在源码里很难区分，就不要用相同的名字。

## 怎么选 const 和 let

选最精确的绑定来表达计算：

| 情况 | 优先选 |
| --- | --- |
| 固定选项、大小、名称或计算结果 | `const` |
| 语言自身管理的循环计数器 | 循环绑定 |
| 累计的偏移量、校验和或累加器 | `let` |
| 只在小区块里变化的值 | 块内 `let` |
| 宽度属于文件格式约定的值 | 显式类型注解 |
| 类型显而易见的临时值 | 类型推断 |

格式描述和生成汇编中的大多数绑定应该是常量。可变状态在局部、短生命周期、只跟一个计算相关时最清晰。

下一章讲产生这些值的表达式：算术、比较、布尔逻辑、位运算、字段访问和函数调用。

[返回语言指南](../language.md)
