尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

WTF-Solidity 第5讲:Solidity 数据存储位置(storage/memory/calldata)与变量作用域完全指南

WTF-Solidity 第5讲:Solidity 数据存储位置(storage/memory/calldata)与变量作用域完全指南 WTF-Solidity 第5讲Solidity 数据存储位置storage/memory/calldata与变量作用域完全指南【免费下载链接】WTF-SolidityWTF Solidity 极简入门教程供小白们使用。Now supports English! 官网: https://wtf.academy项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-SoliditySolidity 的引用类型数组、结构体、映射不像值类型那样直接持有数据而是保存数据所在位置的指针因此在使用前必须显式声明数据存储位置。本文以 WTF-Solidity 仓库第5讲为骨架结合 Languages/en/05_DataStorage_en/DataStorage.sol 与根目录 05_DataStorage/DataStorage.sol 两份源码系统讲解storage/memory/calldata三种数据位置的 gas 差异与赋值语义、三类变量作用域以及以太单位与时间单位帮助你写出既省 gas 又符合语义的合约代码。一、Solidity 中的引用类型Reference Type与值类型uint、address、bool等直接存储数值不同引用类型Reference Type不直接存储值本身而是存储数据所在位置的地址/指针。数组array、结构体struct和映射mapping都属于引用类型它们占据较大的存储空间因此在声明和使用时必须显式指定数据存储位置。映射mapping同样是引用类型它的数据位置规则详见第7讲 07_Mapping。二、数据位置storage / memory / calldataSolidity 有三种数据存储位置不同位置的gas 成本差异显著存储位置存放介质生命周期可修改性Gas 成本storage链上类似计算机硬盘永久可修改高memory内存临时函数执行期间可修改低calldata内存临时只读函数执行期间不可修改低整体 gas 消耗从多到少依次为storagememorycalldata。1. storage合约中的状态变量默认都是storage数据持久化存储在链上是唯一允许省略数据位置声明的地方。例如源码 Languages/en/05_DataStorage_en/DataStorage.sol 中的状态变量xcontract DataStorage { // x 的数据位置是 storage这是唯一可以省略数据位置声明的场景 uint[] x [1,2,3]; }2. memory函数中的参数和临时变量一般使用memory数据存储在内存中、不上链。特别地当函数返回值是变长类型string、bytes、array和自定义结构体时必须加memory修饰例如源码中global()函数的返回声明bytes memory。3. calldatacalldata与memory类似同样存储在内存中、不上链区别在于calldata变量不可修改immutable一般只用于函数参数可以避免额外的内存拷贝、进一步节省 gas。源码中的示例function fCalldata(uint[] calldata _x) public pure returns(uint[] calldata){ // 参数为 calldata 数组不能被修改 // _x[0] 0 // 这样修改会报错 return(_x); }在 Remix 中编译这段代码时如果尝试对calldata数组赋值编译器会直接抛出TypeError: Calldata arrays are read-only的编译错误这正是calldata只读语义的体现三、数据位置与赋值行为引用还是拷贝数据位置不仅关乎数据持久性还决定了赋值的语义——是创建引用还是独立副本storage合约状态变量赋值给本地storage函数内→ 创建引用修改新变量会影响原变量memory赋值给memory→ 创建引用修改新变量会影响原变量其他情况storage↔memory、calldata等→ 创建独立副本修改一方不会影响另一方。场景一storage 赋值给本地 storage引用uint[] x [1,2,3]; // 状态变量数组 x function fStorage() public{ // 声明一个 storage 变量 xStorage指向 x。修改 xStorage 也会影响 x uint[] storage xStorage x; xStorage[0] 100; }调用fStorage()后状态变量x会被真正改写为[100,2,3]。在 Remix 调试器中可以看到Solidity State中x[0]由0变为100场景二storage 赋值给 memory独立拷贝uint[] x [1,2,3]; // 状态变量数组 x function 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变为[100,200,0]但状态变量x仍保持[1,2,3]。调试器中Solidity Locals与Solidity State的对比清楚地证明了拷贝语义开发陷阱从storage读取数据赋值给memory后只修改memory而忘记把结果写回storagestorage中的数据不会发生任何变化。这是新手最容易犯的错误之一。四、变量作用域状态变量 / 局部变量 / 全局变量Solidity 变量按作用域分为三类状态变量state variable、局部变量local variable和全局变量global variable。1. 状态变量State Variable状态变量数据存储在链上合约内所有函数都可以访问gas 消耗高。声明位置在合约内、函数外contract Variables { uint public x 1; uint public y; string public z; 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); }3. 全局变量Global Variable全局变量是 Solidity 的预留关键字作用于全局范围可在函数内不声明直接使用function global() external view returns(address, uint, bytes memory){ address sender msg.sender; uint blockNum block.number; bytes memory data msg.data; return(sender, blockNum, data); }上例使用了msg.sender消息发送者地址、block.number当前区块高度和msg.data完整调用数据三个最常用的全局变量。调用返回结果如(0x5B3... , 7, 0x0a051990)分别对应调用者、区块号与 calldata 数据。常用全局变量一览表全局变量类型含义blockhash(uint blockNumber)bytes32给定区块的哈希值只适用于最近 256 个区块不含当前区块block.coinbaseaddress payable当前区块矿工的地址block.gaslimituint当前区块的 gaslimitblock.numberuint当前区块号block.timestampuint当前区块时间戳Unix 纪元以来的秒gasleft()uint256剩余 gasmsg.databytes calldata完整 calldatamsg.senderaddress payable消息发送者当前调用者msg.sigbytes4calldata 前四个字节函数标识符msg.valueuint随消息发送的 wei 数量此外Cancun 升级还新增了两个与 EIP-4844Proto-Danksharding相关的全局变量block.blobbasefee当前区块的 blob 基础费用和blobhash(uint index)返回与当前交易关联的第index个 blob 的版本化哈希若交易不含 blob 则返回空字节。这些全局变量在 Languages/en/05_DataStorage_en/readme.md 中有完整整理。五、以太单位与时间单位1. 以太单位Ether UnitsSolidity不存在小数点用0表示小数点以保障交易的精确度、防止精度损失。使用以太单位可以避免货币换算误算wei: 1gwei: 1e9 1000000000ether: 1e18 1000000000000000000根目录源码 05_DataStorage/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; }2. 时间单位Time Units时间单位让合约可以精确表达一周内完成一个月后触发等语义提高可读性与可维护性seconds: 1minutes: 60 seconds 60hours: 60 minutes 3600days: 24 hours 86400weeks: 7 days 604800源码中同样以assert链式验证了各单位的换算关系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 weeksUnit() external pure returns(uint) { assert(1 weeks 604800); assert(1 weeks 7 days); return 1 weeks; }六、源码佐证与本地运行本讲的完整可运行源码有两份根目录版 05_DataStorage/DataStorage.solpragma solidity ^0.8.34Variables合约额外包含weiUnit/gweiUnit/etherUnit/secondsUnit/minutesUnit/hoursUnit/daysUnit/weeksUnit全套单位验证函数英文版 Languages/en/05_DataStorage_en/DataStorage.sol内容为本文讲解的核心三函数与三作用域示例。仓库根目录 foundry.toml 将编译器固定为solc 0.8.34与两份源码的 pragma 版本约束一致可直接用 Foundry 编译验证forge build 05_DataStorage/DataStorage.sol仓库的测试脚本 scripts/run-forge-tests.sh 将05_DataStorage列为可独立跑测试的主教程章节之一说明该章属于仓库 CI 覆盖范围代码经过编译验证、可直接使用。若在浏览器环境学习直接将该文件粘贴到 Remix 中编译部署即可复现本文全部行为。总结本讲围绕 WTF-Solidity 第5讲核心内容讲清了三个关键知识点引用类型array/struct/mapping需要显式声明数据存储位置三种数据位置storage链上、gas 高、memory内存、可修改、gas 低、calldata内存、只读、最省 gas以及它们之间的赋值语义storage→storage、memory→memory是引用其余是独立拷贝三类变量作用域状态变量、局部变量与全局变量外加以太单位与时间单位这两组 Solidity 内置换算常量。理解这三组概念是写出 gas 友好、语义正确的 Solidity 合约的前提。下一讲将基于本讲的引用类型与存储位置知识介绍数组array与结构体struct的具体用法详见 06_ArrayAndStruct。【免费下载链接】WTF-SolidityWTF Solidity 极简入门教程供小白们使用。Now supports English! 官网: https://wtf.academy项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Solidity创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表