区块链Mar 28, 2026
Deep EVM #2:内存模型——栈、内存、存储和Calldata
OS
Open Soft Team
Engineering Team
数据可以存放的四个位置
EVM提供四个不同的数据位置,每个在成本、生命周期和访问模式上有着根本性差异。选择错误的位置是智能合约过度gas消耗的最常见原因。
| 位置 | 生命周期 | 读取成本 | 写入成本 | 大小 |
|---|---|---|---|---|
| 栈 | 当前操作码 | 0(DUP: 3) | 0(PUSH: 3) | 1024个字 |
| 内存 | 当前调用上下文 | MLOAD: 3* | MSTORE: 3* | 可扩展 |
| 存储 | 永久(区块链) | SLOAD: 2100/100 | SSTORE: 20000/2900/100 | 2^256个槽 |
| Calldata | 当前调用上下文 | CALLDATALOAD: 3 | 只读 | 交易输入 |
*内存每次操作消耗3 gas加上二次扩展成本。
栈:快速但小巧
栈是EVM的工作内存。每次算术、比较和逻辑操作都从栈中读写。它最多容纳1024个元素,每个32字节宽。
在实践中,你很少使用超过16个栈槽,因为DUP和SWAP操作码只能达到16个元素深度。
内存:便宜但短暂
内存是一个按字节寻址的数组,起始为空并按需扩展。它仅在当前调用上下文期间持续存在。
空闲内存指针
Solidity保留内存的前128字节用于特殊目的。0x40-0x5f处的空闲内存指针追踪下一个可用的内存地址。
内存扩展成本
内存的gas成本不仅是每次MLOAD/MSTORE的3 gas——当访问超出当前最高水位线的内存时,会收取二次扩展成本:
memory_cost = (memory_size_words^2 / 512) + (3 * memory_size_words)
存储:永久但昂贵
存储是EVM的持久化键值存储。每个合约有其独立的2^256个32字节槽的存储空间。
Solidity中的存储布局
Solidity按顺序将状态变量分配到存储槽,并在可能时将多个小变量打包到同一个槽中。
Calldata:只读且便宜
Calldata是交易或调用发送的输入数据。它是只读的,访问便宜(CALLDATALOAD消耗3 gas)。
瞬态存储(EIP-1153)
EIP-1153(Cancun升级,2024年3月)引入了TSTORE和TLOAD操作码。瞬态存储类似普通存储,但在交易结束时自动清除。每次TLOAD和TSTORE仅消耗100 gas。
总结
EVM的内存模型看似简单——四个有明确取舍的位置。但成本差异巨大:栈ADD消耗3 gas而SSTORE消耗20000 gas。深刻理解这些成本是区分gas高效合约与gas浪费合约的关键。