目录

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