在 Zig 中,引用本身(即指针变量)通常存储在栈上,但它指向的数据可以位于堆或栈上。

引用(指针)本身的存储位置

pub fn main() void {
    var x: i32 = 42;          // x 存储在栈上
    const ptr = &x;           // ptr 变量(引用)本身存储在栈上
    std.debug.print("指针地址: {*}\\n", .{ptr});
}

在这个例子中:

引用指向的数据位置

1. 指向栈上的数据

pub fn example() void {
    var local_var: i32 = 100;     // 栈上分配
    const ref = &local_var;       // 引用指向栈上的数据

    // 危险:不要返回指向局部变量的引用!
    // return ref; // 这会导致悬垂指针!
}

2. 指向堆上的数据

pub fn createOnHeap(allocator: std.mem.Allocator) !*i32 {
    // 在堆上分配内存
    const ptr = try allocator.create(i32);
    ptr.* = 200;                  // 解引用并赋值

    // 安全:堆内存会持续存在,直到手动释放
    return ptr;
}

关键概念总结

概念 存储位置 生命周期管理
引用变量本身 栈上 自动(随作用域结束而销毁)
指向栈的数据 栈上 自动(随作用域结束而销毁)
指向堆的数据 堆上 手动(需要显式释放)

示例说明

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();

    // 情况1:引用指向栈上的数据
    {
        var stack_value: i32 = 10;
        const stack_ref = &stack_value; // 引用存储在栈上,指向栈数据
        std.debug.print("栈引用值: {}\\n", .{stack_ref.*});
    } // stack_value 和 stack_ref 在这里自动销毁

    // 情况2:引用指向堆上的数据
    {
        const heap_ref = try allocator.create(i32); // 引用存储在栈上,指向堆数据
        heap_ref.* = 20;
        std.debug.print("堆引用值: {}\\n", .{heap_ref.*});
        allocator.destroy(heap_ref); // 必须手动释放!
    }

    // 情况3:引用作为函数参数(仍然在栈上)
    const value: i32 = 30;
    printValue(&value); // 传递指向栈上数据的引用
}

fn printValue(ptr: *const i32) void {
    // ptr 参数本身在栈上(作为函数参数)
    std.debug.print("函数参数引用值: {}\\n", .{ptr.*});
}

为什么这个问题重要?

理解引用存储位置的重要性:

  1. 生命周期管理:指向栈数据的引用不能比数据本身存活更久
  2. 性能考虑:栈分配比堆分配快得多