• Solidity中的calldata,storage,memory


    目录

    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)

    1. pragma solidity ^0.4.24;
    2. contract Person {
    3. int public _age;
    4. constructor (int age) public {
    5. _age = age;
    6. }
    7. function f() public view{
    8. modifyAge(_age);
    9. }
    10. function modifyAge(int age) public pure{
    11. age = 100;
    12. }
    13. }
    • 分析
      • 在这里一开始deploy合约时,传入的age值为30,此时_age的值为30
      • 然后运行f()函数,在这里使用了为storage类型的_age作为函数modifyAge的参数,相当于创建了一个临时变量age(memory类型),将storage类型的变量_age赋值给memory类型的变量age,是值传递,所以在modifyAge函数中,age变量的值的变化并不会影响到_age变量的值
      • 所以再查看_age的值,还是为30

    (2)

    1. pragma solidity ^0.4.24;
    2. contract Person {
    3. string public _name;
    4. constructor() public {
    5. _name = "chenqin";
    6. }
    7. function f() public view{
    8. modifyName(_name);
    9. }
    10. function modifyName(string name) public pure{
    11. string memory name1 = name;
    12. bytes(name1)[0] = 'L';
    13. }
    14. }
    • 分析
      • 在这里一开始deploy合约时,设置的_name为"chenqin"
      • 然后,调用f()函数,将storage类型的状态变量_name作为参数,赋值给函数modifyName函数的memory类型的name,为值传递
      • 之后,在modifyName函数中,还将memory类型的name赋值给memory类型的name1,为引用传递!改变一个另一个也跟着改变,
      • 最后,因为先是进行了值传递,name与_name之间已经互不影响了,所以不会跟着改变_name。(标记)处的代码并不会修改_name的值
      • 因此,不管如何以上函数,_name始终为chenqin

    (3)

    1. pragma solidity ^0.4.24;
    2. contract Person {
    3. string public _name;
    4. string public changedName;
    5. constructor() public {
    6. _name = "chenqin";
    7. }
    8. function f() public{//不能声明为view,因为改变了状态变量
    9. modifyName(_name);
    10. }
    11. function modifyName(string name) public{//不能声明为view,因为改变了状态变量
    12. changedName = name;
    13. bytes(name)[0] = 'L';
    14. }
    15. }
    • 分析
      • 调用f()函数,将storage类型的状态变量_name作为参数,赋值给函数modifyName(string) memory类型的name形参,为值传递
      • 然后,memory类型的name作为形参,赋值给storage类型的状态变量changedName,为值传递
      • 因此,(标记)的那行代码,name的值的改变不会导致changedName的值的改变,更不要说_name了
      • 调用f函数,最终的结果是:_name=chenqin,changeName=chenqin

    storage作为参数,赋值给storage

    1. pragma solidity ^0.4.24;
    2. contract Person {
    3. string public _name;
    4. constructor() public {
    5. _name = "chenqin";
    6. }
    7. function f() public{
    8. modifyName(_name);
    9. }
    10. function modifyName(string storage name) internal {
    11. string storage name1 = name;
    12. bytes(name1)[0] = 'L';
    13. }
    14. }

    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)函数改成如下,也是能够成功的,因为其实没必要进行两次引用传递

    1. function modifyName(string storage name) internal {
    2. bytes(name)[0] = 'L';
    3. }

    memory作为参数,赋值给memory

    1. pragma solidity ^0.4.24;
    2. contract Person {
    3. function modifyName(string name) public pure returns(string){
    4. string memory name1 = name;
    5. bytes(name1)[0] = 'L';
    6. return name;
    7. }
    8. }
    • 分析
      • 这里调用modifyName函数,将memory类型的name,赋值给memory类型的name1,为引用传递
      • 这时候改变name1的值,它的值也随之改变

    memory作为参数,赋值给storage

    1. pragma solidity ^0.4.24;
    2. contract Person {
    3. string public _name;
    4. constructor() public {
    5. _name = "chenqin";
    6. }
    7. function f(string name) public{
    8. _name = name//(x)
    9. name = "ikun"(y)
    10. }
    11. }
    • 分析
      • 调用f函数,将memory类型的name,赋值给storage类型的_name,为值传递
      • (x)处_name的值会被修改成name,然后不再随name的改变而改变,即(y)处代码对_name无影响。
      • f函数执行完的结果还是:_name=chenqin
  • 相关阅读:
    三大数据库 sequence 之华山论剑 (上篇)
    手写Spring-第十一章-用动态代理实现AOP核心功能
    Java的 super与this关键字图解
    java8 Lambda表达式以及Stream 流
    CompletableFuture方法介绍及代码示例
    第三次笔记:算术逻辑单元 电路基本原理 加法器的设计 一位全加器 串行进位加法器 并行进位加法器 补码加减运算器 无符号整数加减法 标志位的生成
    使用tkinter开发GUI程序6 -- 事件响应
    如何避免大语言模型绕过知识库乱答的情况?LlamaIndex 原理与应用简介
    [网鼎杯 2020 朱雀组]Nmap
    java递归获取所有的子级节点
  • 原文地址:https://blog.csdn.net/weixin_62775913/article/details/126690675