Ziglings 笔记 38: 结构体数组与 undefined 之谜

队伍集结

在 Ziglings 的第 38 个练习中,我们将结构体放入了数组中。这是构建复杂程序(比如游戏中的实体列表、通过 ID 查找记录等)的基础。

此外,这个练习还顺带揭示了 Zig 作为一个“安全”的系统语言,是如何处理未初始化内存的。

挑战:添加队员

我们有一个容量为 2 的数组 chars。第一个位置已经被 “Glorp the Wise” 占据了。我们需要在第二个位置(索引 1)添加 “Zump the Loud”。

解决方案

const std = @import("std");

const Role = enum {
    wizard,
    thief,
    bard,
    warrior,
};

const Character = struct {
    role: Role,
    gold: u32,
    health: u8,
    experience: u32,
};

pub fn main() void {
    // 1. 声明数组但不初始化内容
    // undefined 表示:“我有意留空,稍后赋值”
    var chars: [2]Character = undefined;

    // 初始化第一个角色
    chars[0] = Character{
        .role = Role.wizard,
        .gold = 20,
        .health = 100,
        .experience = 10,
    };

    // 2. 初始化第二个角色 (Zump)
    chars[1] = Character{
        .role = Role.bard,
        .gold = 10,
        .health = 100,
        .experience = 20,
    };

    // 遍历打印
    for (chars, 0..) |c, num| {
        std.debug.print("Character {} - G:{} H:{} XP:{}\n", .{
            num + 1, c.gold, c.health, c.experience,
        });
    }
}

核心知识点总结

1. 结构体数组

var chars: [2]Character 在内存中是连续存放的。这与 Java 或 Python 的对象数组不同(后者通常是引用的数组)。在 Zig 中,这意味着极高的数据局部性和缓存效率。

2. undefined 的两面性

使用 undefined 是有风险的。如果你声明了它却忘了给 chars[1] 赋值,然后去读取它,会发生什么?

  • 在 ReleaseFast 模式下:这是未定义行为 (UB)。程序可能打印乱码,也可能崩溃,或者被黑客利用。
  • 在 Debug 模式下:Zig 会帮我们一把。

3. 神奇的 0xAA

为了帮开发者捉虫,Zig 在 Debug 模式下会将 undefined 的内存区域填充为字节 0xAA(二进制 10101010)。

  • 如果你看到一个 u8 变成了 170
  • 或者一个 u32 变成了 2863311530 (即 0xAAAAAAAA)。
  • 或者一个指针指向了地址 0xAAAAAAAA

这就意味着:你正在访问未初始化的内存! 这种设计让隐蔽的内存错误变得显而易见。


后续预告:我们已经学会了定义数据。但是,C++ 或 Java 程序员可能会问:“方法在哪里?” 在 Zig 中,我们也可以在结构体内部定义函数。下一篇我们将学习 Pointers(指针),这是理解 Zig 方法调用的前提。