# Undefined Behavior

### Undefined Behavior

Zig gives the programmer direct access to memory, integers, pointers, and machine operations. This makes many programs simple and efficient. It also makes some operations dangerous.

An operation whose result is not defined by the language is called undefined behavior.

Undefined behavior means the compiler is allowed to assume the operation never happens. If it does happen, the result is unpredictable. The program may crash. It may appear to work. It may corrupt memory silently.

A simple example is reading past the end of an array:

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

pub fn main() void {
    const values = [_]u8{ 10, 20, 30 };

    std.debug.print("{d}\n", .{values[5]});
}
```

The array has three elements:

```zig
[_]u8{ 10, 20, 30 }
```

Valid indexes are `0`, `1`, and `2`.

The expression:

```zig
values[5]
```

reads memory outside the array.

In safe build modes, Zig detects this and stops the program:

```text
panic: index out of bounds
```

In unsafe modes, the compiler may remove checks and assume the index is valid.

The important point is not whether the program crashes. The important point is that the language no longer defines what happens.

Integer overflow is another example.

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

pub fn main() void {
    var x: u8 = 255;
    x += 1;

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

A `u8` stores values from `0` to `255`.

Adding `1` to `255` exceeds the range.

In safe modes, Zig detects the overflow:

```text
panic: integer overflow
```

If wrapping behavior is wanted, it must be requested explicitly:

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

pub fn main() void {
    var x: u8 = 255;
    x +%= 1;

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

The operator:

```zig
+%=
```

means wrapping addition.

The result is:

```text
0
```

Zig separates checked arithmetic from wrapping arithmetic. Overflow is not silently ignored.

Pointer misuse is another source of undefined behavior.

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

pub fn main() void {
    var x: i32 = 10;

    const ptr: *i32 = &x;

    ptr.* = 20;

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

This is valid. `ptr` points to a real object.

But a pointer can also be created incorrectly:

```zig
const bad: *i32 = @ptrFromInt(1);
```

This creates a pointer from an arbitrary integer.

Dereferencing such a pointer is undefined unless the address is actually valid memory of the correct type and alignment.

Many undefined operations involve assumptions about memory layout.

For example:

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

const S = struct {
    a: u8,
    b: u32,
};

pub fn main() void {
    var s = S{
        .a = 1,
        .b = 2,
    };

    const p: *u32 = @ptrCast(&s.a);

    std.debug.print("{d}\n", .{p.*});
}
```

The address of `s.a` is not necessarily aligned for `u32`.

Dereferencing `p` may fail on some systems.

Alignment matters because many CPUs require certain values to begin at certain memory boundaries.

Zig checks alignment in safe modes.

Undefined behavior also appears when using uninitialized memory.

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

pub fn main() void {
    var x: i32 = undefined;

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

The value of `x` is undefined.

The program must assign a real value before reading it.

`undefined` is not zero. It is not random. It means the value is unspecified and should not be used.

Zig deliberately exposes undefined behavior instead of hiding it behind implicit conversions or silent runtime rules.

The language follows a simple principle:

> Operations that are safe should work directly.
>
> Operations that are unsafe should be explicit.

This is why Zig distinguishes:

| Operation | Meaning |
|---|---|
| `+` | Checked addition |
| `+%` | Wrapping addition |
| `@ptrCast` | Explicit pointer reinterpretation |
| `undefined` | Uninitialized value |
| `@alignCast` | Explicit alignment assertion |

The compiler assumes that undefined behavior never occurs. This allows better optimization and simpler generated code.

For example, if a loop index is guaranteed to stay inside array bounds, the compiler can remove repeated bounds checks.

The programmer gets both performance and safety checks during development.

The usual approach is:

| Build mode | Purpose |
|---|---|
| Debug | Maximum safety checking |
| ReleaseSafe | Optimized with safety checks |
| ReleaseFast | Maximum optimization |
| ReleaseSmall | Smaller binaries |

During development, safety checks help detect mistakes early. In production, the programmer decides how much checking to keep.

Undefined behavior cannot be eliminated from systems programming. Zig instead tries to make dangerous operations visible, explicit, and locally understandable.

Exercise 19-1. Write a program that triggers integer overflow with `+`, then rewrite it using `+%`.

Exercise 19-2. Create an array with four elements and intentionally access index `4`. Observe the runtime behavior in Debug mode.

Exercise 19-3. Declare a variable with `undefined`, assign a value later, and print the final value.

Exercise 19-4. Use `@ptrFromInt` to construct a pointer. Explain why dereferencing it is dangerous.

