跳至内容
0%

Yul 中的内存是如何工作的?

发布于 2023年7月28日4 分钟阅读

Yul 是一种中间编程语言,可用于在 智能合约 内编写一种形式的汇编语言。在了解了 Yul storage 以及如何读写打包的 storage 变量之后,接下来需要学习 Yul 智能合约中 memory 的工作方式。

Yul 中 memory 是如何工作的?

memory 的行为与 storage 不同。memory 不是持久化的,这意味着函数执行结束后,所有变量都会被清除。

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

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

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

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 指令:

Instruction
Explanation

mload(p)

与 sload() 类似,但这里是加载 p 之后的接下来 32 字节

mstore(p, v)

与 sstore() 类似,但这里是将值 v 存储在 p 及其后 32 字节的位置

mstore8(p, v)

与 mstore() 类似,但只针对单个字节

msize()

返回访问过的最大 memory 索引

pop(x)

丢弃值 x

return(p, s)

结束执行,并返回 memory 位置 p - v 处的数据

revert(p, s)

结束执行且不保存状态变更,并返回 memory 位置 p - v 处的数据

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

如何在 Yul 中使用 struct 和 memory

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

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

现在来看一些代码!

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

Memory Location
Value Stored

0x00

暂存空间(空)

0x20

暂存空间(空)

0x40

0xc0(空闲 memory 指针)

0x60

0x80

s.subVar1: 0x20(十进制为 32)

0xa0

s.subVar1: 0x40(十进制为 64)

0xc0

新的空闲 memory 指针。这就是 msize() 会返回的值。(空)

从中可以总结出以下几点:

  • 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 布局。

Memory Location
Value Stored

0x00

暂存空间(空)

0x20

暂存空间(空)

0x40

0x140(空闲 memory 指针)

0x60

0x80

数组的新长度(6)

0xa0

arr[0](0)

0xc0

arr[1](1)

0xe0

arr[2](2)

0x100

arr[3](3)

0x120

arr[4](4)

0x140

空闲 memory 指针(空)

memory 部分到此结束!

接下来,学习 如何使用 Yul 调用智能合约,或者返回学习 如何读写打包的 storage 变量

Background gradient

构建区块链应用

Alchemy 将最强大的 Web3 开发者产品和工具与资源、社区及专业支持结合在一起。