以太坊虚拟机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插槽存储关系相关推荐

    1. solidity智能合约[37]-以太坊虚拟机数据存储

      EVM 当调用solidity函数时,都会在以太坊虚拟机当中来执行.因此了解以太坊虚拟机的细节,了解其数据的存储机制变得尤为重要. 以太坊虚拟机中的空间分为3大部分.分别为storage.stack与 ...

    2. solidity payable_以太坊区块链搭建与使用(五)-智能合约Solidity

      一.智能合约Solidity开发工具 1.remix-ide http://remix.ethereum.org/ 在线版本,也可以去github下载安装到本地.开发.编译.发布.执行.测试 2.re ...

    3. solidity开发以太坊代币智能合约

      智能合约开发是以太坊编程的核心之一,而代币是区块链应用的关键环节,下面我们来用solidity语言开发一个代币合约的实例,希望对大家有帮助. 以太坊的应用被称为去中心化应用(DApp),DApp的开发 ...

    4. 以太坊数据结构与存储分析

      一.概述 在以太坊中,数据的存储大致分为三个部分,分别是:状态数据.区块链和底层数据. 其中,底层数据存放以太坊中全部数据,存储形式是[k,v]键值对,目前使用数据库是LevelDB:所有与交易,操作 ...

    5. 以太坊的数据存储结构

      1 存储整体结构 2 区块 区块分为两部分,即区块头和区块体. 区块头就是以太坊中的区块链部分.它保存了前一个区块(也可称为父区块)的哈希值,通过区块头的连接形成了一条由密码学背书的链. 区块体包含了 ...

    6. 【区块链 | Solidity】以太坊Solidity如何实现海量空投代币?

      以太坊Solidity如何实现海量空投代币? 1. 摘要 通证token项目启动时,短期内繁荣生态,要舍得给粉丝们打币,把利益分出去.本文聚焦在技术层面,实现如何快速完成TOKEN海量空投,既要节约时 ...

    7. 《迅雷链精品课》第七课:以太坊数据存储分析

      上一节课我们学习了比特币的区块链数据存储,接着前一篇的内容,我们继续了解以太坊的相关内容.业界一直把以太坊认为是区块链发展进程中2.0的代表,因为它在比特币的基础上增加了图灵完备的智能合约,扩展了区块 ...

    8. 以太坊存储Swarm的开发指南

      swarm是一个分布式存储平台以及内容分发服务,是一个以太坊Web3栈的一个本土服务层.swarm的最主要目标是为以太坊公共记录,尤其是Dapp代码与数据以及区块数据提供一个足够去中心化以及足够重复的 ...

    9. 一文读懂以太坊存储数据核心数据结构:MPT

      作者 | JouyPub 来源 | 简书 出品 | 区块链大本营 MPT (Merkle Patricia Tries) 是以太坊存储数据的核心数据结构,它是由 Merkle Tree 和 Patri ...

    10. 区块链入门 第七部分 以太坊

      以太坊 以太坊相关导航 以太坊(Ethereum)是一个开源的有智能合约功能的公共区块链平台.通过其专用加密货币以太币(Ether)提供去中心化的虚拟机("以太虚拟机" Ether ...

    最新文章

    1. 【笔记】2010-11-25记录
    2. easyre-153 testre寒假逆向生涯(13/100)
    3. html怎么设置单选框样式,CSS - 如何设置所选单选按钮标签的样式?
    4. Android开发之百度地图在地图上绘画圆的方法(官方方法)
    5. 前端学习(1398):多人管理18项目重定向
    6. openssl、ssh
    7. 嵌入式Linux系统编程学习之二常用命令
    8. js创建对象的多种方式及优缺点
    9. json.stringify php,JSON.stringify()用法介绍
    10. android 手机内存清理,安卓手机内存如何清理 安卓手机内存清理方法【介绍】
    11. 手机双摄像头原理及产业解析
    12. 穿上钢铁侠战衣变身钢铁侠,现代表示我做到了!
    13. 听说你想进大厂?当心这13个MySQL送命题!
    14. 粤嵌6818开发板项目
    15. 如何深入理解时间序列分析中的平稳性?
    16. ArcGIS如何将Excel表格转换为SHP格式
    17. 一个网络请求的历险之旅
    18. 昭阳E47G开机问题
    19. Springboot上传文件时提示405
    20. 离职的哪些理由千万不能说呢

    热门文章

    1. OBD(On-Board-Diagnose)
    2. PHP implode和explode用法
    3. PhotonServer MMO游戏开发
    4. java onfocus_[Java教程]onfocus和onblur应用代码实例
    5. Android一步步实现无痕埋点(3)-------虎躯一震
    6. 九度OJ题目1035:找出直系亲属
    7. python金融分析小知识(7)——股票收盘价曲线可视化
    8. 计算机考博面试题,交大系统博士笔试和面试题目
    9. 计算机 不识u盘,电脑uefi不识别u盘怎么办
    10. API MISUSE: <CBCentralManager: 0x000000000> can only accept this command while in the powered on