Solidity - 内存布局

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Solidity - 内存布局相关的知识,希望对你有一定的参考价值。

参考技术A

在 Solidity - 引用类型 中提到,当没有给局部变量的 uint[] storage p 赋值的时候, p 会默认指向 storage 的第一个 slot 。
首先, slot 的英文翻译是指容器的插槽、投币口,暂时先不解释这个东西。先看看 storage ,官方对 storage 的解释是

简单理解就是, storage 是一个超大的数组,数组可以看成储物柜, slot 就是一个个的小柜子。

其中每一个 slot 的大小是 32 字节, slot0 的地址(即 storage 的起始地址)是 0x00 。Solidity对声明时候没有进行初始化的变量,都默认值为0。因此 p 作为一个指针,其中指针的值就是内存的地址(这点与C语言指针一样),因此它会指向 slot0 。至于为什么 a 的值变了,当然就是因为 a 存储在了 slot0 。
下面整理一下 storage 内存布局的几条规则:

items_arr 数组有三个元素,他们的内存占用情况是,首先 struct items 的内存占用是2字节,然后按照一个 struct 占用一个 slot 的话,那么 items_arr 占用了 3*32 的字节,其中很多都浪费了。(这个其实我自己也不太肯定,因为使用Remix调试的时候,看不到 storage 内存详情,也许要通过指令集来判断,这里暂时存疑,以后验证再补充。XXX)

arrays 有两种类型,静态长度 uint[3] 与动态长度 uint[] 。静态长度的话,在编译的时候,就可以确定他的内存占用大小,因此可以提前分配。但是动态长度的 array ,他的元素大小是不确定的,因此需要使用别的方式来存储动态长度的数组。举个例子:

其中 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 也会占用一个 slot ,但是这个 slot 是空的,不记录东西,用来做为寻找元素的 基址 ,寻址方式则跟 Arrays 的一样。

区块链开发之Solidity编程基础

Solidity编程基础三

概要

本章将进行太坊虚拟机EVM的介绍、Solidity的三种数据存储位置的 区别以及不同情况下跨区域数据赋值的gas成本分析与利用等内容。

在前文讲变量、函数时,我们讲过EVM提供了四种数据结构来存储数据:Storage、Calldata、Stack、Memory。

以太坊虚拟机 EVM

EVM是以太坊用于提供Solidity合约运行的轻量级操作系统,其运行在以太坊的每个节点上。EVM的架构基于栈机器模型,这意味着 其指令集是基于栈而非寄存器来运作的,所以在开始探讨Solidity的数据存储之前,我想先介绍下以太坊虚拟机 的一些相关内容,以便更容易理解后续的部分。

EVM的内部结构大致如下图所示:

在EVM中指令的执行流程如下:当一个交易触发智能合约代码的执行时, 就会实例化一个EVM,EVM的ROM载入了要调用的合约代码。程序计数器 被清零,存储从合约账号对应的部分载入,内存清零,设置区块和环境 变量,然后代码开始执行。

数据位置

Storage、Calldata、Stack、Memory。

storage/存储

  • 存储中的数据是永久存在的
  • 存储是一个key/value库 存储中的数据写入区块链,因此会修改状态,这也是存储使用成本高的原因
  • 当清零一个存储槽时,会返还一定数量的gas
  • 存储按256位的槽位分配,即使没有完全使用一个槽位,也需要支付其开销

memory/内存

  • 内存是一个字节数组,槽大小位256位(32字节)
  • 数据仅在函数执行期间存在,执行完毕后就被销毁
  • 读或写一个内存槽都会消耗gas

calldata/调用数据

  • 调用数据是不可修改、非持久化的区域,用来保存函数参数,其行为类似于内存
  • 外部函数的参数必须使用calldata,但是也可用于其他变量
  • 调用数据避免了数据拷贝,并确保数据不被修改
  • 函数也可以返回使用calldata声明的数组和结果,但是不可能分配这些类型

Stack 栈

EVM为了导入变量和以太坊的机器/汇编指令代码,维护了一个栈,本质是EVM的内存工作环境,有1024级神,如果存储超过1024级数据,便会触发异常。

总结:

  • 默认的函数参数(包括返回的参数)是memory
  • 默认的局部变量时storage
  • 默认的状态变量(合约声明中的公有变量)是storage

数据赋值成本

  • 在存储和内存(或调用数据)间的赋值将创建一个新的独立拷贝
  • 内存之间的赋值仅创建引用,这意味着对一个内存变量的修改会同时反应在其他引用相同数据的内存变量上
  • 从存储到局部存储变量的赋值,实际上只会给一个引用
  • 所有其他赋值通常导致产生新的数据拷贝。例如赋值给状态变量 或位于存储的结构类型的局部变量成员时,即使局部变量只是一个引用,也会产生新的数据拷贝

memory存储位置同普通程序的内存一致,即时分配,即时使用,而在区块链上,由于底层实现了图灵完备,会有非常多的状态需要永久记录下来,因此需要使用storage类型存储,一旦使用了该类型,数据将永远存在链上,基于程序的上下文,大多数时候默认选择storage,也可以通过指定关键字storage和memory进行修改。

以上是关于Solidity - 内存布局的主要内容,如果未能解决你的问题,请参考以下文章

浅析C++内存布局

C++ 虚拟继承内存布局

C++ 内存布局:深入理解C++内存布局

C中的结构内存布局

一个类的内存布局是连续的吗?

Rust 中的精确内存布局控制?