Ziglings 笔记 51: 深入内存核心
熔岩核心之旅
在 Ziglings 的第 51 个练习中,我们暂时放下了新语法的学习,转而深入计算机的“熔岩核心”——内存模型。
理解内存是成为系统程序员的关键。数据到底存在哪里?当我们赋值时发生了什么?
挑战:升级角色
我们需要编写一个 levelUp 函数来增加角色的经验值。
这里的难点在于理解 Zig 的参数传递规则:函数参数默认为常量。如果我们尝试直接传递 Character 结构体,我们将无法在函数内部修改它。
解决方案
我们需要使用指针来实现“引用传递”:
const std = @import("std");
const Character = struct {
gold: u32 = 0,
health: u8 = 100,
experience: u32 = 0,
};
pub fn main() void {
// glorp 存放在栈 (Stack) 上
var glorp = Character{ .gold = 30 };
const reward_xp: u32 = 200;
const print = std.debug.print;
// 实验:值拷贝
// 这里的 access1 是 glorp 的副本,修改它不会影响 glorp
var glorp_access1: Character = glorp;
glorp_access1.gold = 111;
print("1:{}!. ", .{glorp.gold == glorp_access1.gold}); // false
// 实验:指针引用
// access2 指向 glorp 的地址,修改它就是修改 glorp
var glorp_access2: *Character = &glorp;
glorp_access2.gold = 222;
print("2:{}!. ", .{glorp.gold == glorp_access2.gold}); // true
print("XP before:{}, ", .{glorp.experience});
// 修复 1: 传递 glorp 的地址 (&glorp)
levelUp(&glorp, reward_xp);
print("after:{}.\n", .{glorp.experience});
}
// 修复 2: 接收指针 (*Character)
// 这样我们就有权限修改指针指向的内存了
fn levelUp(character_access: *Character, xp: u32) void {
// 注意:Zig 会自动处理结构体指针的解引用
// 所以 character_access.experience += xp 也是合法的
character_access.*.experience += xp;
}
核心知识点总结
1. 内存三剑客
- 数据段: 存放
const全局变量(如练习中的the_narrator)。程序启动时加载,位置固定。 - 栈 (Stack): 函数调用的游乐场。
glorp就在这里。CPU 对栈操作有特殊优化,速度极快,但空间有限。 - 堆 (Heap): 自由的荒野。当你需要动态分配大量内存时使用(后续章节会讲)。
2. 值 vs 引用
var b = a: Copy。b是a的克隆。var b = &a: Reference。b是a的遥控器。
3. 函数参数的 Const 属性
这是 Zig 的重要设计。
fn foo(n: u8) void {
n = 5; // ❌ 编译错误!n 是常量
}
这强制我们在需要修改数据时显式地使用指针。这让代码的意图(Side Effects)变得非常清晰:如果通过值传递,我就知道这个函数是安全的,不会偷偷修改我的数据。
后续预告:我们已经彻底搞懂了单个变量的指针。但是,如果我们要操作一排连续的数据(数组),单个指针还够用吗?下一篇我们将探讨 Zig 的大杀器——切片 (Slices)。