---
title: 一些没分类的小知识
createTime: 2025/10/12 15:34:38
permalink: /programming/solidity/other/miscellaneous/
---

## 关于 memory 和 storage 存储类型

- `storage`：合约的持久化状态数据，保存在链上状态。对 `storage` 的写入最昂贵，读取也比内存贵；修改会永久生效。
- `memory`：函数调用期间的临时数据，函数返回后即释放。对 `memory` 的更改不会持久化。
- （补充）`calldata`：外部函数参数的只读数据位置，零拷贝、不可修改，用于节省 gas。

### 生命周期与成本
- `storage` 写入昂贵、读取较贵；适合保存需要长期存在的状态。
- `memory` 在函数结束时释放，读取/写入相对便宜；适合临时计算与返回值。
- 复杂引用类型（数组、`struct`、`mapping`、`string`、`bytes`）在函数参数或局部变量处通常必须显式标注数据位置。

### 默认与必须声明
- 状态变量总是位于 `storage`（例如 `User[] public users;`）。
- 外部函数（`external`）的复杂类型参数默认是 `calldata`；内部/公共函数需要显式标注 `memory` 或 `storage`。
- 局部变量的复杂类型必须指定数据位置，否则编译报错。

### 拷贝与引用语义
- 从 `storage` 读取到 `memory` 会“复制”数据；修改 `memory` 副本不影响原始 `storage`。
- 使用 `storage` 局部变量可以得到对状态数据的“引用”，对其赋值会持久化。

```solidity
pragma solidity ^0.8.20;

contract Users {
    struct User { string name; uint age; }
    User[] public users;

    function add(string memory name, uint age) external {
        users.push(User(name, age)); // 写入 storage
    }

    function updateName(uint i, string memory newName) external {
        User storage u = users[i];   // storage 引用（指向链上状态）
        u.name = newName;            // 修改持久化生效
    }

    function tryUpdate(uint i) external {
        User memory u = users[i];    // 从 storage 复制到 memory
        u.age = 99;                  // 仅修改副本，不会影响链上状态
    }
}
```

### 在内部函数传递 storage 引用
- 仅内部/私有函数可以接收 `storage` 引用参数，从而直接修改状态；外部函数参数不能是 `storage`。

```solidity
pragma solidity ^0.8.20;

contract Users2 {
    struct User { string name; uint age; }
    User[] public users;

    function _inc(User storage u) internal { u.age += 1; }

    function birthday(uint i) external {
        _inc(users[i]); // 传递 storage 引用，持久化修改
    }
}
```

### 动态 memory 数组与限制
- 可在 `memory` 中构造动态数组：`new uint[](n)`；适合作为返回值或临时计算。
- `mapping` 只能存在于 `storage`，不能在 `memory` 中创建或拷贝。

```solidity
pragma solidity ^0.8.20;

contract Arrays {
    function make(uint n) external pure returns (uint[] memory a) {
        a = new uint[](n);
        for (uint i = 0; i < n; i++) a[i] = i;
    }
}
```

### 常见坑与实践建议
- 给 `storage` 变量整体赋值会进行深拷贝或引用变更（依据类型），要明确拷贝成本与语义。
- 修改 `memory` 副本不会持久化；要修改链上状态请使用 `storage` 引用。
- 大型 `string/bytes/数组` 在 `memory↔storage` 间复制成本高，尽量减少不必要的复制。
- 外部函数能用 `calldata` 的地方尽量使用（只读参数），节省 gas。

