WTF Solidity 第5讲:变量数据存储与作用域 —— 吃透 storage / memory / calldata 与变量作用域

发布时间:2026/9/14 3:53:36

WTF Solidity 第5讲:变量数据存储与作用域 —— 吃透 storage / memory / calldata 与变量作用域 WTF Solidity 第5讲变量数据存储与作用域 —— 吃透 storage / memory / calldata 与变量作用域【免费下载链接】WTF-SolidityWTF Solidity 极简入门教程供小白们使用。Now supports English! 官网: https://wtf.academy项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-SoliditySolidity 的引用类型数组、结构体与值类型不同必须显式声明数据存储位置这一选择直接决定了合约的 Gas 开销与变量行为。本篇以 WTF-Solidity 仓库 05_DataStorage 章节 为核心结合其配套源码 DataStorage.sol系统讲解storage、memory、calldata三种数据位置的差异与赋值规则、三类变量作用域以及 Solidity 内置的以太单位与时间单位。学完本篇你将能准确判断每个变量该放在哪里、赋值后是引用还是副本从而写出更省 Gas、更少踩坑的合约。引用类型Reference Type为什么必须声明数据位置Solidity 中的变量按数据性质分为值类型Value Type与引用类型Reference Type。其中引用类型包括数组array和结构体struct这类变量的本体较大、占用存储空间多直接按值拷贝成本极高。因此 Solidity 规定使用引用类型变量时必须显式声明它的数据存储位置data location编译器才能确定变量在存储链上还是内存临时中布局。这一要求从仓库源码的注释中可以直接印证DataStorage.sol 在状态变量x上方写道// The data location of x is storage. // This is the only place where the // data location can be omitted. uint[] public x [1,2,3];状态变量是唯一可以省略数据位置声明的地方——因为它天然就是storage。除此之外任何数组、结构体类型的参数、局部变量与返回值都必须显式标注位置。三种数据位置storage、memory、calldataSolidity 的数据存储位置共有三类它们的核心差异在于数据存到哪里以及Gas 成本storage数据存储在链上类似计算机的硬盘持久保存读写都要消耗较多的 Gas。memory数据临时存储在内存中函数调用结束后即释放不上链Gas 成本低。calldata与memory类似也是内存中临时存储、不上链但它直接引用调用者传入的原始 calldata只读、不可修改一般用于函数参数Gas 更省。整体 Gas 消耗从多到少依次为storagememorycalldata。这也解释了为何链上状态读写是合约开发中最需要优化的部分。三类位置的具体用法storage合约里的状态变量默认都是storage存储在链上任何函数都可访问持久生效。memory函数里的参数和临时变量一般用memory存储在内存中、不上链。尤其当返回类型是变长数据string、bytes、array、自定义struct时必须加memory修饰因为变长数据无法在链上/ABI 编解码之外直接返回必须借助内存中转。calldata与memory类似但只读通常用于函数的输入参数避免将入参拷贝进内存从而节省 Gas。示例如下完整代码见 DataStorage.solfunction fCalldata(uint[] calldata _x) public pure returns(uint[] calldata){ // 参数为 calldata 数组不能被修改 // _x[0] 0 // 这样修改会报错 return(_x); }如果把_x[0] 0;取消注释Remix 会直接抛出编译错误TypeError: Calldata arrays are read-only.calldata 数组只读这正是calldata不可修改特性的直观体现数据位置与赋值规则引用还是副本在不同存储位置之间相互赋值时结果可能完全不同有时产生独立的副本修改新变量不影响原变量有时产生引用两者指向同一份数据修改会同步。这是 Solidity 新手最容易踩坑的地方规则如下**产生引用修改会同步**的情况storage状态变量赋值给本地storage变量时创建的是引用修改新变量会影响原变量memory赋值给memory时同样创建引用修改会同步。**产生副本修改互不影响**的情况除上述两种外其余赋值均为拷贝。典型如从storage读取数据赋给memory再修改memory变量——如果没有把结果写回storage原数据不会改变。storage 引用的示例以仓库源码 DataStorage.sol 中的fStorage为例uint[] x [1,2,3]; // 状态变量数组 x function fStorage() public{ // 声明一个 storage 的变量 xStorage指向 x。修改 xStorage 也会影响 x uint[] storage xStorage x; xStorage[0] 100; }这里xStorage并不是x的拷贝而是指向同一个底层存储槽的引用。因此执行fStorage()后状态变量x[0]会被真正改写为100。在 Remix 调试器的 Solidity State 面板中可以清楚看到状态变量x的变化uint256[] x长度为 3且x[0] 100memory 副本的示例同文件中的fMemory则演示了副本行为见 DataStorage.solfunction fMemory() public view{ // 声明一个 Memory 的变量 xMemory复制 x。修改 xMemory 不会影响 x uint[] memory xMemory x; xMemory[0] 100; xMemory[1] 200; uint[] memory xMemory2 x; xMemory2[0] 300; }xMemory和xMemory2都是x的内存副本无论怎么修改它们链上的x始终保持[1,2,3]不变。这正是从 storage 读入 memory 后修改结果不会自动回写 storage的典型场景——如果你希望修改生效必须显式把memory数据赋值回storage变量。变量的作用域状态变量、局部变量、全局变量Solidity 中的变量按作用域分为三类状态变量state variable、局部变量local variable和全局变量global variable。1. 状态变量State Variable状态变量是数据存储在链上的变量所有合约内函数都可以访问Gas 消耗高。它在合约内、函数外声明contract Variables { uint public x 1; uint public y; string public z; }在函数里可以随时更改状态变量的值见 DataStorage.sol 中的foo函数function foo() external{ // 可以在函数里更改状态变量的值 x 5; y 2; z 0xAA; }2. 局部变量Local Variable局部变量仅在函数执行过程中有效函数退出后即失效数据存储在内存里、不上链Gas 低。局部变量在函数内声明function bar() external pure returns(uint){ uint xx 1; uint yy 3; uint zz xx yy; return(zz); }bar声明为pure因为函数体内的xx、yy、zz全部是局部变量既不读也不写链上状态编译器允许这种最省 Gas 的函数修饰。3. 全局变量Global Variable全局变量是 Solidity 的预留关键字在整个合约范围内可直接使用无需声明。在函数内可以直接引用它们例如 DataStorage.sol 中的global函数function global() external view returns(address, uint, bytes memory){ address sender msg.sender; // 请求发起地址 uint blockNum block.number; // 当前区块高度 bytes memory data msg.data; // 请求数据完整 calldata return(sender, blockNum, data); }调用global()后Remix 的 decoded output 会依次返回请求发起地址如0xAb483F649...、当前区块高度如7和原始 calldata 数据直观展示了三类常用全局变量的取值常用全局变量速查表以下是最常用的全局变量更完整的列表可参阅 Solidity 官方文档的 Special Variables and Functions 章节全局变量类型含义blockhash(uint blockNumber)bytes32给定区块的哈希值只适用于最近的 256 个区块不包含当前区块block.coinbaseaddress payable当前区块矿工的地址block.gaslimituint当前区块的 gaslimitblock.numberuint当前区块的 numberblock.timestampuint当前区块的时间戳Unix 纪元以来的秒gasleft()uint256剩余 gasmsg.databytes calldata完整 call datamsg.senderaddress payable消息发送者当前 callermsg.sigbytes4calldata 的前四个字节function identifiermsg.valueuint当前交易发送的wei值block.blobbasefeeuint当前区块的 blob 基础费用Cancun 升级新增的全局变量blobhash(uint index)bytes32返回与当前交易关联的第index个 blob 的版本化哈希第一个字节为版本号当前为0x01后面接 KZG 承诺的 SHA256 哈希的最后 31 个字节若当前交易不包含 blob 则返回空字节。Cancun 升级新增全局变量的延伸以太单位与时间单位除了上述直接可用的全局变量Solidity 还内置了两套单位字面量让合约中处理金额与时间更可读、更精确。以太单位Ether UnitsSolidity 中不存在小数点用0表示小数位以确保交易的精确度并防止精度损失。利用以太单位可以避免手写一长串零导致误算方便程序员在合约中处理货币交易单位对应的 wei 值wei1gwei1e9 1000000000ether1e18 1000000000000000000仓库源码中的weiUnit、gweiUnit、etherUnit函数见 DataStorage.sol用assert验证了这些换算关系function weiUnit() external pure returns(uint) { assert(1 wei 1e0); assert(1 wei 1); return 1 wei; } function gweiUnit() external pure returns(uint) { assert(1 gwei 1e9); assert(1 gwei 1000000000); return 1 gwei; } function etherUnit() external pure returns(uint) { assert(1 ether 1e18); assert(1 ether 1000000000000000000); return 1 ether; }assert断言不通过会直接回滚交易因此这三个函数能编译并正常执行本身就是单位换算正确性的运行时证明。时间单位Time Units时间单位让合约可以精确表达时间约束例如操作必须在一周内完成事件在一个月后发生提高合约的可读性与可维护性单位对应的秒数seconds1minutes60 seconds 60hours60 minutes 3600days24 hours 86400weeks7 days 604800源码中的时间单位函数见 DataStorage.sol同样用assert固化换算关系function secondsUnit() external pure returns(uint) { assert(1 seconds 1); return 1 seconds; } function minutesUnit() external pure returns(uint) { assert(1 minutes 60); assert(1 minutes 60 seconds); return 1 minutes; } function hoursUnit() external pure returns(uint) { assert(1 hours 3600); assert(1 hours 60 minutes); return 1 hours; } function daysUnit() external pure returns(uint) { assert(1 days 86400); assert(1 days 24 hours); return 1 days; } function weeksUnit() external pure returns(uint) { assert(1 weeks 604800); assert(1 weeks 7 days); return 1 weeks; }源码实践编译与运行本讲示例本讲的完整可运行代码位于 05_DataStorage/DataStorage.sol文件头声明pragma solidity ^0.8.34;与仓库根目录 foundry.toml 中solc 0.8.34的编译器版本一致可直接用 Foundry 生态编译验证。章节在仓库中的组织根目录的05_DataStorage目录与 src/05_DataStorage 目录互为镜像存放同一套.sol源码与教程文档方便按目录索引仓库提供了统一的测试脚本 scripts/run-forge-tests.sh其中05_DataStorage被列入主教程章节清单可通过forge test --match-contract RootTest或对每个章节目录执行forge test来批量验证合约可编译、可通过测试在浏览器中也可直接把 DataStorage.sol 粘贴到 Remix 中编译部署然后依次调用fStorage、fMemory、fCalldata、global以及各单位函数对照上文三张截图观察行为差异。总结本讲围绕引用类型展开核心是storage、memory、calldata三个关键字的用法storage持久上链、Gas 高memory临时存内存、Gas 低calldata只读引用调用数据、最省 Gas。同时需要牢记赋值规则——storage→本地storage与memory→memory是引用修改会同步其余多为副本修改不生效。变量按作用域又分为状态变量、局部变量与全局变量配合以太单位与时间单位即可准确、省 Gas 地编写合约。下一讲将介绍引用类型中的数组与结构体。【免费下载链接】WTF-SolidityWTF Solidity 极简入门教程供小白们使用。Now supports English! 官网: https://wtf.academy项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Solidity创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/9/14 3:53:36

Linux设备驱动开发:硬件与内核的契约式工程实践

1. 这不是“写个驱动”那么简单:一个真实嵌入式团队踩了三年才理清的开发逻辑 “Linux设备驱动开发”这七个字,看起来像教科书目录里的一章标题,但在我带过的十几个嵌入式项目里,它从来不是从 hello_world.c 开始的。它是一条从…

2026/9/14 3:53:36

西门子S7-200 SMART PLC在锅炉控制系统中的应用

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/14 4:38:38

纯前端复刻QQ音乐界面:Web课程设计实战指南

简介:面向前端初学者的QQ音乐界面模仿型Web课程设计资源,适合完成HTMLCSS课程作业、学习页面布局与交互特效的学生参考。压缩包共102个文件,主要包含HTML页面、CSS样式、JavaScript脚本、大量截图与背景音乐,包体约16.16MB&#x…

2026/9/14 4:38:38

PyTorch UNet肝脏MRI分割实战:数据预处理、模型训练与推理后处理全解

简介:一套基于PyTorch与U-Net架构的MRI肝脏图像分割完整项目方案,面向计算机专业毕业设计、课程设计以及需要医学影像实战练习的初学者。项目包含可运行的Python源码、预处理后的肝脏MRI数据集与训练好的模型权重,覆盖数据增强、模型训练、评…

2026/9/14 4:38:38

PSO优化RBF神经网络spread参数实现分类预测调参

简介:针对多特征输入的单输出分类预测任务,这份Matlab代码实现了基于粒子群算法(PSO)优化径向基神经网络(RBF)的完整流程,面向需要快速搭建PSO-RBF分类模型的科研人员与工程师。程序以扩散速度作…

2026/9/14 4:38:38

RenderCV 自定义字体指南:在简历中使用 .ttf / .otf 字体

RenderCV 自定义字体指南:在简历中使用 .ttf / .otf 字体 【免费下载链接】rendercv Resume builder for academics and engineers 项目地址: https://gitcode.com/GitHub_Trending/re/rendercv 本指南介绍 RenderCV 的自定义字体(Custom Fonts&a…

2026/9/14 4:33:37

VB6工资管理系统毕设代码接手调试与修改实战指南

简介:这是一份面向计算机专业毕业设计的VB工资管理系统完整资料包,适合需要完成课程设计、开题报告与答辩准备的学生使用。项目覆盖需求分析、系统设计、编码实现、测试优化等完整开发环节,帮助读者将VB编程与Access或SQL Server数据库知识应…

2026/9/14 2:17:50

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/14 0:03:22

KCF目标跟踪算法与OTB工程实现:毕业设计实战解析

简介:这是一份基于KCF核相关滤波算法、融合尺度池与抗遮挡处理的目标检测跟踪MATLAB完整源码,主要面向计算机相关专业准备毕业设计、课程设计或期末大作业的学生,也适合需要项目实战练习的初学者。源码在OTB数据集上完成验证,能够…

2026/9/14 0:03:22

语音情感识别实战:Keras实现LSTM、CNN、SVM与MLP多模型对比

简介:面向语音情感识别入门与进阶开发者,这份基于Keras的项目源码完整实现了LSTM、CNN、SVM、MLP四种模型,兼容Python3.8与Keras/TensorFlow2环境。压缩包内含49个文件,大小约70.31MB,主体包括Python脚本、yaml/json配…

2026/9/12 6:29:36

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/12 14:32:17

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/13 11:18:28

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码