{"author":{"address":"0x758e4E534AFBB044Dea64AB07e5d783fEc7e1541","user":"https://learnblockchain.cn/people/12931"},"content":{"body":"在 Solidity 中，引用类型（如 structs、arrays 和 mappings）需要明确标注其存储位置。存储位置决定了数据的生命周期和可访问性。以下分了3种场景来讨论：\r\n\r\n## 1. **合约内 函数外**\r\n\r\n在合约内但函数外声明的变量，默认使用 `storage` 且无法更改。这些变量是合约状态的一部分，会持久化存储在区块链上。\r\n\r\n```solidity\r\ncontract SimpleContract {\r\n    uint[] data; // 默认使用 storage\r\n}\r\n```\r\n\r\n## 2. **函数参数的存储位置**\r\n\r\n函数参数的存储位置可以是 `memory`、`calldata` 或 `storage`，具体选择取决于数据的使用方式和函数的可见性。\r\n\r\n### `memory`\r\n\r\n- 传入的参数被copy到了内存中且可以被修改。\r\n- 对函数可见性没有要求。\r\n\r\n```solidity\r\ncontract SimpleContract {\r\n    function modifyMemoryArray(uint[] memory memory_a) public pure {\r\n        memory_a[0] = 0; // 可以修改 memory 数组的值\r\n    }\r\n}\r\n\r\n```\r\n\r\n### `calldata`\r\n\r\n- 传入的参数没有被copy到内存中, 是只读的，不能修改 。\r\n- 对函数可见性没有要求。(0.6.9版本以上)\r\n\r\n```solidity\r\ncontract SimpleContract {\r\n    function modifyCalldataArray(uint[] calldata calldata_a) public pure {\r\n        // calldata_a[0] = 0; // 不能修改 calldata 数组的值，会报错\r\n    }\r\n}\r\n\r\n```\r\n\r\n### `storage`\r\n\r\n- 传入的参数只是传递了参数的引用，可以修改，并会影响原有变量。\r\n- 使用 `storage` 的函数可见性必须设置为 `internal` 或 `private`。\r\n\r\n```solidity\r\ncontract SimpleContract {\r\n    uint[] storage_a  = [1, 2, 3];\r\n    function f1() public {\r\n    modifyStorageArray(storage_a ); // 直接传递了这个数组的引用\r\n    }\r\n    function modifyStorageArray(uint[] storage storage_a) internal {\r\n        storage_a[0] = 0; // 可以修改 storage 数组的值\r\n    }\r\n}\r\n\r\n```\r\n\r\n## 3. **函数内部的使用**\r\n\r\n在函数内部，可以使用 `storage` 或 `memory` 来声明变量，具体取决于数据的使用方式。\r\n\r\n### `storage`\r\n\r\n在函数内部使用 `storage` 时，本质上是创建了一个对合约状态变量的引用。修改 data 数组的值会影响原有变量。\r\n\r\n```solidity\r\ncontract SimpleContract {\r\n    uint[] data = [1, 2, 3];\r\n\r\n    function foo() public {\r\n        uint[] storage dataRef = data; // 创建了一个对状态变量 data 的引用\r\n        dataRef[0] = 10; // 修改 data 数组的值\r\n    }\r\n}\r\n\r\n```\r\n\r\n### `memory`\r\n\r\n在函数内部使用 `memory` 时，是创建一个新的内存数组，其生命周期仅限于函数执行期间。修改 memory 数组的值，不会影响原有变量。\r\n\r\n```solidity\r\ncontract SimpleContract {\r\n    function foo() public {\r\n        uint[] memory tempData = new uint[](10); // 创建一个新的 memory 数组\r\n        tempData[0] = 1; // 修改 memory 数组的值\r\n    }\r\n}\r\n\r\n```\r\n\r\n## 总结\r\n\r\n- **`storage`**：用于持久化存储在合约状态中的数据，函数参数和函数内部变量都可以使用，需注意其函数可见性限制。\r\n- **`memory`**：用于临时存储在函数执行期间的数据，函数参数和函数内部变量都可以使用，无特殊可见性限制。\r\n- **`calldata`**：用于只读的函数参数，主要用于 `external` 或 `public` 函数。\r\n\r\n通过合理标注存储位置，可以优化合约的性能(gas的优化)和安全性。\r\n\r\n### 参考资料\r\n\r\n1. [Solidity Documentation - Data Location](https://docs.soliditylang.org/en/latest/types.html#data-location)\r\n2. [CSDN Blog - Solidity 数据存储位置](https://blog.csdn.net/lj900911/article/details/83037607)\r\n3. [Alchemy Documentation - When to Use Storage vs Memory vs Calldata in Solidity](https://docs.alchemy.com/docs/when-to-use-storage-vs-memory-vs-calldata-in-solidity#:\\~:text=In%20summary%2C%20memory%20is%20used,accessed%20and%20modified%20by%20any)\r\n4. [Ethereum Stack Exchange - In What Cases Would I Set a Parameter to Use Storage Instead of Memory?](https://ethereum.stackexchange.com/questions/107028/in-what-cases-would-i-set-a-parameter-to-use-storage-instead-of-memory)","title":"Solidity:  引用类型的储存位置: Storage, Memory, Calldata."},"history":null,"timestamp":1720422980,"version":1}