# Memory Model Basics

### Memory Model Basics

Memory is where a program keeps its data while it runs.

Every variable, array, string, object, pointer, and function call uses memory in some way. When you write Zig, you are closer to memory than you are in languages like Python, JavaScript, Java, or Go. Zig does not hide memory from you. It makes memory part of the program’s design.

This is one of the most important ideas in Zig.

A Zig programmer should always be able to ask:

Where is this value stored?

How long does it live?

Who is allowed to read it?

Who is allowed to modify it?

Who is responsible for freeing it?

These questions are the beginning of Zig’s memory model.

#### Memory Is Just Bytes

At the lowest level, memory is a long sequence of bytes.

A byte is usually 8 bits. Each byte has an address. You can think of memory like a huge row of numbered boxes:

```text
address:  1000  1001  1002  1003  1004  1005
value:      42     0    255     7    10    99
```

A program does not usually work with raw bytes directly. It works with typed values.

For example:

```zig
const age: u8 = 42;
```

This creates an unsigned 8-bit integer. It needs 1 byte.

```zig
const count: u32 = 1000;
```

This creates an unsigned 32-bit integer. It needs 4 bytes.

The type tells Zig how many bytes the value needs and how those bytes should be interpreted.

A `u8` and an `i8` both use 1 byte, but they are interpreted differently. A `u8` stores values from `0` to `255`. An `i8` stores values from `-128` to `127`.

Same bytes, different meaning.

#### Values Have Size

Every concrete type in Zig has a size.

You can ask Zig for the size of a type with `@sizeOf`:

```zig
const std = @import("std");

pub fn main() void {
    std.debug.print("u8  size = {}\n", .{@sizeOf(u8)});
    std.debug.print("u32 size = {}\n", .{@sizeOf(u32)});
    std.debug.print("i64 size = {}\n", .{@sizeOf(i64)});
}
```

Typical output:

```text
u8  size = 1
u32 size = 4
i64 size = 8
```

The unit is bytes.

This matters because memory use is not abstract in Zig. If you create an array of 1000 `u64` values, that array needs 8000 bytes, because each `u64` needs 8 bytes.

```zig
const numbers: [1000]u64 = undefined;
```

The type `[1000]u64` means “an array of 1000 unsigned 64-bit integers.”

Its size is:

```text
1000 * 8 = 8000 bytes
```

Zig wants you to see this clearly.

#### Stack Memory

The stack is memory used for function calls and local variables.

When a function is called, Zig creates space for that function’s local data. When the function returns, that space is no longer valid.

Example:

```zig
fn addOne(x: i32) i32 {
    const y = x + 1;
    return y;
}
```

Inside `addOne`, the parameter `x` and the local constant `y` live only while `addOne` is running.

After the function returns, they are gone.

This is normal stack memory behavior.

Stack memory is fast. It is also simple. You do not manually free stack memory. It is automatically reclaimed when the function exits.

Example:

```zig
pub fn main() void {
    const x: i32 = 10;
    const y: i32 = 20;
    const z = x + y;
    _ = z;
}
```

Here, `x`, `y`, and `z` are local values. They are stored in memory associated with `main`.

For beginners, the important rule is:

Local variables usually live only until the end of the block or function that contains them.

#### Blocks Create Lifetimes

A block is code inside `{}`.

```zig
pub fn main() void {
    const a = 10;

    {
        const b = 20;
        _ = b;
    }

    _ = a;
}
```

The variable `a` is visible until the end of `main`.

The variable `b` is visible only inside the inner block.

After the inner block ends, `b` cannot be used.

```zig
pub fn main() void {
    {
        const b = 20;
    }

    // This is invalid:
    // _ = b;
}
```

This is not only about names. It is also about lifetime. The storage for `b` belongs to the inner block.

Zig uses scopes to make lifetimes easier to reason about.

#### Heap Memory

The heap is memory used for data whose size or lifetime is not tied to one simple function call.

Stack memory is good when the compiler knows the size and lifetime clearly. Heap memory is useful when data must be created dynamically.

For example, suppose you do not know how many bytes you need until runtime. You can ask an allocator for memory:

```zig
const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    const allocator = gpa.allocator();

    const buffer = try allocator.alloc(u8, 1024);
    defer allocator.free(buffer);

    buffer[0] = 42;
}
```

This program allocates 1024 bytes on the heap.

The important lines are:

```zig
const buffer = try allocator.alloc(u8, 1024);
```

This asks the allocator for a slice of 1024 `u8` values.

```zig
defer allocator.free(buffer);
```

This frees the memory when the current scope exits.

Zig does not have a garbage collector that automatically finds unused heap memory. If you allocate memory, you must free it, or design your allocator so cleanup happens in one known place.

#### Stack vs Heap

Stack and heap memory are both memory. The difference is how they are managed.

| Memory kind | Typical use | Lifetime | Freed by |
|---|---:|---|---|
| Stack | Local values, function calls | Usually until function or block exits | Automatically |
| Heap | Dynamic data, large data, variable-sized data | Chosen by the program | Allocator/free logic |

Use the stack when the data is simple and local.

Use the heap when the data must be dynamically sized, returned, shared, or kept longer than one function call.

A fixed-size local array can live on the stack:

```zig
pub fn main() void {
    var data: [4]u8 = .{ 1, 2, 3, 4 };
    _ = data;
}
```

A dynamically allocated array lives on the heap:

```zig
const data = try allocator.alloc(u8, count);
defer allocator.free(data);
```

Here, `count` can be known only at runtime.

#### Pointers Are Addresses

A pointer stores the address of another value.

Example:

```zig
pub fn main() void {
    var x: i32 = 10;
    const p = &x;

    p.* = 20;
}
```

The expression `&x` means “the address of `x`.”

The variable `p` points to `x`.

The expression `p.*` means “the value that `p` points to.”

So this line:

```zig
p.* = 20;
```

changes `x` to `20`.

A pointer does not own memory by itself. It only refers to memory.

This distinction matters.

A value can exist. A pointer can point to it. But if the value stops existing, the pointer becomes invalid.

#### Dangling Pointers

A dangling pointer is a pointer that points to memory that is no longer valid.

Example:

```zig
fn badPointer() *i32 {
    var x: i32 = 123;
    return &x;
}
```

This is wrong.

The variable `x` is local to `badPointer`. When `badPointer` returns, `x` is gone. Returning `&x` would give the caller a pointer to dead stack memory.

The memory may still contain the old bytes for a moment, but it no longer belongs to `x`. Using that pointer would be unsafe.

Zig is designed to catch many mistakes, but the programmer must still understand lifetimes. Low-level programming requires care.

#### Slices Are Pointer Plus Length

A slice is a view into a sequence of values.

A slice contains:

| Part | Meaning |
|---|---|
| pointer | where the first item is |
| length | how many items are in the slice |

Example:

```zig
pub fn main() void {
    var data = [_]u8{ 10, 20, 30, 40 };

    const slice = data[1..3];

    _ = slice;
}
```

The slice `data[1..3]` refers to the values:

```text
20, 30
```

It starts at index `1` and stops before index `3`.

A slice does not copy the array. It views part of the same memory.

If you modify the slice, you modify the original array:

```zig
pub fn main() void {
    var data = [_]u8{ 10, 20, 30, 40 };

    const slice = data[1..3];
    slice[0] = 99;

    // data is now: 10, 99, 30, 40
}
```

This is important. Slices are convenient, but they still point into existing memory. The original memory must remain valid while the slice is used.

#### Ownership

Ownership means responsibility for memory.

Zig does not have a built-in ownership system like Rust. Instead, Zig relies on explicit rules, clear APIs, and programmer discipline.

When you write Zig code, you should decide who owns each piece of memory.

Example:

```zig
const buffer = try allocator.alloc(u8, 1024);
defer allocator.free(buffer);
```

Here, the current function owns `buffer`. It allocated the memory, so it must free the memory.

Now consider this function:

```zig
fn fill(buffer: []u8) void {
    for (buffer) |*byte| {
        byte.* = 0;
    }
}
```

This function receives a slice. It does not allocate the memory. It does not free the memory. It only uses the memory temporarily.

That is a common Zig pattern.

The caller owns the memory. The callee borrows it for a while.

#### Mutability

Zig separates mutable and immutable access.

```zig
const x = 10;
```

`x` cannot be changed.

```zig
var y = 10;
y = 20;
```

`y` can be changed.

This also affects pointers.

```zig
const x: i32 = 10;
const p = &x;
```

Here, `p` points to a constant value. You cannot modify `x` through `p`.

```zig
var y: i32 = 10;
const p = &y;
p.* = 20;
```

Here, `y` is mutable, so the pointer can be used to change it.

This helps make intent clear. If something should not change, use `const`.

#### Undefined Memory

Zig has a special value called `undefined`.

```zig
var x: i32 = undefined;
```

This means: reserve memory for `x`, but do not initialize it with a meaningful value.

Reading `x` before assigning a real value is a bug.

```zig
var x: i32 = undefined;

// Wrong:
_ = x;
```

But this is fine:

```zig
var x: i32 = undefined;
x = 42;
_ = x;
```

`undefined` is useful when you want to initialize memory later. But beginners should use it carefully. Prefer real initial values unless you have a specific reason.

#### Memory Has Alignment

Types often need to be stored at addresses that match their alignment.

For example, a `u32` is usually more efficient when stored at an address divisible by 4.

You can ask Zig for a type’s alignment:

```zig
const std = @import("std");

pub fn main() void {
    std.debug.print("u8  align = {}\n", .{@alignOf(u8)});
    std.debug.print("u32 align = {}\n", .{@alignOf(u32)});
    std.debug.print("u64 align = {}\n", .{@alignOf(u64)});
}
```

Alignment is a low-level detail, but it matters when working with pointers, packed structs, binary formats, C interop, and manual memory management.

For now, remember this:

A value has both a size and an alignment.

#### The Basic Memory Rules

For beginners, Zig memory can be understood with a small set of rules.

A value occupies bytes.

A type tells Zig how to interpret those bytes.

Local variables usually live on the stack.

Dynamically allocated memory usually lives on the heap.

Pointers store addresses.

Slices store a pointer and a length.

Allocated memory must be freed.

A pointer or slice must not outlive the memory it refers to.

`const` means the value cannot be changed through that binding.

`undefined` means memory exists, but its value is not meaningful yet.

These rules will appear again and again in Zig code.

#### A Complete Example

Here is a small program that uses stack memory, heap memory, a pointer, and a slice:

```zig
const std = @import("std");

pub fn main() !void {
    var stack_number: i32 = 10;
    const pointer = &stack_number;

    pointer.* = 20;

    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    const allocator = gpa.allocator();

    const heap_buffer = try allocator.alloc(u8, 4);
    defer allocator.free(heap_buffer);

    heap_buffer[0] = 1;
    heap_buffer[1] = 2;
    heap_buffer[2] = 3;
    heap_buffer[3] = 4;

    const view = heap_buffer[1..3];

    std.debug.print("stack_number = {}\n", .{stack_number});
    std.debug.print("view = {any}\n", .{view});
}
```

Output:

```text
stack_number = 20
view = { 2, 3 }
```

What happens here?

`stack_number` is a local variable. It lives on the stack.

`pointer` stores the address of `stack_number`.

`pointer.* = 20` changes `stack_number`.

`heap_buffer` is allocated on the heap.

`defer allocator.free(heap_buffer)` ensures the heap memory is freed later.

`view` is a slice into `heap_buffer`.

The slice does not own the memory. It only views part of it.

This small example contains the main ideas of Zig memory: values, addresses, stack memory, heap memory, allocation, freeing, and borrowed views into memory.

#### The Main Idea

Zig does not try to make memory disappear.

Instead, Zig gives you a clear way to talk about memory directly.

This can feel hard at first, because you must think about details that other languages hide. But that is also the strength of Zig. You can write programs where memory use, lifetime, and ownership are visible in the code.

When memory is visible, bugs become easier to reason about.

