Skip to content

Commit 7ac497b

Browse files
committed
Update study notes for 2025-08-18
1 parent 6b54451 commit 7ac497b

1 file changed

Lines changed: 65 additions & 0 deletions

File tree

brucexu-eth.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,71 @@ val64 := mload(0x40) -> PUSH1(0x40) set address -> MLOAD get value -> SWAP3 set
151151

152152
源代码实际上会翻译成 opcode 进行实际的执行,所以多个 opcode 对应一行具体的代码。通过 source map 进行映射
153153

154+
### EVM memory 相关知识
155+
156+
- 256-bit(32 byte)对齐的线性字节数组,按字节寻址(MLOAD/MSTORE 以 32 byte 为基本单位)。
157+
- 生命周期:仅在 当前执行上下文 存在(一次外部调用 / CREATE / DELEGATECALL 结束就被丢弃)。
158+
- 费用:第一次把某一字节范围扩展到比以往更高的地址时才付 gas(线性 + 微量二次项)。
159+
- 与 stack、storage、calldata 完全独立:stack 最快最小、storage 永久最贵、calldata 只读且外部传入。
160+
161+
#### 0x00 – 0x3F:Scratch Space(64 byte 临时工作区)
162+
163+
Solidity 编译器在做 Keccak-256、ABI 编码、拼装返回值时,喜欢把原材料先丢进这 64 byte,然后立即计算哈希或 RETURN.
164+
165+
是两排 32 bytes 的内存数据空间。
166+
167+
TODO 如果临时工作区需要缓存的数据超过 64 bytes 怎么办?
168+
169+
#### 0x40 – 0x5F:Free Memory Pointer(32 byte)
170+
171+
任何向内存写入动态数据的 Solidity 代码,第一步都是:
172+
let ptr := mload(0x40) 取旧指针 → 写数据 → 计算新尾址(对齐 32 byte)→ mstore(0x40, newPtr) 更新。默认 0x80,真正自由空间从 0x80 开始。
173+
174+
所以在所有 smart contract 的开头,是固定的:
175+
176+
```
177+
PUSH1(0x80)
178+
PUSH1(0x40)
179+
MSTORE
180+
```
181+
182+
这样将 0x80 写入到 0x40 的地址,方便之后进行增加。
183+
184+
TODO 编写一个写入动态数据的 example 进行测试和查看效果。
185+
186+
#### 0x60 – 0x7F:Zero Slot(32 byte 常量 0)
187+
188+
这 32 byte 在函数入口被置零一次,此后永远保持 0。
189+
190+
省 gas:随时需要 32 byte 的全 0 数据块(空动态数组长度、布尔 false、占位 padding 等)时,直接 mload(0x60) 拿现成的;不用再写入 32 byte 的 0。
191+
192+
同时确保 0x60–0x7F 也不会被误当作可写空间。
193+
194+
#### 0x80 以后:用户数据区
195+
196+
所有动态大小的数据(bytes, string, bytes[], struct、abi.encode* 结果、函数返回值等)都从这里向高地址连续生长。
197+
198+
```
199+
assembly {
200+
// 申请 96 byte (=0x60) 空间示例
201+
let ptr := mload(0x40) // 取当前 free mem ptr
202+
// ...在 [ptr .. ptr+0x5F] 写入内容...
203+
mstore(0x40, add(ptr, 0x60)) // 对齐后写回
204+
}
205+
```
206+
207+
TODO 研究几个常见的操作的 opcode 和 gas 变更:`keccak256(bytes memory)``abi.encode(...)`.
208+
209+
## 超过 64 byte 时,编译器(或你写的 Yul/assembly)会怎么做?
210+
211+
| 场景 | 处理策略 | 关键点 |
212+
| ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- |
213+
| **1. 需要哈希 / 编码的数据 ≤ 64 byte** | 直接写进 **0x00-0x3F**`keccak256(0x00, len)` | 无需扩展 memory,最省 gas |
214+
| **2. 数据 > 64 byte,但已存在于 memory 的别处** | 根本 **不拷贝** 到 Scratch Space;<br>直接 `keccak256(ptr, len)` | 例如 `keccak256(someArray.offset, someArray.length)` |
215+
| **3. 数据 > 64 byte,且要临时拼装后再哈希 / 返回** | - 先用 **Free Memory Pointer (mload 0x40)** 申请一段从 **0x80** 开始的空闲区<br>- 把各字段按 ABI 规则拼到那里<br>- 更新 0x40 → `keccak256(ptr, totalLen)``return(ptr, totalLen)` | 这才会真正「扩展内存」并付扩展 gas |
216+
217+
Scratch Space 并不是“唯一的临时区”,只是 “够用就用、不够就去 0x80+” 的第一选择。0x00-0x3F 只是一个“高速缓存线”:小于 64 byte 时最省事;一旦超过,编译器(或你)就会改用从 0x80 起 的常规 free-memory 区域。关键是理解 Free Memory Pointer (0x40) 的分配协议:所有动态缓冲区都从这里申请、向高地址增长。因此,“临时工作区超过 64 byte” 并不会出错——它只意味着会触发一次正常的内存分配并多付一点扩展 gas。
218+
154219
# 2025-08-15
155220

156221
这个 forge debug 无法找到 source map 这个问题还是比较严重的,只能在 test case 里面 debug 有什么用呢?

0 commit comments

Comments
 (0)