# Packed Memory

### Packed Memory

A normal struct is laid out for efficient access.

```zig
const Point = struct {
    x: u8,
    y: u32,
};
```

The compiler may place padding between fields. Padding is unused space inserted to satisfy alignment.

A `u32` is usually aligned more strictly than a `u8`. Therefore this struct may occupy more than five bytes.

Packed memory uses an exact bit layout.

```zig
const Point = packed struct {
    x: u8,
    y: u32,
};
```

In a packed struct, fields are placed according to their declared bit sizes. This is useful when the program must match an external representation.

Common cases are hardware registers, network headers, file formats, instruction encodings, and binary protocols.

A packed struct can describe bit fields.

```zig
const Control = packed struct {
    enable: bool,
    mode: u3,
    reserved: u4,
};
```

This struct occupies one byte. The fields use exactly eight bits:

```text
enable   1 bit
mode     3 bits
reserved 4 bits
```

A value can be used like any other struct value.

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

const Control = packed struct {
    enable: bool,
    mode: u3,
    reserved: u4,
};

pub fn main() void {
    var c = Control{
        .enable = true,
        .mode = 5,
        .reserved = 0,
    };

    std.debug.print("{} {d}\n", .{ c.enable, c.mode });
}
```

The type `u3` is a three-bit unsigned integer. It stores values from `0` to `7`.

Packed structs make small integer widths practical.

```zig
const Header = packed struct {
    version: u4,
    length: u12,
    flags: u8,
};
```

This layout uses 24 bits.

The exact layout matters. The program is now coupled to the order and width of every field.

Packed memory is not the same as portable serialization.

If a file format defines byte order, the program must still handle byte order explicitly.

For many formats, byte-level code is clearer.

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

pub fn main() void {
    const bytes = [_]u8{ 0x34, 0x12 };

    const x = std.mem.readInt(u16, bytes[0..2], .little);

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

This says plainly that the integer is stored little-endian.

A packed struct says how fields occupy memory. It does not make every binary format safe to read by casting a pointer.

Alignment must still be respected.

```zig
const Header = packed struct {
    tag: u8,
    length: u32,
};

const ptr: *Header = @ptrCast(@alignCast(raw_ptr));
```

The cast asserts that `raw_ptr` points to a valid `Header` at a valid alignment.

For byte streams, copying and decoding is often safer than pointer casting.

Packed structs are especially useful for memory-mapped registers.

```zig
const Status = packed struct {
    ready: bool,
    error_flag: bool,
    code: u6,
};

const status: *volatile Status =
    @ptrFromInt(0x4000_0004);
```

Reading:

```zig
if (status.ready) {
    // device is ready
}
```

accesses the register as a structured value.

This is much clearer than manual masks:

```zig
const raw = reg.*;
const ready = (raw & 0x01) != 0;
```

Manual masks are still useful when the representation is complex or shared with C code.

A packed struct can also have a backing integer type.

```zig
const Flags = packed struct(u8) {
    read: bool,
    write: bool,
    execute: bool,
    reserved: u5,
};
```

The backing type says the whole struct is represented as a `u8`.

This makes the intended size explicit.

Packed memory has costs.

Accessing packed fields may require masking and shifting.

Taking pointers to packed fields can be restricted or require special pointer types.

Unaligned loads may be slower or invalid on some targets.

The benefit is control. The cost is responsibility.

Use ordinary structs for ordinary program data.

```zig
const User = struct {
    id: u64,
    name: []const u8,
};
```

Use packed structs when the memory layout itself is part of the problem.

```zig
const TcpFlags = packed struct(u8) {
    fin: bool,
    syn: bool,
    rst: bool,
    psh: bool,
    ack: bool,
    urg: bool,
    ece: bool,
    cwr: bool,
};
```

The rule is simple:

| Need | Use |
|---|---|
| Normal data model | `struct` |
| Exact bit layout | `packed struct` |
| Tagged alternatives | `union(enum)` |
| Raw byte format | explicit byte parsing |

Packed memory is a low-level tool. It should appear at the boundary of the program, where Zig meets hardware, wire formats, or stored binary data.

Exercise 19-21. Define a packed struct that fits in one byte and contains three fields.

Exercise 19-22. Add a backing integer type to the struct from Exercise 19-21.

Exercise 19-23. Decode a two-byte little-endian integer using `std.mem.readInt`.

Exercise 19-24. Explain why an ordinary `struct` should be preferred for normal application data.

