深入solidity内部 -以太坊EVN插槽存储关系
以太坊虚拟机Ethereum Virtual Machine(EVM) 拥有三种存储区域。
- 存储storage ( 贮存了合约声明中所有的变量)
贮存了合约声明中所有的变量。 虚拟机会为每份合约分别划出一片独立的 存储storage 区域,并在函数相互调用时持久存在,所以其使用开销非常大。
每个账户有一块持久化内存区称为 存储 。 存储是将256位字映射到256位字的键值存储区。 在合约中枚举存储是不可能的,且读存储的相对开销很高,修改存储的开销甚至更高。合约只能读写存储区内属于自己的部分。
- 内存memory ( 用于暂存数据)
用于暂存数据。其中存储的内容会在函数被调用(包括外部函数)时擦除,所以其使用开销相对较小。
合约会试图为每一次消息调用获取一块被重新擦拭干净的内存实例。 内存是线性的,可按字节级寻址,但读的长度被限制为256位,而写的长度可以是8位或256位。当访问(无论是读还是写)之前从未访问过的内存字(word)时(无论是偏移到该字内的任何位置),内存将按字进行扩展(每个字是256位)。扩容也将消耗一定的gas。 随着内存使用量的增长,其费用也会增高(以平方级别)
- 栈 (用于存放小型的局部变量)
用于存放小型的局部变量。使用几乎是免费的,但容量有限。
对绝大部分数据类型来说,由于每次被使用时都会被复制,所以你无法指定将其存储在哪里。
在数据类型中,对所谓存储地点比较重视的是结构和数组。 如果你在函数调用中传递了这类变量,假设它们的数据可以被贮存在 存储storage 或 内存memory 中,那么它们将不会被复制。也就是说,当你在被调用函数中修改了它们的内容,这些修改对调用者也是可见的
EVM 不是基于寄存器的,而是基于栈的,因此所有的计算都在一个被称为 栈(stack) 的区域执行。 栈最大有1024个元素,每个元素长度是一个字(256位)。对栈的访问只限于其顶端,限制方式为:允许拷贝最顶端的16个元素中的一个到栈顶,或者是交换栈顶元素和下面16个元素中的一个。所有其他操作都只能取最顶的两个(或一个,或更多,取决于具体的操作)元素,运算后,把结果压入栈顶。当然可以把栈上的元素放到存储或内存中。但是无法只访问栈上指定深度的那个元素,除非先从栈顶移除其他元素
不同数据类型的变量会有各自默认的存储地点:
状态变量总是会存在
存储storage
函数参数默认存放在
内存memory
结构、数组或映射类型的局部变量,默认会放在
存储storage
除结构、数组及映射类型之外的局部变量,会储存在栈中
因为结构和数组属于引用类型, 映射mapping和动态数组不可预知大小,不能在状态变量之间存储他们
引用类型
引用类型可以通过多个不同的名称修改它的值,而值类型的变量,每次都有独立的副本。因此,必须比值类型更谨慎地处理引用类型。 目前,引用类型包括结构,数组和映射,如果使用引用类型,则必须明确指明数据存储哪种类型的位置(空间)里:
- 内存memory 即数据在内存中,因此数据仅在其生命周期内(函数调用期间)有效。不能用于外部调用。
- 存储storage 状态变量保存的位置,只要合约存在就一直存储.
- 调用数据calldata 用来保存函数参数的特殊数据位置,是一个只读位置。
状态变量在储存(storage)中的布局
合约的状态变量以一种紧凑方式存储到区块存储中, 除了动态大小的数组和 映射mapping (见下文),数据的存储方式是从位置
0
开始连续放置在 存储storage 中。 对于每个变量,根据其类型确定字节大小。存储大小少于 32 字节的多个变量会被打包到一个 存储插槽storage slot 中,规则如下:
- 存储插槽storage slot 的第一项会以低位对齐的方式储存。
- 值类型仅使用存储它们所需的字节。
- 如果 中的剩余空间不足以储存一个值类型,那么它会被存入下一个存储插槽storage slot
- 结构体(struct)和数组数据总是会开启一个新插槽(但结构体或数组中的各元素,则按规则紧密打包)。
- 结构体和数组之后的数据也或开启一个新插槽。
对于使用继承的合约,状态变量的排序由C3线性化合约顺序( 顺序从最基类合约开始)确定。如果上述规则成立,那么来自不同的合约的状态变量会共享一个 存储插槽storage slot 。
结构体和数组中的成员变量会存储在一起,就像它们单独声明时一样
在使用小于 32 字节的元素(变量)时,合约的 gas 使用量可能会高于使用 32 字节的元素。这是因为 以太坊虚拟机Ethereum Virtual Machine(EVM) 每次操作 32 个字节, 所以如果元素比 32 字节小,以太坊虚拟机Ethereum Virtual Machine(EVM) 必须执行额外的操作以便将其大小缩减到到所需的大小。
当我们在处理状态变量时,利用编译器会将多个元素缩减的存储大小打包到一个 存储插槽storage slot 中,也许是有益,因为可以合并多次读写为单个操作。
如果你不是在同一时间读或写一个槽中的所有值,这可能会适得其反。 当一个值被写入一个多值存储槽时,必须先读取该存储槽,然后将其与新值合并,避免破坏同一槽中的其他数据,再写入。
当处理函数参数或 内存memory 中的值时,因为编译器不会打包这些值,所以没有什么额外的益处。
最后,为了允许 以太坊虚拟机Ethereum Virtual Machine(EVM) 对此进行优化,请确保 存储storage 中的变量和
struct
成员的书写顺序允许它们被紧密地打包。例如,应该按照
uint128,uint128,uint256
的顺序来声明状态变量,而不是使用uint128,uint256,uint128
, 因为前者只占用两个 存储插槽storage slot,而后者将占用三个。映射和动态数组
由于 映射mapping 和动态数组不可预知大小,不能在状态变量之间存储他们。相反,他们自身根据 以上规则 仅占用 32 个字节,然后他们包含的元素的存储的其实位置,则是通过 Keccak-256 哈希计算来确定。
假设 映射mapping 或动态数组根据上述存储规则最终可确定某个位置
p
。 对于动态数组,此插槽中会存储数组中元素的数量(字节数组和字符串除外,见下文)。 对于 映射mapping ,该插槽未被使用(为空),但它仍是需要的,以确保两个彼此挨着 映射mapping ,他们的内容在不同的位置上。数组的元素会从
keccak256(p)
开始; 它的布局方式与静态大小的数组相同。一个元素接着一个元素,如果元素的长度不超过16字节,就有可能共享存储槽。动态数组的数组会递归地应用这一规则,例如,如何确定
x[i][j]
元素的位置,其中x
的类型是uint24[][]
,计算方法如下(假设x`本身存储在槽`p
): 槽位于keccak256(keccak256(p)+i)+floor(j/floor(256/24))
且可以从槽数据v``得到元素内容,使用``(v>>((j%floor(256/24))*24))&type(uint24).max
.映射mapping 中的键
k
所对应的槽会位于keccak256(h(k). p)
,其中.
是连接符,h
是一个函数,根据键的类型:- 值类型,
h
与在内存中存储值的方式相同的方式将值填充为32字节。 - 对于字符串和字节数组,
h(k)
只是未填充的数据。
如果映射值是一个非值类型,计算槽位置标志着数据的开始位置。例如,如果值是结构类型,你必须添加一个与结构成员相对应的偏移量才能到达该成员。
例如,考虑下面的合约:
*// SPDX-License-Identifier: GPL-3.0***pragma solidity** >=**0.4.0** <**0.9.0**;**contract** **C** {struct S { uint16 a; uint16 b; uint256 c; }uint x;mapping(uint => mapping(uint => S)) data; }
让我们计算一下
data[4][9].c
的存储位置。映射本身的位置是1``( 前面有32字节变量 ``x
)。 因此data[4]
存储在keccak256(uint256(4) . uint256(1))
。data[4]
的类型又是一个映射,data[4][9]
的数据开始于槽位keccak256(uint256(9). keccak256(uint256(4). uint256(1))
。在结构
S
的成员c
中的槽位偏移是1
,因为a
和b``被装在一个槽位中。 最后 ``data[4][9].c
的插槽位置是keccak256(uint256(9) . keccak256(uint256(4) . uint256(1)) + 1
. 该值的类型是uint256
,所以它使用一个槽变量在内存布局
Solidity保留了四个32字节的插槽,字节范围(包括端点)特定用途如下:
0x00
-0x3f
(64 字节): 用于哈希方法的暂存空间(临时空间)0x40
-0x5f
(32 字节): 当前分配的内存大小(也作为空闲内存指针)0x60
-0x7f
(32 字节): 零位插槽
暂存空间可以在语句之间使用 (例如在内联汇编中)。 零位插槽用作动态内存数组的初始值,并且永远不应写入(空闲内存指针最初指向
0x80
).Solidity 总是将新对象放在空闲内存指针上,并且内存永远不会被释放(将来可能会改变)。
Solidity 中的内存数组中的元素始终占据32字节的倍数(对于
bytes1[]
总是这样,但不适用与bytes
和string
)。多维内存数组是指向内存数组的指针,动态数组的长度存储在数组的第一个插槽中,然后是数组元素。
警告:
深入solidity内部 -以太坊EVN插槽存储关系相关推荐
- solidity智能合约[37]-以太坊虚拟机数据存储
EVM 当调用solidity函数时,都会在以太坊虚拟机当中来执行.因此了解以太坊虚拟机的细节,了解其数据的存储机制变得尤为重要. 以太坊虚拟机中的空间分为3大部分.分别为storage.stack与 ...
- solidity payable_以太坊区块链搭建与使用(五)-智能合约Solidity
一.智能合约Solidity开发工具 1.remix-ide http://remix.ethereum.org/ 在线版本,也可以去github下载安装到本地.开发.编译.发布.执行.测试 2.re ...
- solidity开发以太坊代币智能合约
智能合约开发是以太坊编程的核心之一,而代币是区块链应用的关键环节,下面我们来用solidity语言开发一个代币合约的实例,希望对大家有帮助. 以太坊的应用被称为去中心化应用(DApp),DApp的开发 ...
- 以太坊数据结构与存储分析
一.概述 在以太坊中,数据的存储大致分为三个部分,分别是:状态数据.区块链和底层数据. 其中,底层数据存放以太坊中全部数据,存储形式是[k,v]键值对,目前使用数据库是LevelDB:所有与交易,操作 ...
- 以太坊的数据存储结构
1 存储整体结构 2 区块 区块分为两部分,即区块头和区块体. 区块头就是以太坊中的区块链部分.它保存了前一个区块(也可称为父区块)的哈希值,通过区块头的连接形成了一条由密码学背书的链. 区块体包含了 ...
- 【区块链 | Solidity】以太坊Solidity如何实现海量空投代币?
以太坊Solidity如何实现海量空投代币? 1. 摘要 通证token项目启动时,短期内繁荣生态,要舍得给粉丝们打币,把利益分出去.本文聚焦在技术层面,实现如何快速完成TOKEN海量空投,既要节约时 ...
- 《迅雷链精品课》第七课:以太坊数据存储分析
上一节课我们学习了比特币的区块链数据存储,接着前一篇的内容,我们继续了解以太坊的相关内容.业界一直把以太坊认为是区块链发展进程中2.0的代表,因为它在比特币的基础上增加了图灵完备的智能合约,扩展了区块 ...
- 以太坊存储Swarm的开发指南
swarm是一个分布式存储平台以及内容分发服务,是一个以太坊Web3栈的一个本土服务层.swarm的最主要目标是为以太坊公共记录,尤其是Dapp代码与数据以及区块数据提供一个足够去中心化以及足够重复的 ...
- 一文读懂以太坊存储数据核心数据结构:MPT
作者 | JouyPub 来源 | 简书 出品 | 区块链大本营 MPT (Merkle Patricia Tries) 是以太坊存储数据的核心数据结构,它是由 Merkle Tree 和 Patri ...
- 区块链入门 第七部分 以太坊
以太坊 以太坊相关导航 以太坊(Ethereum)是一个开源的有智能合约功能的公共区块链平台.通过其专用加密货币以太币(Ether)提供去中心化的虚拟机("以太虚拟机" Ether ...
最新文章
- 【笔记】2010-11-25记录
- easyre-153 testre寒假逆向生涯(13/100)
- html怎么设置单选框样式,CSS - 如何设置所选单选按钮标签的样式?
- Android开发之百度地图在地图上绘画圆的方法(官方方法)
- 前端学习(1398):多人管理18项目重定向
- openssl、ssh
- 嵌入式Linux系统编程学习之二常用命令
- js创建对象的多种方式及优缺点
- json.stringify php,JSON.stringify()用法介绍
- android 手机内存清理,安卓手机内存如何清理 安卓手机内存清理方法【介绍】
- 手机双摄像头原理及产业解析
- 穿上钢铁侠战衣变身钢铁侠,现代表示我做到了!
- 听说你想进大厂?当心这13个MySQL送命题!
- 粤嵌6818开发板项目
- 如何深入理解时间序列分析中的平稳性?
- ArcGIS如何将Excel表格转换为SHP格式
- 一个网络请求的历险之旅
- 昭阳E47G开机问题
- Springboot上传文件时提示405
- 离职的哪些理由千万不能说呢
热门文章
- OBD(On-Board-Diagnose)
- PHP implode和explode用法
- PhotonServer MMO游戏开发
- java onfocus_[Java教程]onfocus和onblur应用代码实例
- Android一步步实现无痕埋点(3)-------虎躯一震
- 九度OJ题目1035:找出直系亲属
- python金融分析小知识(7)——股票收盘价曲线可视化
- 计算机考博面试题,交大系统博士笔试和面试题目
- 计算机 不识u盘,电脑uefi不识别u盘怎么办
- API MISUSE: <CBCentralManager: 0x000000000> can only accept this command while in the powered on