存储位置与拷贝机制: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
上一篇 2026年3月8日 15:11
下一篇 2026年3月21日 01:05

相关推荐

  • 引用类型详解:数组、结构体、映射、字符串

    引用类型详解:数组、结构体、映射、字符串 学习目标 掌握 Solidity 中四种引用类型(数组、结构体、映射、字符串/变长字节数组)的定义和使用方法。 前置知识 已学习值类型(整型、布尔、地址、定长字节数组等)。 数组(Array) storage 中的数组 Solidity 中数组分为两种: 静态数组 T[K]:长度固定,编译时确定 动态数组 T[]:长…

    Web3与WASM 2026年3月10日
    8100
  • Delegatecall 与代理模式

    Delegatecall 与代理模式 学习目标 理解 delegatecall 的工作原理 掌握 Storage Layout(存储布局)规则 掌握代理模式与合约升级 了解非结构化存储(Unstructured Storage) delegatecall 原理 什么是 delegatecall 语法:address.delegatecall(bytes ca…

    2026年3月10日
    4700
  • Gas机制与转账设计

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

    2026年3月10日
    5900
  • 合约交互与 ABI

    合约交互与 ABI 学习目标 掌握合约间调用方式、接口定义、ABI 数据结构、Web3.js 访问合约的方法。 合约间调用基础 EOA(外部账号)发起调用,可能触发合约间的调用链 调用者必须持有被调用合约的地址 方式一:同文件内直接调用 当两个合约在同一个文件中时,可以直接通过合约类型和地址进行调用: // SPDX-License-Identifier: …

    2026年3月10日
    6200
  • Web3 概述与愿景

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

    Web3与WASM 2026年3月10日
    5900
简体中文 繁体中文 English