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: Copyba 的克隆。
  • var b = &a: Referenceba 的遥控器。

3. 函数参数的 Const 属性

这是 Zig 的重要设计。

fn foo(n: u8) void {
    n = 5; // ❌ 编译错误!n 是常量
}

这强制我们在需要修改数据时显式地使用指针。这让代码的意图(Side Effects)变得非常清晰:如果通过值传递,我就知道这个函数是安全的,不会偷偷修改我的数据。


后续预告:我们已经彻底搞懂了单个变量的指针。但是,如果我们要操作一排连续的数据(数组),单个指针还够用吗?下一篇我们将探讨 Zig 的大杀器——切片 (Slices)。