开始之前,我们约定数据块也叫插槽,也就是storage。
storage是永久存储在区块链上的地方。如果你想操作storage中的数据,你可以将它复制到内存中。然后,所有内存代码都在堆栈上执行。Stack 的最大深度为 1024 个元素,支持 256 位的字长。
结构
当定义局部变量时,它存储在内存中,然后压入堆栈以执行。
1024栈深
简介
设计原因
具有固定大小使得 EVM 的整体模型更加简单且易于实现
如果它非常大,那么执行合约会更昂贵(即需要更多内存)。1024 是一个非常保守的值,以尽可能安全
EVM 的设计方式往往会使更大的堆栈变得无用。EVM 只能访问堆栈中前16个slot。因此,即使您有一个 4096 slot的的堆栈,也只能够访问前16个,除非你不断pop,才可以访问到下面更深层次的内容
访问限制
对stack的访问仅限于顶端:
注意:只可以访问前16个元素:状态变量可以无限个,但是局部变量最多16个,局部变量存储在堆栈中,下面是一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| contract stackExample { function test() public{ bytes1 a1 = "0"; bytes1 a2 = "0"; bytes1 a3 = "0"; bytes1 a4 = "0"; bytes1 a5 = "0"; bytes1 a6 = "0"; bytes1 a7 = "0"; bytes1 a8 = "0"; bytes1 a9 = "0"; bytes1 a10 = "0"; bytes1 a11 = "0"; bytes1 a12 = "0"; bytes1 a13 = "0"; bytes1 a14 = "0"; bytes1 a15 = "0"; bytes1 a16 = "0"; bytes1 a17 = "0"; } }
|
报错内容:
1 2 3 4 5 6
| 报错内容如下: from solidity: aaa.sol:6:9: CompilerError: Stack too deep, try removing local variables. uint256 a1 = 0; ^--------^ 【去掉任何一个变量则不报错,因为局部变量最多16个】
|
2023/24/27更新
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract stackExample { function test() public returns (uint256){ uint256 a1 = 0; uint256 a2 = 0; uint256 a3 = 0; uint256 a4 = 0; uint256 a5 = 0; uint256 a6 = 0; uint256 a7 = 0; uint256 a8 = 0; uint256 a9 = 0; uint256 a10 = 0; uint256 a11 = 0; uint256 a12 = 0; uint256 a13 = 0; uint256 a14 = 0; uint256 a15 = 0; uint256 a16 = 0; uint256 a17 = 0; uint256 a18 = 0; return a18; } } //上述合约无法通过编译// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract stackExample { function test() public returns (uint256){ uint256 a1 = 0; uint256 a2 = 0; uint256 a3 = 0; uint256 a4 = 0; uint256 a5 = 0; uint256 a6 = 0; uint256 a7 = 0; uint256 a8 = 0; {uint256 a9 = 0; uint256 a10 = 0; uint256 a11 = 0; uint256 a12 = 0; uint256 a13 = 0; uint256 a14 = 0; uint256 a15 = 0;} uint256 a16 = 0; uint256 a17 = 0; uint256 a18 = 0; return a18; } } //上述合约可以通过编译 // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract stackExample { function test() public{ uint256 a1 = 0; uint256 a2 = 0; uint256 a3 = 0; uint256 a4 = 0; uint256 a5 = 0; uint256 a6 = 0; uint256 a7 = 0; uint256 a8 = 0; uint256 a9 = 0; uint256 a10 = 0; uint256 a11 = 0; uint256 a12 = 0; uint256 a13 = 0; uint256 a14 = 0; uint256 a15 = 0; uint256 a16 = 0; uint256 a17 = 0; uint256 a18 = 0; } } //上述合约可以通过编译 |
拷贝与引用
特点
数据类型
局部变量的location
memory
calldata
storage:指向storage
消息调用的有效载荷叫做calldata,和location的calldata不是同一个意思!
消息调用message的calldata,即有效载荷的部分。message不能被修改,因此location的calldata是只读的。calldata的数据块不能被值拷贝,但是可以进行引用拷贝。也就是说,calldata这个area的数据只能引用message中的数据,或者两个calldata之间相互引用。
任何一个成员变量永远都只会指向属于自己的数据块(插槽),是一对一的关系,不存在一对多、多对一、多对多的情况,如图:
图示与算法
代码演示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
| pragma solidity ^0.8.0;contract MemberVatiable{ int256[] data1; int256[] data2; function getData1() public view returns(int256 memory){ return data1; } function getData2() public view returns(uint256 memory){ return data2; } function insertData1(int256 d) public{ return data1.push(d); } function insertData2(int256 d) public{ return data2.push(d); } //1.成员变量都是存放在storage //2.storage存储位置的变量之间赋值的时候是值拷贝 function setData2ToData1() public{ data1 = data2;//成员变量相互赋值 } //成员变量本质上就是插槽,storage layout function refAndState_1() public{ int256p[] storage dataref = data1;//data1和dataref指向同一个数据块(插槽) //下面这条语句是引用赋值: data2和dataref指向同一个数据块(插槽) dataref = data2; } //成员变量本质上就是插槽,storage layout function refAndState_2() public{ int256p[] storage dataref = data2;//data2和dataref指向同一个数据块(插槽) //下面这条语句是值拷贝 data1 = dataref; } function calldata_ref_right(string calldata name) public{ string calldata temp = name;//引用拷贝 name = temp; } function calldata_copy_wrong(string calldata name) public { //calldata不能成为值拷贝目标 //以下内容报错 //string memory temp = name;//值拷贝 //name = temp; //报错内容:Type string memory is not implicitly convertible to expected type string calldata. } function valueCopy() public{//值类型的数据之间赋值: 只能是值拷贝 uint v1 = 1; uint v2 = 2; v1 = v2; } function refCopy() public{//引用类型的数据之间赋值: //定义一个引用变量,必须指明他的location,否则报错,例如 //string x; ===> 这会报错,应该这么写 string memory x;//或string calldata x;或string storage x; //假如location写成calldata,那么这个变量x就是指向消息调用calldata的一部分calldata //注意:location的calldata指的是数据类型的,而消息调用的数据叫做calldata,两者不同 } function x_1() public{ data1 = data2;//值拷贝 } function x_2() public{ int256[] memory temp = new int256[](6); data1 = temp;//值拷贝 } function x_3() public{ int256[] memory temp = new int256[](6); temp1 = data2;//值拷贝 } function x_4() public{ int256[] storage temp = data1; temp = data2;//引用拷贝 } } |
例子1
初始状态下,调用insertData1()和insertData2()向data1和data2插入两个数据,结果:getData1()返回[1,1],getData2()返回[2,2]
调用setData2ToData1(),结果:getData1()返回[2,2],getData2()返回[2,2]
调用insertData1()向data1插入数据1
结果:getData1()返回[2,2,1],getData2()返回[2,2]
调用insertData2()向data2插入数据2
结果:getData1()返回[2,2,1],getData2()返回[2,2,2]
结论:storage存储位置的变量之间赋值的时候是值拷贝
例子2
初始状态下,调用insertData1()和insertData2()向data1和data2插入两个数据,结果:getData1()返回[1,1],getData2()返回[2,2]
调用refAndState_2(),结果:getData1()返回[2,2],getData2()返回[2,2]
文章来源: http://mp.weixin.qq.com/s?__biz=MzIzMTc1MjExOQ==&mid=2247508764&idx=1&sn=bc79ec8e82f9d7de14f17ddac110ad78&chksm=e89d8bc4dfea02d2b0f004aace3f1d8f9913d24c598461699722f914f4b3f56362da83fe1491#rd
如有侵权请联系:admin#unsafe.sh