存儲位置與拷貝機制: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