存儲位置與拷貝機制: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日
    6100
  • Web3 概述與願景

    Web3 概述與願景 學習目標 理解 Web1、Web2、Web3 的演進歷程與核心區別 掌握 Web3 的核心理念:去中心化、數據確權、用戶主權 瞭解 Web3 帶來的創新機會與全新商業模式 熟悉 Web3 開發者的學習路線圖 前置知識 基本的互聯網使用經驗 對軟件開發有初步瞭解(非必須,但有助於理解技術部分) 一、Web1 → Web2 → Web3 的…

    Web3與WASM 2026年3月10日
    5900
簡體中文 繁體中文 English