Solidity 数据存储布局

简介: 本文深入解析 Solidity 中 storage、memory 与 calldata 的区别与使用场景,通过代码示例与 Foundry 测试演示其行为差异,帮助开发者优化智能合约性能与安全性。

存储位置

生命周期

读写权限

读/写成本

应用场景

storage

合约永久状态变量

可读可写

读/写都昂贵

状态变量、长期持久化存储

memory

函数调用期间有效

可读可写

便宜(RAM式)

局部变量、中间临时处理

calldata

函数外部调用参数只读

只读

极便宜

external 函数的参数(特别适合数组)


二、状态变量(storage)

  • 所有 state variable 都自动存储在 storage 中。
  • 是 Solidity 最昂贵的存储类型,因为它对应 EVM 中的持久化状态存储(State Trie)。

代码语言:txt

AI代码解释

contract Example {
    uint[] public values; // 默认在 storage 中
    function update() public {
        values.push(1); // 直接修改 storage
    }
}
  • 可以通过 storage 引用函数参数或局部变量,但此时是指针引用

代码语言:txt

AI代码解释

function modify(uint[] storage arr) internal {
    arr[0] = 42; // 直接修改原始数组
}

三、memory 临时数据区

  • 用于函数中的临时变量和参数拷贝。
  • 生命周期仅在函数调用期间。
  • 在调用时创建,在返回时销毁。
  • 写入和读取比 storage 更便宜,但数据不会持久。

代码语言:txt

AI代码解释

function copy(uint[] memory data) public pure returns (uint) {
    data[0] = 123; // 修改的是 memory 中的副本
    return data[0];
}

使用 memory 时,变量是“值传递”或“拷贝引用”,不会影响 storage 中原始数据。


四、实验:storage vs memory 修改对比

代码语言:txt

AI代码解释

contract DataTest {
    uint[] public data;
    constructor() {
        data.push(1);
        data.push(2);
        data.push(3);
    }
    // 修改副本(不改变原始 storage)
    function updateMemory() public view returns (uint[] memory) {
        uint[] memory temp = data;
        temp[0] = 99;
        return temp; // 原始 data 不变
    }
    // 修改 storage 变量本身
    function updateStorage() public {
        uint[] storage temp = data;
        temp[0] = 88;
    }
}

Foundry 测试用例

代码语言:txt

AI代码解释

contract StorageTest is Test {
    DataTest dt;
    function setUp() public {
        dt = new DataTest();
    }
    function testUpdateMemoryDoesNotAffectStorage() public {
        uint[] memory result = dt.updateMemory();
        assertEq(result[0], 99);                // 复制体被改
        assertEq(dt.data(0), 1);                // storage 未改
    }
    function testUpdateStorageAffectsState() public {
        dt.updateStorage();
        assertEq(dt.data(0), 88);               // 状态变量被改变
    }
}

五、calldata 外部调用参数(只读)

  • 函数参数在 external 函数中默认使用 calldata
  • 它是最便宜的内存类型。
  • 只读,不可修改。aly.sascery.com55
  • 通常用于接收大量数组或字符串的函数。

代码语言:txt

AI代码解释

function readOnly(uint[] calldata data) external pure returns (uint) {
    // 编译失败
    data[0] = 10;
    return data[0]; // 不能修改 data
}

当你写 external 函数且带有数组参数时,推荐使用 calldata

代码语言:txt

AI代码解释

function sum(uint[] calldata nums) external pure returns (uint) {
    return nums[0] + nums[1];
}

优势:

  • 不能修改,更安全
  • gas 成本更低(比 memory 少一次复制)

不支持写入或 push/pop:aly.techxar.com66代码语言:txt

AI代码解释

// 编译失败
function fail(uint[] calldata nums) external {
    nums[0] = 1; // 不能修改 calldata
}

六、测试演示(Foundry 示例)

src/DataLocation.sol:

代码语言:txt

AI代码解释

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract DataLocation {
    uint[] public store;
    function storeToMemory() public view returns (uint[] memory) {
        uint[] memory temp = new uint[](store.length);
        for (uint i = 0; i < store.length; i++) {
            temp[i] = store[i];
        }
        return temp;
    }
    function storeFromCalldata(uint[] calldata input) external {
        store = input; // 拷贝 calldata 到 storage
    }
    function getCalldata(uint[] calldata input) external pure returns (uint) {
        return input[0]; // 只能读,不能写
    }
}

test/DataLocation.t.sol:

代码语言:txt

AI代码解释

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "../src/DataLocation.sol";
contract DataLocationTest is Test {
    DataLocation dl;
    function setUp() public {
        dl = new DataLocation();
    }
    function testMemoryCopy() public {
        uint[] memory input;
        input = new uint[](1);
        input[0] = 10;
        dl.storeFromCalldata(input);
        uint[] memory result = dl.storeToMemory();
        assertEq(result[0], 10);
    }
    function testGetCalldata() public view {
        uint[] memory arr;
        arr = new uint[](1);
        arr[0] = 42;
        uint val = dl.getCalldata(arr); 
        assertEq(val, 42);
    }
}

执行结果:  

代码语言:bash

AI代码解释

$ ➜  counter git:(main) ✗ forge test --match-path test/DataLocation.t.sol  -vvv
[⠊] Compiling...
[⠒] Compiling 1 files with Solc 0.8.30
[⠑] Solc 0.8.30 finished in 535.07ms
Compiler run successful!
Ran 2 tests for test/DataLocation.t.sol:DataLocationTest
[PASS] testGetCalldata() (gas: 10033)
[PASS] testMemoryCopy() (gas: 57458)
Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 4.60ms (2.16ms CPU time)
Ran 1 test suite in 182.96ms (4.60ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests)

七、小结对比

特性

storage

memory

calldata

生命周期

永久存储

函数调用期间

函数调用期间

可修改性

gas 成本

使用场景

状态变量

临时变量

外部参数传入


八、建议实践

  • 函数参数能用 calldata 就用 calldata,尤其是 external 函数,能节省大量 Gas。
  • 避免误用 storage 引用,会导致原始状态被意外修改。
  • 了解深浅拷贝行为,能避免修改副本却期望原始数据变化的误解。

小练习

  1. 实现一个函数,接受 calldata 数组,复制到 memory 并排序。
  2. 编写 storage 引用和 memory 副本操作对比函数,配合 Foundry 测试验证行为。
  3. 编写一个结构体数组操作合约,理解不同位置之间的行为差异。
相关文章
|
1月前
|
人工智能 网络协议 JavaScript
路由与交换系列之OSPF在帧中继的配置实践
本实验主要讲解帧中继网络中OSPF故障排除方法,包括Hub-Spoke组网架构理解、IP地址规划、基本配置、连通性测试及OSPF邻居关系建立等内容,旨在掌握帧中继环境下动态路由协议的配置与排错技巧。
39 0
|
1月前
|
人工智能 JavaScript 前端开发
基于iTextSharp实现PDF加密功能
本教程介绍使用C#与iTextSharp库实现PDF文档加密的方法。内容包括环境搭建、界面设计及后台逻辑编写,涵盖选择文件、设置用户与所有者密码、加密操作等步骤,帮助开发者快速掌握PDF安全处理技巧。
48 0
|
搜索推荐 Android开发 iOS开发
安卓与iOS系统的用户界面设计对比分析
本文通过对安卓和iOS两大操作系统的用户界面设计进行对比分析,探讨它们在设计理念、交互方式、视觉风格等方面的差异及各自特点,旨在帮助读者更好地理解和评估不同系统的用户体验。
677 1
|
自动驾驶 物联网 5G
什么是 5G 以及它如何工作?
【8月更文挑战第23天】
2422 0
|
算法 API C++
使用C++进行系统级编程的深入探索
【5月更文挑战第23天】本文探讨了C++在系统级编程中的应用,强调其接近底层、高性能、可移植性和面向对象编程的优势。关键技术和最佳实践包括:内存管理(智能指针和RAII原则)、多线程(std::thread和同步原语)、系统调用与API、以及设备驱动和内核编程。编写清晰代码、注重性能、确保安全稳定及利用开源库是成功系统级编程的关键。
|
弹性计算 负载均衡 监控
阿里云aca考试大纲 分享阿里云aca题库及答案
现在云计算云服务是高新技术的重要发展发现之一,阿里云长期占有国内的最大市场份额,因此考取阿里云认证也成为想入行的技术人员努力争取的目标,今天就分享阿里云aca题库及答案.希望能帮更多人实现理想。
3983 1
阿里云aca考试大纲 分享阿里云aca题库及答案
|
编解码 物联网
【BLE】蓝牙5.2 新特性 - LE Audio
连接同步通道是基于蓝牙连接的,首先要先建立ble连接基于时间同步的音频传输机制,可以实现多个设备的数据同步一个master可以建立多个CIG每个CIG可以最多31个CIS每个CIS里面最多有31个subevent链路层有LL_CIS_REQ 和 LL_CIS_RSP来创建CIS无连接的单向的,无应答机制广播通道,对接收者的数量没有限制不仅可以广播数据包还可以广播控制包每个big里面最多可以包含31个bis。
2693 1
【BLE】蓝牙5.2 新特性 - LE Audio
|
11月前
|
存储 分布式计算 监控
数据库优化:提升性能与效率的全面策略
【10月更文挑战第21】数据库优化:提升性能与效率的全面策略
|
运维 安全 网络架构
【专栏】NAT技术是连接私有网络与互联网的关键,缓解IPv4地址短缺,增强安全性和管理性
【4月更文挑战第28天】NAT技术是连接私有网络与互联网的关键,缓解IPv4地址短缺,增强安全性和管理性。本文阐述了五大NAT类型:全锥形NAT(安全低,利于P2P)、限制锥形NAT(增加安全性)、端口限制锥形NAT(更安全,可能影响协议)、对称NAT(高安全,可能导致兼容性问题)和动态NAT(公网IP有限时适用)。选择NAT类型需考虑安全性、通信模式、IP地址数量和设备兼容性,以确保网络高效、安全运行。
1215 1
|
存储 机器学习/深度学习 大数据
云计算与大数据:合作与创新
本文探讨了大数据技术与云计算的背景和发展,大数据的5V特征(量、速度、多样、复杂、不确定)及云计算的3S特点(服务、共享、可扩展)。两者相互依赖,云计算为大数据提供计算与存储资源。核心算法涉及分布式计算、数据挖掘和机器学习,如线性回归、逻辑回归等。通过代码示例展示了Hadoop的MapReduce、Scikit-learn的KNN和TensorFlow的线性回归应用。未来趋势包括数据量增长、实时处理、AI与ML集成及数据安全挑战。附录解答了大数据、云计算等相关问题。
731 3

热门文章

最新文章