Solidity 中的数据存储位置
你会发现这块内容是最具有挑战性的。下面的表格会是每块数据存储位置的概览,标识了读写的权限。具体每个数据存储位置的详情,可以阅读对应的具体模块。
数据读写权限
我们什么时候使用关键字, storage、memory、calldata ?
任何具有复杂类型的变量比如 array,struct,mappings 或者 enum 必须指定一个数据存储位置。
Storage
Storage 对应合约内的存储。
你可以把你不想被 cloned 的变量作为 Storage 存储。
Storage 是长期有效的,但是成本比较高。
Memory
在 ETH 中 memory 保存了临时变量,就像函数的参数。
而且,memory 变量在外部函数调用时会被清除。
Memory 是短暂且便宜的
Calldata
calldata 和 memory 的使用场景比较像,但是 calldata 初始化之后不能再修改值。
Stack
stack 存储了小的局部变量。然而它只能存储数量有限的变量。stack 在 eth 中只能有最大 1024 个元素。
一旦函数执行完成就结束。
函数参数的数据存储位置规则
下面的表格展示了函数参数可选的数据类型,取决于函数的可见性。
外部函数或者公有函数不能访问 storage
函数内部的数据存储位置
在函数内部所有的三种数据存储位置都能被指定,不管函数的可见性。然后在在赋值的时候存在一些具体的规则,如下面表格中展示:
Storage
storage references can be assigned a: | |
---|---|
state variable directly | ✅ |
state variable via storage reference | ✅ |
memory reference | ❌ |
calldata value directly | ❌ |
calldata value via calldata reference | ❌ |
Memory
memory references can be assigned a: | |
---|---|
state variable directly | ✅ |
state variable via storage reference | ✅ |
memoryreference | ✅ |
calldata value directly | ✅ |
calldata value via calldata reference | ✅ |
Calldata
calldata references can be assigned a: | |
---|---|
state variable directly | ❌ |
state variable via storagereference | ❌ |
memoryreference | ❌ |
calldata value directly | ✅ |
calldata value via calldatareference | ✅ |
数据存储位置-行为
当我们指定数据存储位置的时候,有 2 件主要的事情需要考虑:影响范围和 gas 的使用量。
让我们用一个简单的合约来更好的了解这个区别。为什么比较不用的存储位置,我们使用了不同的函数使用不同的数据类型关键词。
- a getter storage
- a getter memory
- a setter storage
- a setter memory
// SPDX-License-Identifier: Apache-2.0pragma solidity ^0.8.0;contract Garage { struct Item { uint256 units; } mapping(uint256 => Item) items; function getItemUnitsStorage(uint _itemIndex) public view returns(uint) { Item storage item = items[_itemIndex]; return item.units; } function getItemUnitsMemory(uint _itemIndex) public view returns (uint) { Item memory item = items[_itemIndex]; return item.units; }}