solidity中数据位置storage、memory、calldata的区别

在 Solidity 中,storagememorycalldata 是三种关键的数据位置,它们决定了变量的存储位置、生命周期和行为。

1. storage(存储区)

  • 位置:永久存储在区块链上,是合约状态变量存储的位置
  • 生命周期:与合约生命周期相同,永久存储
  • 成本:读写成本高(写入消耗大量 gas,读取相对便宜)
  • 特点
    • 引用类型,传递的是引用(类似指针)

    • 修改会影响原始数据

    • 用于状态变量

      contract Example {
      uint[] public arr; // storage变量

      复制代码
      function storageExample() public {
          uint[] storage s = arr;  // storage引用
          s.push(1);  // 直接修改arr
      }

      }

2. memory(内存区)

  • 位置:临时存储在内存中,函数执行期间存在
  • 生命周期:仅在函数执行期间存在
  • 成本:读写成本低,但大型数组可能需要较多 gas
  • 特点
    • 值类型,传递的是副本(基本类型)或新内存分配(引用类型)

    • 修改不会影响原始数据

    • 必须用于函数内的局部变量(引用类型)

      function memoryExample(uint[] memory input) public pure returns (uint[] memory) {
      uint[] memory localArr = new uint; // 在内存中创建新数组
      localArr[0] = input[0]; // 复制值
      return localArr; // 返回内存数组
      }

3. calldata(调用数据区)

  • 位置:只读的调用数据,存储函数参数
  • 生命周期:仅在函数调用期间存在
  • 成本:只读,gas 成本最低
  • 特点
    • 不可修改的只读区域

    • 通常用于外部函数(external)的参数

    • 避免不必要的复制,节省 gas

      function calldataExample(bytes calldata data) external pure returns (bytes memory) {
      // data[0] = 0x01; // 错误!calldata不可修改
      bytes memory copy = data; // 可以复制到memory进行修改
      return data; // 可以直接返回(需要转为memory)
      }

对比表格

特性 storage memory calldata
存储位置 区块链 临时内存 调用数据
生命周期 永久 函数执行期间 函数调用期间
可修改性 可读写 可读写 只读
传递方式 引用 值/副本 引用
Gas 成本 中等
适用场景 状态变量 函数内部变量 外部函数参数

使用规则和最佳实践

1. 参数数据位置规则

复制代码
// public/internal函数:参数默认memory
function func1(uint[] memory arr) public {}

// external函数:引用类型参数默认calldata
function func2(uint[] calldata arr) external {}

// 错误:external函数不能使用memory参数(但新版本已允许)

2. 返回值数据位置

复制代码
function returnsMemory() public pure returns (uint[] memory) {
    uint[] memory arr = new uint[](3);
    return arr;  // 返回memory数据
}

function returnsStorage() public view returns (uint[] storage) {
    return arr;  // 返回storage引用
}

3. 赋值规则

复制代码
uint[] storage s = arr;      // storage → storage ✓
uint[] memory m = arr;       // storage → memory ✓(复制)
uint[] memory m2 = m;        // memory → memory ✓(复制)
// uint[] storage s2 = m;    // memory → storage ✗(不允许)
// uint[] calldata c = arr;  // storage → calldata ✗

4. Gas 优化技巧

复制代码
// 使用calldata节省gas(避免复制)
function processData(bytes calldata data) external {
    // 直接读取calldata,不复制到memory
    require(data.length > 0);
}

// 需要修改时再复制到memory
function modifyData(bytes calldata data) external {
    bytes memory modified = data;  // 复制到memory
    modified[0] = 0x01;           // 进行修改
}

常见错误

复制代码
contract CommonErrors {
    uint[] public arr;
    
    // 错误:未指定数据位置
    // function error1(uint[] a) public {}
    
    // 错误:尝试修改calldata
    // function error2(uint[] calldata a) external {
    //     a[0] = 1;  // 编译错误
    // }
    
    // 正确:明确指定数据位置
    function correct(uint[] memory a) public {
        uint[] memory local = a;  // 复制到memory
        local[0] = 1;            // 可以修改
    }
}

总结

  1. storage:用于永久存储的状态变量
  2. memory:用于函数内部的临时变量,可修改
  3. calldata:用于外部函数参数,只读,最节省 gas

选择合适的数据位置对于优化 Gas 消耗和确保合约正确性至关重要。通常:

  • 外部函数参数使用 calldata
  • 内部函数参数和局部变量使用 memory
  • 需要修改状态时使用 storage 引用

注:

强制数据位置类型:

• 可见性为external的函数入参的数据类型(Data Location)必须是calldata类型。

•状态变量的数据位置(Data Location)类型必须是storage类型。​

默认数据类型:

• 一个函数的入参和出参的数据位置(Data Location)类型默认是memory类型。​(external函数的入参除外,因为强制为calldata类型)

• 除了入参和出参之外的所有局部变量的数据位置(Data Location)类型默认为storage。

相关推荐
许强0xq7 小时前
订单流战争:AI、区块链与市场透明度的终极博弈
web3·区块链·智能合约·solidity·dapp
BlockChain8887 小时前
以太坊交易全流程详解
区块链
weixin_413838567 小时前
基于区块链的校园二手书交易系统
vue.js·spring·区块链·fabric
f16422547 小时前
区块链入门知识
区块链
是小蟹呀^8 小时前
【总结】LangChain中如何维持记忆
python·langchain·memory
迷藏4941 天前
**发散创新:基于Solid协议的Web3.0去中心化身份认证系统实战解析**在Web3.
java·python·web3·去中心化·区块链
CIO_Alliance1 天前
区块链技术在iPaaS系统集成中的应用
区块链·ipaas·系统集成·制造业·企业数智化转型·零售电商
The_Ticker2 天前
印度股票实时行情API(低成本方案)
python·websocket·算法·金融·区块链
苏慕白的博客2 天前
精度套利:藏在盘口缝隙里的稳定机会
区块链·量化·高频量化·套利
Tattoo_Welkin3 天前
【RWA 机制,ERC-4626,ERC-3643,ERC-7540,ERC-7575,LayerZero】
区块链