目录
calldata
memory
storage
三者之间的转换
storage作为参数,赋值到memory
(1)
(2)
(3)
storage作为参数,赋值给storage
memory作为参数,赋值给memory
memory作为参数,赋值给storage
calldata
官方文档对calldata的描述:
Calldata is a non-modifiable, non-persistent area where function arguments are stored, and behaves mostly like memory.
翻译:Calldata是一个不可修改的、非持久化的区域,函数参数存储在这里,其行为主要类似于内存。
-
它只能用于函数声明参数(而不是函数逻辑) -
它是不可变的(不能被覆盖和更改),调用数据避免了数据拷贝,并确保数据不被修改 -
它必须用于external函数的动态参数 -
它是临时的(该值在事务完成后会销毁) -
它是最便宜的存储位置,一般建议将函数参数声明为calldata,因为gas费会比较低。 -
是const -
外部函数的参数(不包括返回参数)被强制指定为calldata
memory
简介:在合约中的本地内存变量。它的生命周期很短,当函数执行结束后就销毁了
- 内存是一个字节数组,内存槽为256位(32字节)
- 数据仅在函数执行期间存在,执行完毕后就被销毁,读或写一个内存槽都会消耗3gas
- 为了避免矿工的工作量过大,22个操作之后的单操作成本会上涨
storage
简介:在合约中可以被所有函数访问的全局变量。storage是永久的存储,意味着以太坊会把它保存到公链环境里的每一个节点
- 存储中的数据是永久存在的。存储是一个key/value库- 存储中的数据写入区块链,因此会修改状态,这也是存储使用成本高的原因。
- 占用一个256位的存储槽需要消耗20000 gas,
- 修改一个已经使用的存储槽的值,需要消耗5000 gas,当清零一个存储槽时,会返还一定数量的gas,
- 存储按256位的槽位分配,即使没有完全使用一个槽位,也需要支付其开销
三者之间的转换
storage作为参数,赋值到memory
(1)
pragma solidity ^0.4.24;
contract Person {
int public _age;
constructor (int age) public {
_age = age;
}
function f() public view{
modifyAge(_age);
}
function modifyAge(int age) public pure{
age = 100;
}
}
- 分析
- 在这里一开始deploy合约时,传入的age值为30,此时_age的值为30
- 然后运行f()函数,在这里使用了为storage类型的_age作为函数modifyAge的参数,相当于创建了一个临时变量age(memory类型),将storage类型的变量_age赋值给memory类型的变量age,是值传递,所以在modifyAge函数中,age变量的值的变化并不会影响到_age变量的值
- 所以再查看_age的值,还是为30
(2)
pragma solidity ^0.4.24;
contract Person {
string public _name;
constructor() public {
_name = "chenqin";
}
function f() public view{
modifyName(_name);
}
function modifyName(string name) public pure{
string memory name1 = name;
bytes(name1)[0] = 'L';
}
}
- 分析
- 在这里一开始deploy合约时,设置的_name为"chenqin"
- 然后,调用f()函数,将storage类型的状态变量_name作为参数,赋值给函数modifyName函数的memory类型的name,为值传递
- 之后,在modifyName函数中,还将memory类型的name赋值给memory类型的name1,为引用传递!改变一个另一个也跟着改变,
- 最后,因为先是进行了值传递,name与_name之间已经互不影响了,所以不会跟着改变_name。(标记)处的代码并不会修改_name的值
- 因此,不管如何以上函数,_name始终为chenqin
(3)
pragma solidity ^0.4.24;
contract Person {
string public _name;
string public changedName;
constructor() public {
_name = "chenqin";
}
function f() public{//不能声明为view,因为改变了状态变量
modifyName(_name);
}
function modifyName(string name) public{//不能声明为view,因为改变了状态变量
changedName = name;
bytes(name)[0] = 'L';
}
}
- 分析
- 调用f()函数,将storage类型的状态变量_name作为参数,赋值给函数modifyName(string) memory类型的name形参,为值传递
- 然后,memory类型的name作为形参,赋值给storage类型的状态变量changedName,为值传递
- 因此,(标记)的那行代码,name的值的改变不会导致changedName的值的改变,更不要说_name了
- 调用f函数,最终的结果是:_name=chenqin,changeName=chenqin
storage作为参数,赋值给storage
pragma solidity ^0.4.24;
contract Person {
string public _name;
constructor() public {
_name = "chenqin";
}
function f() public{
modifyName(_name);
}
function modifyName(string storage name) internal {
string storage name1 = name;
bytes(name1)[0] = 'L';
}
}
PS:如果modifyName函数不声明为internal会报错:这是因为形参是默认为memory类型的,这里声明为storage,那么函数的类型就必须声明为internal或者private
- 分析
- 调用f()函数,首先会将为storage类型的_name变量,赋值给modifyName函数storage类型的name,为引用传递
- 然后在modifyName函数中,将storage类型的name变量,赋值给storage类型的name1变量,为引用传递
- 都为引用传递,所以最后name1值的变化会导致_name的值的变化
- 调用f函数前:_name=chenqin。调用f函数后:_name=Lhenqin
引申:其实在这里如果将modifyName(string)函数改成如下,也是能够成功的,因为其实没必要进行两次引用传递
function modifyName(string storage name) internal {
bytes(name)[0] = 'L';
}
memory作为参数,赋值给memory
pragma solidity ^0.4.24;
contract Person {
function modifyName(string name) public pure returns(string){
string memory name1 = name;
bytes(name1)[0] = 'L';
return name;
}
}
- 分析
- 这里调用modifyName函数,将memory类型的name,赋值给memory类型的name1,为引用传递
- 这时候改变name1的值,它的值也随之改变
memory作为参数,赋值给storage
pragma solidity ^0.4.24;
contract Person {
string public _name;
constructor() public {
_name = "chenqin";
}
function f(string name) public{
_name = name//(x)
name = "ikun"(y)
}
}
- 分析
- 调用f函数,将memory类型的name,赋值给storage类型的_name,为值传递
- (x)处_name的值会被修改成name,然后不再随name的改变而改变,即(y)处代码对_name无影响。
- f函数执行完的结果还是:_name=chenqin
|