这里所讲的变量特指在Storage中存储的evm全局变量。

首先需要明确的一点,目前版本的solidity(^0.8.0)不会对变量顺序进行重排。

Storage的每个槽位(slot)大小为256bit,slot的地址范围也是256bit,也就是说理论上最多可以有256bit*2^256大小的储存空间。

当然,由于gas的限制,实际并不可能开满,但这巨大的寻址空间确实有利于后面一些不定长的数据结构的存储。

constant常量不占用任何储存空间。

对齐

solidity并不会使用类似c中的复杂的对齐方式,而是简单地尝试在已有变量后直接连接下一个变量,若发现当前slot已经无法放下该变量,就会直接从下一个slot开始。

struct和数组无论如何都会从下一个新slot开始。

定长变量

对于常规类型,或者定长数组等长度在编译时就可知的变量,solidity会简单地按照对齐规则顺序地排列这些变量。

solidity修改变量标准套路:

PUSH5 0x1234567890 // value
PUSH1 0x0          // slot
PUSH1 0x1          // offset in slot
PUSH2 0x100
EXP
DUP2
SLOAD              // load slot origin value
DUP2
PUSH5 0xFFFFFFFFFF
MUL
NOT
AND
SWAP1
DUP4
PUSH5 0xFFFFFFFFFF
AND
MUL
OR
SWAP1
SSTORE
POP

mapping

声明:mapping(address => uint256)

mapping不可迭代,也没有方法获得其size,只能通过key来访问对应元素,同时,也不可以作为参数传递给其他函数/合约。

在Storage中,以SHA3( key+mapping slot number)作为value的开始地址(因为value 大小可能大于slot大小,所以称为开始地址),写为伪汇编如下:

mstore(0x20, slot_number);
mstore(0x0, key);
value_addr = keccak256(0x0, 0x40);
value = sload(value_addr);

虽然mapping不会在对应slot存储任何东西,但是仍然占据该slot,可视为一个占位的uint256。

当使用bytes/string作为key时,solidity不再会使用0x00~0x40这段保留空间计算SHA,而是会在memory上新开一块空间,将bytes/string的实际内容连接上已经扩充为uint256的mapping所在的slot编号存入,并计算这段内容的SHA3。(注意,此时bytes/string后没有任何padding,也就是说,不保证slot编号存入的地址一定是某个slot的开头)

free_pointer = mload(0x40)
xxdatacopy(free_pointer, DATA_START, DATA_LEN)
mstore(free_pointer + DATA_LEN, slot_number)
value_addr = keccak256(free_pointer, free_pointer + DATA_LEN + 0x20);
value = sload(value_addr);