2018-10-24 笔记
在Solidity - 引用类型中提到,当没有给局部变量的uint[] storage p
赋值的时候,p
会默认指向storage
的第一个slot
。
首先,slot
的英文翻译是指容器的插槽、投币口,暂时先不解释这个东西。先看看storage
,官方对storage
的解释是
Every account has a persistent key-value store mapping 256-bit words to 256-bit words called storage.
中文意思是,storage
是一个持久化的存储区,它将32字节的键映射到32字节的键。
简单理解就是,storage
是一个超大的数组,数组可以看成储物柜,slot
就是一个个的小柜子。
其中每一个slot
的大小是32
字节,slot0
的地址(即storage
的起始地址)是0x00
。Solidity对声明时候没有进行初始化的变量,都默认值为0。因此p
作为一个指针,其中指针的值就是内存的地址(这点与C语言指针一样),因此它会指向slot0
。至于为什么a
的值变了,当然就是因为a
存储在了slot0
。
下面整理一下storage
内存布局的几条规则:
- 变量位置按照声明的顺序,从
slot0
开始排序。因此a
存储在了slot0
。 - 变量占用的内存大小与他定义的类型一致,也就是
uint256
占用32
个字节,uint8
占用1
个字节。 - 如果一个变量不能完全存在一个
slot
中,那么他就从下一个slot
开始存储。
uint8 a; //存储在slot0
uint256 b; //因为slot0剩余的空间不够存下一个uint256,则从slot1开始存储,另外uint 相当于 uint256
-
structs
和arrays
总是在一个新的slot
中开始存储,并且占用整个slot
,但每一个元素是紧紧相挨的。
pragma solidity ^0.4.0;
contract hello {
struct items {
uint8 a;
uint8 b;
}
items[3] public items_arr;
}
items_arr
数组有三个元素,他们的内存占用情况是,首先struct items
的内存占用是2字节,然后按照一个struct
占用一个slot
的话,那么items_arr
占用了3*32
的字节,其中很多都浪费了。(这个其实我自己也不太肯定,因为使用Remix调试的时候,看不到storage
内存详情,也许要通过指令集来判断,这里暂时存疑,以后验证再补充。XXX)
Arrays的存储方式
arrays
有两种类型,静态长度uint[3]
与动态长度uint[]
。静态长度的话,在编译的时候,就可以确定他的内存占用大小,因此可以提前分配。但是动态长度的array
,他的元素大小是不确定的,因此需要使用别的方式来存储动态长度的数组。举个例子:
pragma solidity ^0.4.0;
contract hello {
uint a;
uint[] public b;
uint c;
uint[3] d;
}
其中a
的位置是slot0
,那么b
的位置是slot1
,c
的位置是slot2
,d
的位置是slot3
-slot5
。可以看到动长数组只占用了一个slot
,然后这个位置用来存放动长数组的长度,即b.lenght
的值。动态数组的元素存储在其他的位置,这个位置根据keccak256()
方法计算出来,比如b[1]
的位置就是keccak256(1 . p)
。其中p
是b
所在slot1
的起始地址0x32
(因为一个slot
占32
个字节),另外.
代表的是连接符。
Mappings的存储方式
Mappings
也会占用一个slot
,但是这个slot
是空的,不记录东西,用来做为寻找元素的基址,寻址方式则跟Arrays
的一样。