---
title: "Yul 中的内存是如何工作的？"
description: "了解 Yul 智能合约编程语言中内存的工作原理"
---

# Yul 中的内存是如何工作的？

[Yul](http://www.alchemy.com/overviews/what-is-yul) 是一种中间编程语言，可用于在 [智能合约](https://www.alchemy.com/overviews/solidity-smart-contract) 内编写一种形式的汇编语言。在了解了 Yul storage 以及如何读写打包的 storage 变量之后，接下来需要学习 Yul 智能合约中 memory 的工作方式。

## **Yul 中 memory 是如何工作的？**

memory 的行为与 [storage](http://www.alchemy.com/overviews/yul-storage) 不同。memory 不是持久化的，这意味着函数执行结束后，所有变量都会被清除。

memory 可以类比为其他语言中的 heap，但它没有垃圾回收机制。

memory 比 storage 便宜得多。前 22 个字（word）的 memory 成本是线性计算的，但要注意，超过这个范围后 memory 成本会变为二次方增长。

memory 以 32 字节为一段进行布局。稍后我们会更详细地了解这一点，但现在只需知道 0x00 - 0x20 是一个段（可以把它想象成一个 slot，如果这样理解更方便的话，但它们其实是不同的东西）。

[Solidity](https://www.alchemy.com/overviews/solidity) 将 0x00 - 0x40 分配为暂存空间（scratch space）。这部分 memory 不保证为空，会被用于某些操作。

0x40 - 0x60 存储的是所谓的空闲 memory 指针（free memory pointer）的位置，用于向 memory 写入新内容。

0x60 - 0x80 留空，作为间隔。

0x80 是我们开始操作的位置。

memory 不会对值进行打包。从 storage 中取出的值会各自存储在独立的 32 字节段中（例如 0x80-0xa0）。

### **Yul 中哪些操作会用到 memory？**

以下操作会用到 memory：

- 外部调用的返回值
- 为外部调用设置函数值
- 从外部调用获取值
- 使用错误字符串 revert
- 记录日志消息
- 使用 keccak256\(\) 进行哈希运算
- 创建其他智能合约

**下面是一些常用的 Yul memory 指令：**

<EmbeddedTable
  table={{
    columns: [
      { key: "1", width: 200, title: "Instruction", dataType: "object" },
      { key: "2", width: 200, title: "Explanation", dataType: "object" },
    ],
    data: [
      {
        "1": { title: "<p>mload(p)</p>", tooltip: "", icon: "" },
        "2": { title: "<p>与 sload() 类似，但这里是加载 p 之后的接下来 32 字节</p>", tooltip: "", icon: "" },
        id: 0,
      },
      {
        "1": { title: "<p>mstore(p, v)</p>", tooltip: "", icon: "" },
        "2": { title: "<p>与 sstore() 类似，但这里是将值 v 存储在 p 及其后 32 字节的位置</p>", tooltip: "", icon: "" },
        id: 1,
      },
      {
        "1": { title: "<p>mstore8(p, v)</p>", tooltip: "", icon: "" },
        "2": { title: "<p>与 mstore() 类似，但只针对单个字节</p>", tooltip: "", icon: "" },
        id: 2,
      },
      {
        "1": { title: "<p>msize()</p>", tooltip: "", icon: "" },
        "2": { title: "<p>返回访问过的最大 memory 索引</p>", tooltip: "", icon: "" },
        id: 3,
      },
      {
        "1": { title: "<p>pop(x)</p>", tooltip: "", icon: "" },
        "2": { title: "<p>丢弃值 x</p>", tooltip: "", icon: "" },
        id: 4,
      },
      {
        "1": { title: "<p>return(p, s)</p>", tooltip: "", icon: "" },
        "2": { title: "<p>结束执行，并返回 memory 位置 p - v 处的数据</p>", tooltip: "", icon: "" },
        id: 5,
      },
      {
        "1": { title: "<p>revert(p, s)</p>", tooltip: "", icon: "" },
        "2": { title: "<p>结束执行且不保存状态变更，并返回 memory 位置 p - v 处的数据</p>", tooltip: "", icon: "" },
        id: 6,
      },
    ],
  }}
/>

接下来我们再看几种数据结构！

### **如何在 Yul 中使用 struct 和 memory**

struct 和固定长度数组的行为其实是一样的，但由于我们在 storage 部分已经看过固定长度数组了，这里我们来看 struct。看看下面这个 struct。

没什么特别的，就是一个简单的 struct。

现在来看一些代码！

这里我们将 s.subVar1 设置到 memory 位置 0x80 - 0xa0，将 s.subVar2 设置到 memory 位置 0xa0 - 0xc0。这就是为什么我们返回的是 0x80 - 0xc0。下面是交易结束前 memory 布局的一张表。

<EmbeddedTable
  table={{
    columns: [
      { key: "1", width: 200, title: "Memory Location", dataType: "object" },
      { key: "2", width: 200, title: "Value Stored", dataType: "object" },
    ],
    data: [
      {
        "1": { title: "<p>0x00</p>", tooltip: "", icon: "" },
        "2": { title: "<p>暂存空间（空）</p>", tooltip: "", icon: "" },
        id: 0,
      },
      {
        "1": { title: "<p>0x20</p>", tooltip: "", icon: "" },
        "2": { title: "<p>暂存空间（空）</p>", tooltip: "", icon: "" },
        id: 1,
      },
      {
        "1": { title: "<p>0x40</p>", tooltip: "", icon: "" },
        "2": { title: "<p>0xc0（空闲 memory 指针）</p>", tooltip: "", icon: "" },
        id: 2,
      },
      {
        "1": { title: "<p>0x60</p>", tooltip: "", icon: "" },
        "2": { title: "<p>空</p>", tooltip: "", icon: "" },
        id: 3,
      },
      {
        "1": { title: "<p>0x80</p>", tooltip: "", icon: "" },
        "2": { title: "<p>s.subVar1: 0x20（十进制为 32）</p>", tooltip: "", icon: "" },
        id: 4,
      },
      {
        "1": { title: "<p>0xa0</p>", tooltip: "", icon: "" },
        "2": { title: "<p>s.subVar1: 0x40（十进制为 64）</p>", tooltip: "", icon: "" },
        id: 5,
      },
      {
        "1": { title: "<p>0xc0</p>", tooltip: "", icon: "" },
        "2": { title: "<p>新的空闲 memory 指针。这就是 msize() 会返回的值。（空）</p>", tooltip: "", icon: "" },
        id: 6,
      },
    ],
  }}
/>

**从中可以总结出以下几点：**

- 0x00 - 0x40 作为暂存空间留空
- 0x40 处存放的是空闲 memory 指针
- Solidity 会在 0x60 处留出一段间隔
- 0x80 和 0xa0 用于存储 struct 的值
- 0xc0 是新的空闲 memory 指针

在 memory 部分的最后，我想给大家展示动态数组在 memory 中的工作方式。在这个例子中，我们会传入 \[0, 1, 2, 3\] 作为参数 arr。作为附加内容，我们还会给数组追加一个新元素。在生产环境中这样做要小心，因为可能会覆盖其他 memory 变量。

**代码如下：**

**下面是执行过程：**

- 获取数组在 memory 中的存储位置
- 获取数组的长度，它存储在数组的第一个 memory 位置
- 在该位置基础上加 32 字节（跳过数组长度部分），得到下一个可用位置
- 将数组长度乘以 32 字节，得到下一个 memory 位置
- 存储我们的新值（4）
- 将数组长度更新加一
- 更新空闲 memory 指针
- 返回该数组

我们再来看一次 memory 布局。

<EmbeddedTable
  table={{
    columns: [
      { key: "1", width: 200, title: "Memory Location", dataType: "object" },
      { key: "2", width: 200, title: "Value Stored", dataType: "object" },
    ],
    data: [
      {
        "1": { title: "<p>0x00</p>", tooltip: "", icon: "" },
        "2": { title: "<p>暂存空间（空）</p>", tooltip: "", icon: "" },
        id: 0,
      },
      {
        "1": { title: "<p>0x20</p>", tooltip: "", icon: "" },
        "2": { title: "<p>暂存空间（空）</p>", tooltip: "", icon: "" },
        id: 1,
      },
      {
        "1": { title: "<p>0x40</p>", tooltip: "", icon: "" },
        "2": { title: "<p>0x140（空闲 memory 指针）</p>", tooltip: "", icon: "" },
        id: 2,
      },
      {
        "1": { title: "<p>0x60</p>", tooltip: "", icon: "" },
        "2": { title: "<p>空</p>", tooltip: "", icon: "" },
        id: 3,
      },
      {
        "1": { title: "<p>0x80</p>", tooltip: "", icon: "" },
        "2": { title: "<p>数组的新长度（6）</p>", tooltip: "", icon: "" },
        id: 4,
      },
      {
        "1": { title: "<p>0xa0</p>", tooltip: "", icon: "" },
        "2": { title: "<p>arr[0]（0）</p>", tooltip: "", icon: "" },
        id: 5,
      },
      {
        "1": { title: "<p>0xc0</p>", tooltip: "", icon: "" },
        "2": { title: "<p>arr[1]（1）</p>", tooltip: "", icon: "" },
        id: 6,
      },
      {
        "1": { title: "<p>0xe0</p>", tooltip: "", icon: "" },
        "2": { title: "<p>arr[2]（2）</p>", tooltip: "", icon: "" },
        id: 7,
      },
      {
        "1": { title: "<p>0x100</p>", tooltip: "", icon: "" },
        "2": { title: "<p>arr[3]（3）</p>", tooltip: "", icon: "" },
        id: 8,
      },
      {
        "1": { title: "<p>0x120</p>", tooltip: "", icon: "" },
        "2": { title: "<p>arr[4]（4）</p>", tooltip: "", icon: "" },
        id: 9,
      },
      {
        "1": { title: "<p>0x140</p>", tooltip: "", icon: "" },
        "2": { title: "<p>空闲 memory 指针（空）</p>", tooltip: "", icon: "" },
        id: 10,
      },
    ],
  }}
/>

memory 部分到此结束！

接下来，学习 [如何使用 Yul 调用智能合约](http://www.alchemy.com/overviews/yul-contract-calls)，或者返回学习 [如何读写打包的 storage 变量](http://www.alchemy.com/overviews/yul-packed-storage-variables)！
