Solidity中存储和内存的数据布局
- 存储(Storage)中的数据布局
1.1 基本原则(基本值类型)
- 独立变量互不影响,因此非常安全。
- 变量存储在
slot中,共有2^256个slot,每个长度为256位。 - 从
0开始连续存储(动态数组和映射除外),当有连续几个变量都小于256位时,会尝试将它们放在同一个slot中。 - 遵循以下规则:
- 小于256位的数据存储在低阶位置(右边)。
Value types使用固定位数存储(例如uint128始终使用128位)。- 当前
slot不够存时,会存在下一个slot中。 Structs和Array会在新的slot开始存储,并且紧密打包。Struct和Array后面的数据,会单开一个slot。
1.2 动态数组和映射
由于动态数组和映射的不可预测性,它们不像普通状态变量那样紧跟着存储,而是占用一个新的slot,再根据特定规则存储。
动态数组
- 在
slot p的位置存储数组长度,在slot Keccak256(p)的位置开始存储数组元素。
| Key | Value |
|---|---|
slot 0 |
其他数据 |
| ... | ... |
slot p |
数组长度 |
| ... | ... |
slot Keccak256(p) |
元素1 |
slot Keccak256(p) + 1 |
元素2 |
| ... | ... |
function getHash(uint256 value) public pure returns (bytes32) {
return keccak256(abi.encodePacked(value));
}
映射
- 在
slot p的位置存储全0数据,表示此处是一个映射的开始。 - 映射的
key存储在slot Keccak256(h(k) . p),映射的value则直接存储在对应的slot中。
| Key | Value |
|---|---|
slot 0 |
其他数据 |
| ... | ... |
slot p |
全0 (映射起始) |
| ... | ... |
slot Keccak256(h(key1) . p) |
key1.value |
| ... | ... |
slot Keccak256(h(key2) . p) |
key2.value |
| ... | ... |
示例1(多个连续映射)
contract Example {
uint256 public a = 0x1;
mapping (address => uint256) public b;
mapping (address => uint256) public c;
function addMapping() public {
b[address(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4)] = 0xb11;
c[address(0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB)] = 0xc11;
b[address(0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2)] = 0xb22;
c[address(0x617F2E2fD72FD9D5503197092aC168c91465E7f2)] = 0xc22;
b[address(0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db)] = 0xb33;
c[address(0x17F6AD8Ef982297579C203069C1DbfFE4348c372)] = 0xc33;
}
}
示例2(多重映射)
contract Example {
uint256 public a = 0x1;
mapping (address => mapping (uint256 => uint256)) public data;
function addMapping() public {
data[address(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4)][3] = 4;
data[address(0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2)][7] = 8;
}
}
1.3 字符串和字节数组
字符串和字节数组的编码方式类似,但不同长度的编码方式有所不同。
长度不超过31字节
- 新起一个
slot,前31个字节存放数据,最后一个字节存放数据长度 * 2。
contract Example {
bytes public b = "WZM";
string public c = "WZM1230";
}
长度超过31字节
- 在
slot p的位置存储length * 2 + 1,在slot Keccak256(p)的位置开始存储数据。
contract Example {
bytes public b = "WZMWZMWZMWZMWZMWZMWZMWZMWZMWZMWZM";
string public c = "WZM1230WZM1230WZM1230WZM1230WZM1230";
}
- 内存(Memory)中的数据布局
- 仅有
string、bytes、array、struct使用内存。 - 以太坊内存保留了前四个
slot,分别是: 0x00 - 0x1f, 0x20 - 0x3f:两个slot,存储散列方法。0x40 - 0x5f:当前分配的内存大小。0x60 - 0x7f:零slot(不能写入)。
每个元素均占用一个slot,与存储不同。
contract Example {
struct Wallet {
address addr;
uint16 num;
}
function Demo() public {
bytes memory a = "123";
bytes8[] memory b = new bytes8[](0x3);
string memory c = "WZM1230";
Wallet memory wallet = Wallet(0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db, 0x123);
}
}