存储位置

基础

默认数据位置

状态变量默认storage
内部函数参数默认memory
外部函数参数默认calldata

例如

pragma solidity ^0.8.0;
contract SimpleAssign{ 
    // storage 类型
    struct S{
        string a;
        uint b;
    }
    function assign(S s) internal{ 
        // 函数参数默认是 memory 类型,即 s 是 memory 类型
        // 局部变量默认 storage 类型
        // 将一个memory类型的参数赋值给storage类型的局部变量会报错。
        S tmp = s; // 报错
  }
}

值转换

storage–>storage

把一个storage类型赋值给一个 storage 类型时,只是修改其指针(引用传递)。

pragma solidity ^0.8.0;
contract StorageToStorageTest{
    struct S{string a;uint b;}

    //默认是storage的
    S s;
    function storageTest(S storage s) internal{
        S test = s;
        test.a = "Test";
    }

    function call() returns (string){
        storageTest(s);
        return s.a; //Test
    }
}

memory–>storage

分为 2 种情况: a. 将 memory–>状态变量; 即将内存中的变量拷贝到存储中(值传递)

pragma solidity ^0.8.0;
contract StorageToStorageTest{
    struct S{string a;uint b;}

    //默认是storage
    S s;
    function storageTest(S s) internal{
        s = s; 
        s.a = "Test";
    }

    function call() returns (string){
        storageTest(s);
        return s.a;
    }
}

b.将memeory–>局部变量 报错

storage–>memory

即将数据从storage拷贝到memory中

  pragma solidity ^0.4.0;
  contract StorageToMemory{
      
  struct S{string a;uint b;}
  // storage 类型
  S s = S("storage", 1);
  
  function storageToMemory(S storage x) internal{
     S memory tmp = x;//由Storage拷贝到memory中
     //memory的修改不影响storage
     tmp.a = "Test";
  }

  function call() returns (string){
    storageToMemory(s);
    return s.a;//storage
  }
}

memory–>memory

和storage转storage一样是引用传递

  pragma solidity ^0.4.0;
  contract MemoryToMemory{
      struct S{string a;uint b;}  
      function smemoryTest(S s) internal{
            S memory test = s;
            test.a = "Test";
      }
      function call() returns (string){
           //默认是storage的
            S memory s = S("memory",10);
            smemoryTest(s);
            return s.a;//Test
       }
  }

Memory

概述

存储在内存中,即分配、即使用,越过作用域则不可访问,等待被回收。 内存是易失性读写字节可寻址空间。它主要用于在执行过程中存储数据,主要用于向内部函数传递参数。鉴于这是易失性区域,每个消息调用都以清除内存开始。所有位置最初都定义为零。作为calldata,内存可以按字节级别寻址,但一次只能读取32字节的字。 当我们写入以前未使用过的单词时,内存被称为“扩展”。除了写入本身的成本之外,这种扩展也有成本,前 724 字节呈线性增长,之后呈二次方增长。

特点

存储在区块链
状态变量
复杂类型的局部变量
Storage对应合约内的存储。
你可以把你不想被cloned的变量作为Storage存储。
Storage是长期有效的,但是成本比较高。

操作码

EVM 提供了三个操作码来与内存区域进行交互

MLOAD 从内存中加载一个单词到堆栈中。
MSTORE 将单词保存到内存中。
MSTORE8 将一个字节保存到内存中。

Calldata

概述

存储函数参数,它是只读的,不会永久存储的一个数据位置。外部函数的参数被强制指定为 calldata,效果与 memory 类似。

特点

calldata几乎是免费的,但是有一个长度的限制。
calldata 是一个只读的字节可寻址空间,其中保存了事务或调用的数据参数。与堆栈不同,要使用此数据,您必须指定确切的字节偏移量和要读取的字节数。

操作码

EVM 提供的用于操作调用数据的操作码

CALLDATASIZE 告诉交易数据的大小。
CALLDATALOAD 将 32 字节的事务数据加载到堆栈中。
CALLDATACOPY 将一定数量的交易数据字节复制到内存中。

常用操作

Memory变量

Memory变量临时变量(内存型),合约函数执行完成时,内存型变量被移除。 在函数参数或返回值声明时,如果返回数据的类型是变长的,那么需要加memory修饰,例如

string、
bytes、
数组、
自定义结构

等类型都需要使用memory。

function setUser(string memory _name, uint8 _age, string memory _sex) public {}

storage变量

storage的作用类似于C++中的引用传递,用storage修饰的变量等同于右值的一个分身,对其进行修改也会影响到本尊。

区别

如果数组中的结构体赋值给函数中声明的变量,使用memory修饰获得的是一个副本,也就是拷贝

function edit(uint _id) public {
//这里获取的是 一份拷贝,下面的修改不会影响数组里的结构体
Person memory p = persons[_id];
p.name = "test";
p.age = 0;
//执行结束后 persons[_id]里的name 和age并不会改变
}

如果使用storage修饰变量,那么得到的是一个引用,就是p 是指向 persons[_id] 的指针。例如

function edit(uint _id) public {
Person storage p = persons[_id];
//这里会改变数组里的结构体
p.name = "test";
p.age = 0;
//执行结束后,persons[_id]的name和age分别变为 "test"和0
}

Last updated