存储中状态变量的布局(Layout of State Variables in Storage)
静态尺寸大小的变量(除了映射和动态尺寸大小的数组类型(的其他类型变量))在存储中,是从位置0连续存储。如果可能的话,不足32个字节的多个条目被紧凑排列在一个单一的存储块,参见以下规则:
- 在存储块中的第一项是存储低阶对齐的。
- 基本类型只使用了正好存储它们的字节数。
- 如果一个基本类型不适合存储块的剩余部分,则移动到下一个存储块中。
- 结构和数组的数据总是开始一个新的块并且占整个块(根据这些规则,结构或数组项都是紧凑排列的)。
警告 |
---|
当使用小于32字节的元素时,合约的gas使用可能会更高。这是因为EVM一次运行32个字节。因此,如果元素小于该元素,则EVM必须使用更多的操作,以便将元素的大小从32字节减小到所需的大小。 |
只有在处理存储值时,使用缩减大小的参数才是有益的,因为编译器将多个元素打包到一个存储槽中,因此,将多个读或写组合到单个操作中。在处理函数参数或内存值时,没有固有的好处,因为编译器不打包这些值。 |
最后,为了允许EVM对此进行优化,确保您尝试对存储变量和struct 成员进行排序,以便它们可以被紧密打包。例如,以uint128 , uint128 , uint256 而不是uint128 , uint256 , uint128 的顺序声明存储变量,前者只占用两个存储槽,而后者将占用三个存储槽。 |
结构和数组元素是一个接着一个存储排列的,就如当初它们被声明的次序。
由于无法预知的分配的大小,映射和动态尺寸大小的数组类型(这两种类型)是使用sha3 计算来找到新的起始位置,来存放值或者数组数据。这些起始位置总是满栈块。
根据上述规则,映射或动态数组本身存放在(没有填满)的存储块位置p
(或从映射到映射或数组递归应用此规则)。对于一个动态数组,存储块存储了数组元素的数目(字节数组和字符串是一个例外,见下文)。对于映射,存储块是未使用的(但它是需要的,因此,前后相邻的两个相同的映射,将使用一个不同的hash分布)。数组数据位于keccak256(p)
, 对应于一个映射key值k
位于keccak256(k . p)
(这里 .
是连接符)。如果该值又是一个非基本类型,位置的偏移量是keccak256(k . p)
。
如果bytes
和 string
是短类型的,它们将和其长度存储在同一个存储块里。特别是:如果数据最长31
字节,它被存储在高阶字节(左对齐), 低字节存储length * 2
。 如果是长类型,主存储块存储 length * 2 + 1
, 数据存储在keccak256(slot)
。
因此,本合约片段如下:
pragma solidity ^0.4.0;
contract C {
struct s { uint a; uint b; }
uint x;
mapping(uint => mapping(uint => s)) data;
}
data[4][9].b
的位置在
keccak256(uint256(9) . keccak256(uint256(4) . uint256(1))) + 1
.