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。

相关推荐
公链开发3 小时前
交易所系统平台开发流程及达普韦伯核心优势
区块链
一点 内容7 小时前
深入理解分布式共识算法 Raft:从原理到实践
分布式·区块链·共识算法
ZFJ_张福杰7 小时前
【区块链】区块链智能合约:从原理到应用的完整入门指南
区块链·智能合约
tg-zm8899968 小时前
在ST5交易系统中,有哪些常用的技术指标来辅助风险管理
大数据·人工智能·区块链
数据皮皮侠9 小时前
上市公司战略性新兴产业专利数据库(2003-2024)
数据库·人工智能·信息可视化·区块链·微信开放平台
_oP_i11 小时前
区块链系统设计
区块链
Web3VentureView12 小时前
从“庞氏骗局”到“价值发现”:Web3 行业自我修正与新范式的曙光
大数据·金融·web3·去中心化·区块链
古城小栈13 小时前
Go语言 赋能 区块链 开发
开发语言·golang·区块链
币圈菜头1 天前
GAEA × REVOX 合作 — 共建「情感 AI + Web3 应用」新生态
人工智能·web3·去中心化·区块链