EVM—calldata存储空间详解文章旨在记录过去所学知识,若文章内容存在不当,欢迎指出。若对文章内容感兴趣,也欢迎评论区留言讨论!文章部分图片取自本人其他博客。一、Calldata的结构EVM中的主要数据存储结构包括Storage、Memeory、Stack、Calldata。本章主要介绍
文章旨在记录过去所学知识,若文章内容存在不当,欢迎指出。若对文章内容感兴趣,也欢迎评论区留言讨论!文章部分图片取自本人其他博客。
EVM中的主要数据存储结构包括Storage、Memeory、Stack、Calldata。本章主要介绍Calldata数据结构。在EVM中,Calldata是由字节组成的,是以和memory相同方式的连续布局,可以将每一单位看作是一个32字节的数据结构。 下面以常见的类型参数为例子说明calldata的布局:
以下面函数为例: 该函数参数kee数据在calldata中的布局: <!--StartFragment-->
<!--EndFragment-->
在分析calldata数据存储之前,明确一点:对于任意参数数据,EVM要想正确的处理该参数,比对要知道:1.参数的长度 2.参数的值。 对于静态数据,calldata只存放参数数据,因为静态数据的长度在编译的时候就已经确定了,其长度已经被写死字节码中,所以在calldata中只需要存放参数的值即可。而对于动态数据,其长度在编译的时候尚不得知,所以动态参数数据长度和参数值都会被存放在calldata中。
对于uint8[4],0x00-0x03这四个字节用来存放函数选择器,calldata中第一个存储空间的索引值是0x04,用于存放uint8[0],由于calldata中的存储空间以32字节为单位,所以calldata中的第二存储空间的索引值是0x24,用于存放uint8[1],以此类推。 由于结构体结构相对复杂,可以更好的帮助理解数据在calldata中的布局。对于一个结构体,如果其中的元素全部都为静态数据,那么这个结构体便是静态结构体。
该函数参数在calldata中的布局如下图所示: <!--StartFragment-->
<!--EndFragment-->
结构体中数据定义的先后顺序就反映了静态数据在calldata中存储的先后顺序。
以下面的函数为例: 该类型参数kee在calldata中的布局: <!--StartFragment--> <!--EndFragment--> 对于动态数据kee,其元素个数在编译的时候我们尚不可知,只有当使用者输入参数具体运行函数test6之后,我们才能知道参数kee的具体个数,所以参数长度没有办法写在字节码中,只能在calldata中开辟空间进行存储。所以我们在0x64下面使用"..."来表示,意味着在真实的运行情况下,还可能有很多元素。 接下来详细解析以下kee在calldata中的布局:
通过该例子我希望大家能够了解对于一个动态类型的参数,他在calldata中至少包含:
为了让大家更加熟悉动态参数在calldata中的存储,再来介绍一下复杂点的情况: <!--StartFragment-->
<!--EndFragment-->
以上述函数为例,介绍对于一个函数参数既有动态数据又有静态数据的情况。 将输入参数设置为[11,22],[44,55],33,在remix上得到整个calldata的情况: <!--StartFragment-->
<!--EndFragment-->
由于可读性较差,故将其转换为下图所示: <!--StartFragment--> <!--EndFragment--> 参数kee的元素在calldata中包含:
参数lee的元素在calldata中包含:
静态参数mee:
注意:有一个小问题,可以检测上面的内容你是否已经理解——为什么mee的数据存放在kee和lee的数据的前面,在参数中不是kee和lee在参数mee前面吗? 实际上是因为,静态数据的长度是在编译的时候确定,所以编译器将合约源代码编译成字节码时就已经会为其分配对应静态数据长度的内存空间,但是对于动态数据,其数据长度是在程序运行时才确定,所以编译器将合约源码编译成字节码时,不能确定动态数据的长度,所以只能按照参数在函数中的声明位置先使用一个offset字段为该动态数据占一个位置。该offset字段的值就直接指向动态数据在calldata中具体位置。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!