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。

相关推荐
MQLYES6 小时前
09-BTC-比特币脚本
区块链
CryptoPP6 小时前
主流国际股票行情API接口横向对比:如何选择适合你的数据源?
大数据·笔记·金融·区块链
公链开发8 小时前
2026 GameFi开发实战:从“玩法优先”到香港合规落地,一套可持续链游技术方案
区块链
CryptoRzz8 小时前
如何高效对接美股实时行情?StockTV API 实战集成指南
java·python·flask·区块链·maven·symfony
终端域名9 小时前
虚拟货币域名:加密货币与域名的碰撞 —— 构建未来互联网新生态
microsoft·区块链·加密货币·数字货币域名·网站域名
草原猫11 小时前
什么是 DAPP开发?DAPP 开发到底难不难?
区块链·dapp开发
终端域名11 小时前
如何保障网络架构变革下物联网设备的安全?
网络·物联网·架构·区块链
终端域名12 小时前
移动互联时代物联网的未来发展趋势:从连接到智能的全面跃迁
物联网·区块链
China_Yanhy12 小时前
Web3 的数字金库:AWS KMS 在区块链项目中的深度应用与选型指南
web3·区块链·aws
终端域名12 小时前
万物智联时代下域名的标识价值:从地址入口到数字生态枢纽
区块链