# Memory Debugging

### Memory Debugging

Memory debugging means finding mistakes in how a program uses memory.

In Zig, memory bugs usually come from one of these problems:

using memory after it has been freed

freeing the same memory twice

forgetting to free allocated memory

reading or writing past the end of a slice

returning a pointer to memory that no longer exists

using an allocator incorrectly

Zig gives you tools and habits that make these bugs easier to find.

#### Start with the Testing Allocator

In tests, use `std.testing.allocator`.

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

test "allocate and free memory" {
    const allocator = std.testing.allocator;

    const bytes = try allocator.alloc(u8, 16);
    defer allocator.free(bytes);

    try std.testing.expectEqual(@as(usize, 16), bytes.len);
}
```

This allocator is useful in tests because it can help detect leaks.

The rule is simple:

Every allocation must have a matching cleanup, unless ownership is clearly transferred.

#### A Memory Leak

A leak happens when you allocate memory and never free it.

Bad:

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

test "leaks memory" {
    const allocator = std.testing.allocator;

    const bytes = try allocator.alloc(u8, 16);
    _ = bytes;
}
```

The allocation is never freed.

Fix it with `defer`:

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

test "does not leak memory" {
    const allocator = std.testing.allocator;

    const bytes = try allocator.alloc(u8, 16);
    defer allocator.free(bytes);

    _ = bytes;
}
```

`defer` is one of the most important tools for memory cleanup in Zig.

#### Use `errdefer` for Error Paths

Sometimes cleanup is needed only if the function fails.

Use `errdefer`.

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

fn makeBuffer(allocator: std.mem.Allocator) ![]u8 {
    const bytes = try allocator.alloc(u8, 1024);
    errdefer allocator.free(bytes);

    // More work could fail here.

    return bytes;
}
```

If the function returns an error after allocation, `errdefer` frees the memory.

If the function succeeds, ownership of `bytes` is returned to the caller.

The caller must then free it:

```zig
test "makeBuffer returns owned memory" {
    const allocator = std.testing.allocator;

    const bytes = try makeBuffer(allocator);
    defer allocator.free(bytes);

    try std.testing.expectEqual(@as(usize, 1024), bytes.len);
}
```

This pattern is common in Zig APIs.

#### Out-of-Bounds Access

A slice has a length.

You may only access valid indexes.

```zig
const values = [_]i32{ 10, 20, 30 };
const slice = values[0..];

const x = slice[3];
_ = x;
```

This is wrong.

Valid indexes are:

```text
0
1
2
```

The index `3` is one past the end.

The correct last index is:

```zig
slice.len - 1
```

So this is valid when the slice is not empty:

```zig
const x = slice[slice.len - 1];
```

Always check empty slices before using `slice.len - 1`:

```zig
fn last(items: []const i32) ?i32 {
    if (items.len == 0) return null;
    return items[items.len - 1];
}
```

#### Use Slices Instead of Raw Pointers

For beginner code, prefer slices.

A slice carries both:

a pointer to memory

a length

That length helps Zig check bounds.

Prefer this:

```zig
fn sum(items: []const i32) i32 {
    var total: i32 = 0;

    for (items) |item| {
        total += item;
    }

    return total;
}
```

Avoid this unless you really need it:

```zig
fn sum(ptr: [*]const i32, len: usize) i32 {
    var total: i32 = 0;
    var i: usize = 0;

    while (i < len) : (i += 1) {
        total += ptr[i];
    }

    return total;
}
```

The slice version is safer and clearer.

#### Use After Free

Use after free means using memory after giving it back to the allocator.

Bad:

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

test "use after free" {
    const allocator = std.testing.allocator;

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

    bytes[0] = 42;
}
```

After `allocator.free(bytes)`, the program no longer owns that memory.

The fix is simple:

Do not use a slice after freeing it.

A good pattern is to free at the end of the scope:

```zig
const bytes = try allocator.alloc(u8, 4);
defer allocator.free(bytes);

bytes[0] = 42;
```

With `defer`, the cleanup happens after normal use.

#### Double Free

Double free means freeing the same allocation twice.

Bad:

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

test "double free" {
    const allocator = std.testing.allocator;

    const bytes = try allocator.alloc(u8, 4);

    allocator.free(bytes);
    allocator.free(bytes);
}
```

The memory is returned twice.

This is a serious ownership bug.

A good rule:

The code that owns memory frees it exactly once.

#### Ownership

Ownership answers this question:

Who is responsible for freeing this memory?

Example:

```zig
fn makeName(allocator: std.mem.Allocator) ![]u8 {
    const name = try allocator.dupe(u8, "zig");
    return name;
}
```

This function returns allocated memory.

The caller owns it:

```zig
test "makeName returns owned memory" {
    const allocator = std.testing.allocator;

    const name = try makeName(allocator);
    defer allocator.free(name);

    try std.testing.expectEqualStrings("zig", name);
}
```

Document ownership through code shape.

If a function returns allocated memory, the caller usually frees it.

If a function only borrows memory, it should not free it.

#### Borrowed Memory

Borrowed memory means the function can use the memory but does not own it.

```zig
fn printName(name: []const u8) void {
    std.debug.print("{s}\n", .{name});
}
```

This function receives a slice.

It does not allocate it.

It does not free it.

It only reads it.

That is borrowed memory.

#### Returning Pointers to Local Data

Do not return a pointer to local stack data.

Bad:

```zig
fn bad() []const u8 {
    var buffer = [_]u8{ 'z', 'i', 'g' };
    return buffer[0..];
}
```

`buffer` lives only while `bad` is running.

After `bad` returns, that memory is no longer valid.

Correct options:

Return a string literal:

```zig
fn name() []const u8 {
    return "zig";
}
```

Or allocate memory and return ownership:

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

fn makeName(allocator: std.mem.Allocator) ![]u8 {
    return try allocator.dupe(u8, "zig");
}
```

#### Initialize Before Reading

Do not read uninitialized memory.

Bad:

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

pub fn main() void {
    var x: i32 = undefined;
    std.debug.print("{}\n", .{x});
}
```

`undefined` means “this value is not initialized.”

Use it only when you will assign a valid value before reading:

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

std.debug.print("{}\n", .{x});
```

For beginners, avoid `undefined` until you understand why you need it.

#### Debugging Memory with Small Tests

When you suspect a memory bug, write a small test.

Instead of debugging a large program, reduce the problem.

Bad starting point:

```text
The whole parser sometimes crashes.
```

Better starting point:

```text
This function crashes when input is empty.
```

Write the smallest test:

```zig
test "last returns null for empty input" {
    const values = [_]i32{};
    try std.testing.expectEqual(null, last(values[0..]));
}
```

Small tests make memory bugs easier to isolate.

#### Practical Checklist

When debugging memory, ask these questions:

Who allocated this memory?

Who owns it?

Who frees it?

Can it be freed twice?

Can it be used after free?

Does the slice length match the actual data?

Can the input be empty?

Is any value read before initialization?

Most Zig memory bugs become clearer when you answer those questions.

#### Complete Example

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

fn copyText(allocator: std.mem.Allocator, text: []const u8) ![]u8 {
    const copy = try allocator.dupe(u8, text);
    return copy;
}

test "copyText returns an owned copy" {
    const allocator = std.testing.allocator;

    const copied = try copyText(allocator, "hello");
    defer allocator.free(copied);

    try std.testing.expectEqualStrings("hello", copied);
}
```

This example has clear ownership:

`copyText` allocates memory.

`copyText` returns ownership.

The test receives ownership.

The test frees the memory.

That is the core habit of memory debugging in Zig: make ownership visible, keep lifetimes small, and clean up exactly once.

