存储位置与拷贝机制:storage、memory、calldata

存储位置与拷贝机制:storage、memory、calldata

学习目标

理解 EVM 中三种数据存储位置的特点,以及引用类型在不同存储位置之间赋值时的拷贝规则。

前置知识

已学习值类型和引用类型(数组、结构体、映射、字符串)。


三种存储位置

storage —— 持久化存储

  • 类似数据库,数据永久保存在区块链
  • 成员变量(状态变量)默认存储在 storage
  • 读写 gas 成本最高

memory —— 临时内存

  • 函数执行期间存在,函数返回后销毁
  • 局部变量默认存储在 memory
  • gas 成本适中

calldata —— 调用数据

  • 来自 transaction 的 msg.data只读
  • 不可修改,gas 成本最低
  • 适用于 external 函数的参数

存储位置对引用类型的影响

关键规则:

  • 不同 location 的同一引用类型,编译器视为不同类型
  • public / external 函数参数只能是 memorycalldata
  • internal / private 函数参数可以是 storage

综合示例

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

contract DataLocations {
    struct MyStruct {
        uint256 foo;
        string text;
    }

    mapping(address => MyStruct) myStructs;

    function examples(uint[] calldata y, string calldata s) external returns (uint[] memory) {
        myStructs[msg.sender] = MyStruct({foo: 123, text: "bar"});

        // storage 引用:修改会持久化
        MyStruct storage myStruct = myStructs[msg.sender];
        myStruct.text = "foo";

        // memory 副本:修改不影响存储
        MyStruct memory readOnly = myStructs[msg.sender];
        readOnly.foo = 456; // 不会影响 myStructs

        _internal(y);

        uint[] memory memArr = new uint[](3);
        memArr[0] = 234;
        return memArr;
    }

    function _internal(uint[] calldata y) private pure {
        uint x = y[0];
    }
}

值拷贝 vs 引用拷贝

这是 Solidity 中最容易混淆的知识点之一,务必反复理解。

核心概念:成员变量的特殊性

在 EVM 中,成员变量(状态变量)指向固定的 storage 数据块(slot),不能像一般引用变量那样切换指向的数据块。因此对成员变量赋值,只能是值拷贝

判定算法

对于赋值操作 x = a,按以下规则判定是值拷贝还是引用拷贝:

1. 如果 x 是成员变量 → 值拷贝
2. 如果 x 是局部变量:
   - x 与 a 的 location 相同 → 引用拷贝
   - x 与 a 的 location 不同 → 值拷贝

检查算法

当判定为值拷贝时,编译器还会进行以下检查:

1. 检查类型中是否包含 mapping → 有则编译错误(mapping 不支持拷贝)
2. 检查 x 是否为 calldata → 是则编译错误(calldata 只读)
3. 通过检查 → 执行值拷贝

一句话总结

当赋值被解释为引用拷贝时,如果不与更高优先级的设计选择相冲突,则为引用拷贝;否则为值拷贝。

常见场景速查

赋值场景 拷贝类型 说明
成员变量 = storage引用 值拷贝 成员变量始终值拷贝
成员变量 = memory变量 值拷贝 成员变量始终值拷贝
storage局部变量 = 成员变量 引用拷贝 同为 storage,指向同一数据
memory局部变量 = memory变量 引用拷贝 同为 memory,指向同一数据
memory局部变量 = storage变量 值拷贝 不同 location
storage局部变量 = memory变量 编译错误 storage 局部变量只能引用已有 storage 数据

关于默认值与初始化

  • 成员变量:自动初始化为默认值(uint → 0,bool → false,address → 0x0 等)
  • memory 局部变量:自动初始化为默认值
  • storage 局部变量必须经过赋值才能使用,不会自动初始化

历史安全漏洞:早期 Solidity 版本中,未赋值的 storage 局部变量会默认指向 slot 0,可能意外覆盖其他状态变量的数据,造成严重安全问题。现代编译器已修复此问题。


完整 storage 交互示例

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

contract DataLocations {
    uint[] public arr;
    mapping(uint => address) map;
    struct MyStruct {
        uint foo;
    }
    mapping(uint => MyStruct) myStructs;

    function f() public {
        // 将 storage 变量传递给 internal 函数
        _f(arr, map, myStructs[1]);

        // storage 局部变量:引用拷贝,指向 myStructs[1]
        MyStruct storage myStruct = myStructs[1];

        // memory 局部变量:独立副本
        MyStruct memory myMemStruct = MyStruct(0);
    }

    function _f(
        uint[] storage _arr,
        mapping(uint => address) storage _map,
        MyStruct storage _myStruct
    ) internal {
        // 操作 storage 引用,修改会持久化
    }

    function g(uint[] memory _arr) public returns (uint[] memory) {
        // 操作 memory 数组,函数返回后销毁
    }

    function h(uint[] calldata _arr) external {
        // 操作 calldata 数组(只读,gas 更低)
    }
}

小结

存储位置 生命周期 可写 Gas 成本 典型用途
storage 永久 最高 状态变量
memory 函数执行期间 中等 局部变量、函数参数
calldata 函数执行期间 最低 external 函数参数

拷贝规则核心

  • 赋值给成员变量 → 始终值拷贝
  • 局部变量间赋值 → 同 location 引用拷贝,不同 location 值拷贝
  • mapping 不可值拷贝,calldata 不可写入

上一篇引用类型详解

主题测试文章,只做测试使用。发布者:Walker,转转请注明出处:https://walker-learn.xyz/archives/7490

(0)
Walker的头像Walker
上一篇 5天前
下一篇 2025年3月27日 15:01

相关推荐

  • 函数定义与访问控制

    函数定义与访问控制 学习目标 掌握 Solidity 函数定义、可见性修饰符、交易属性、modifier 和构造函数。 函数定义 一般形式 function fname([参数]) [可见性][交易属性][modifier...] returns(返回值) { ... } 函数签名:fname([参数]) —— 唯一标识一个函数 返回值:returns(返回…

    Web3与WASM 23小时前
    100
  • Solidity 值类型详解

    Solidity 值类型详解 学习目标 掌握 Solidity 的整型、布尔、地址、定长字节数组、枚举等值类型 理解 EVM 256 位机器架构对类型设计的影响 掌握类型转换规则(隐式 vs 显式) 了解溢出问题的历史与解决方案 理解 Solidity 中所有类型的默认值机制 一、整型(int / uint) 1.1 基本概念 Solidity 提供有符号整…

  • Web3 概述与愿景

    Web3 概述与愿景 学习目标 理解 Web1、Web2、Web3 的演进历程与核心区别 掌握 Web3 的核心理念:去中心化、数据确权、用户主权 了解 Web3 带来的创新机会与全新商业模式 熟悉 Web3 开发者的学习路线图 前置知识 基本的互联网使用经验 对软件开发有初步了解(非必须,但有助于理解技术部分) 一、Web1 → Web2 → Web3 的…

    Web3与WASM 17小时前
    400
  • Gas机制与转账设计

    Gas机制与转账设计 学习目标 理解区块链的经济模型与激励机制 掌握 Gas、Gas Price、Gas Fee 的概念与关系 理解 Ether 单位与转换 掌握合约转账设计(receive / fallback / payable) 区块链的经济系统 为什么需要经济模型? 计算与存储资源是稀缺的:每个节点都要执行和存储所有交易 共识和 trustless …

    20小时前
    100
  • Solidity 入门与开发环境

    Solidity 入门与开发环境 学习目标 理解智能合约的本质与核心特性 掌握合约在以太坊上的运行原理(Transaction + EVM) 认识 Solidity 语言特点与开发工具链 编写并部署第一个智能合约 前置知识 了解区块链基本概念(区块、交易、共识) 了解以太坊账户模型(EOA 与合约账户) 基本编程经验(任意语言均可) 一、智能合约的根本性质 …

    1天前
    900
简体中文 繁体中文 English