Solidity 数据位置(data location)

1. 引用类型/复合数据类型

Solidity中,有一些数据类型由简单数据类型组合而成,相比于简单的值类型,这些类型通常通过名称引用,被称为引用类型,

引用类型包括:

  • 数组 (字符串与bytes是特殊的数组,所以也是引用类型)
  • struct (结构体)
  • map (映射)

这些类型涉及到的数据量较大,复制它们可能要消耗大量Gas,非常昂贵,所以使用它们时,必须考虑存储位置。例如,是保存在内存中,还是在 EVM 存储区中。

2. 数据位置(data location)

在合约中声明和使用的变量都有一个数据位置,指明变量值应该存储在哪里。合约变量的数据位置将会影响Gas消耗量。

Solidity 提供 3 种类型的数据位置。

  • storage
  • memory
  • calldata

1) storage

存储位置 storage 用来存储永久数据,可以被合约中的所有函数访问。

storage 变量,通常用于存储智能合约的状态变量,它在函数调用之间保持持久性。

storage 与其他数据位置相比,成本较高。

我们可以把它理解为计算机的硬盘数据,所有数据都永久存储。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SoldityTest {
   struct MyData {
      uint id;
      string value;
   }
   MyData[] public myData;

   constructor(){
      myData.push(MyData(1, "value1"));
      myData.push(MyData(2, "value2"));
   }

   function operations() external{
      // storage 存储位置
      MyData storage d = myData[0];

      // 修改状态变量myData的内容
      d.value = "new value"; 
   }
}

合约 SoldityTest 部署后,调用 operations 方法,状态变量 myData 的数据被修改,其中 myData[0].value 变为 "new value"。

2) memory

存储位置 memory 用来存储临时数据,比 storage 便宜。

memory 变量,通常用于保存临时变量,以便在函数执行期间进行计算。一旦函数执行完毕,它的内容就会被丢弃。它只能在所在的函数中访问。

我们可以把它理解为每个单独函数的内存 RAM。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SoldityTest {
   struct MyData {
      uint id;
      string value;
   }
   MyData[] public myData;

   constructor(){
      myData.push(MyData(1, "value1"));
      myData.push(MyData(2, "value2"));
   }
   
   function operations() external{
      // memory 存储位置
      MyData memory d = myData[0];

      // 修改状态变量myData的内容
      d.value = "new value"; 
   }
}

合约 SoldityTest 部署后,调用 operations 方法,状态变量 myData 的数据未被修改,其中 myData[0].value 仍然为 "value1"。

3) calldata

calldata 是不可修改的非持久性数据位置,类似于 memory,但只能出现在函数的输入参数位置。

外部函数 external function 的传入参数强制为 calldata 类型。

使用 calldata 的好处是在内部函数中作为输入参数传递时,省掉了数据拷贝的成本,节省 gas 费。memory 作为参数时,需要进行数据拷贝。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SoldityTest {
   function operations(string calldata val) pure external{
      // 使用 calldata 传递参数,省却复制成本
      internalFunc1(val);

      // 使用 memory 传递参数,需要进行复制
      internalFunc2(val);
   }
   
   function internalFunc1(string calldata val) internal pure {
      // ......
   }

   function internalFunc2(string memory val) internal pure {
      // ......
   }
}

下一章:Solidity 变量的数据位置规则

 1. 规则1 – 状态变量状态变量总是存储在存储区中。// SPDX-License-Identifier: MITpragma solidity ^0.8.0;contract ...