本文深入探讨了以太坊虚拟机(EVM)中内存的工作原理,包括其定义、Solidity 中的保留空间、内存布局(如值类型和引用类型的存储方式)以及 gas 成本。此外,文章还提供了关于如何在 Solidity 中有效管理内存的最佳实践,例如避免不必要的内存使用和尊重 Solidity 的内存管理方式。
内存是solidity智能合约拥有的四个数据位置之一(其他是:storage、calldata和stack)。 简单来说,它是智能合约运行时与之关联的“堆”。 值不会持久化,一旦交易完成,内存内容就会被删除。
当调用智能合约时,EVM会为其“实例化”一个内存空间。 该内存是字节寻址的(与字寻址的存储相反),长度为2**256字节,换句话说,内存是一个超长的字节数组。
Solidity 使用内存做很多事情,例如:
Solidity 以非常特定的方式使用前 4 个内存字(每个字 32 字节 -> 总共前 128 字节):
重要的是要注意,如果开发人员将 YUL(例如以_汇编块_的形式)添加到其智能合约函数中,他/她可以以他/她喜欢的任何方式使用内存数组,这意味着如果合约还运行一些 solidity 生成的代码,则可能存在对内存内容产生混淆/误解的风险……
变量可以以不同的方式存储在内存中,具体取决于其数据类型。
值类型存储在内存中,就像在存储中一样,唯一的区别是变量不能被打包(不与其他变量合并在一起),每个变量占用 32 字节。
结构体(Structures) 的管理方式与值类型相同,结构体中的每个变量占用 32 个字节,不执行打包。
固定大小数组的工作方式非常相似,只是第一个字(32 字节)指示数组长度,这样编译器就可以知道数组使用了多少个插槽。
目前,映射(mappings) 和 动态大小数组 无法存储在内存中。
另一方面,bytes 和 strings 可以存储在内存中,我们可以将它们也视为引用类型(因为它们基本上是字节的动态大小数组)。
Solidity 处理内存中的 bytes/string 的方式就像它处理固定大小的数组一样,但是当 bytes/string 被“调整大小”(添加/删除新的字节/字符)时,Solidity 会在内存中完全重新创建它(而不删除它的前一个副本,记住,内存中没有任何东西会被清除)。
内存 gas 费用取决于已用内存量,换句话说,取决于上次访问的内存位置(访问以写入或读取值并不重要),这是一个递增的数字,因为内存永远不会被清除……
访问内存位置的操作码有:RETURN、REVERT、MLOAD、MSTORE、MSTORE8。 这些操作码中的每一个都具有固定 gas 费用(MLOAD 为 3 gas,...)加上一个可变的 gas 费用,如果它们访问以前未访问过的内存位置,则可以添加该费用。
这里需要注意的重要一点是,对于使用的前 724 字节内存,这个“可变 gas 费用”呈线性增长,之后,增长变为指数级,这意味着即使内存应该有 2**256 字节长,有些内存位置也无法到达,仅仅是因为交易会耗尽 gas……
- 原文链接: medium.com/coinmonks/sol...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!