🎓 A1Learner
🔍
0 of 46 lessons completed

Zig Undefined Behavior

Undefined behavior is behavior with no guaranteed result. A program that reaches it may do anything, so such code must never be relied on.

Wrap on purpose

The wrapping operator +% wraps a result that is too large. Here 250 plus 10 wraps to 4 in a u8.

const std = @import("std");
fn wrapAdd(a: u8, b: u8) u8 {
    return a +% b;
}
pub fn main() !void {
    const out = std.io.getStdOut().writer();
    const a: u8 = 250;
    const b: u8 = 10;
    try out.print("{d}\n", .{wrapAdd(a, b)});
}

Report overflow instead

@addWithOverflow returns the wrapped result and a flag. Here the flag says overflow, so the program knows overflow happened.

const std = @import("std");
pub fn main() !void {
    const out = std.io.getStdOut().writer();
    const a: u8 = 250;
    const checked = @addWithOverflow(a, @as(u8, 100));
    const status = if (checked[1] == 1) "overflow" else "ok";
    try out.print("{d} {s}\n", .{ checked[0], status });
}

Stay inside the array

Only indexes inside an array have a guaranteed result. Index 2 is inside this array, so the program prints 21.

const std = @import("std");
pub fn main() !void {
    const out = std.io.getStdOut().writer();
    const data = [_]u32{ 7, 14, 21, 28 };
    try out.print("{d}\n", .{data[2]});
}

Example

const std = @import("std");
fn wrapAdd(a: u8, b: u8) u8 {
    return a +% b;
}
pub fn main() !void {
    const out = std.io.getStdOut().writer();
    const wrapped = wrapAdd(200, 100);
    const checked = @addWithOverflow(@as(u8, 200), @as(u8, 100));
    const small = @addWithOverflow(@as(u8, 40), @as(u8, 2));
    const status = if (checked[1] == 1) "overflow" else "ok";
    const small_status = if (small[1] == 1) "overflow" else "ok";
    try out.print("{d} {d} {s} {d} {s}\n", .{ wrapped, checked[0], status, small[0], small_status });
}

Key points

  • Undefined behavior: undefined behavior is program behavior that the language gives no guaranteed result for.
  • Wrapping operator: a wrapping operator is an operator such as +% that wraps a result instead of overflowing.
  • Checked addition: checked addition is addition that reports overflow through @addWithOverflow instead of overflowing.
  • Out-of-bounds access: an out-of-bounds access is an index outside an array, and it is undefined behavior when safety checks are off.
  • Dangling pointer: a dangling pointer is a pointer to memory that is no longer valid, and using it is undefined behavior.
  • Defined program: a defined program is a program in which every operation has a guaranteed result.

The wrapping version gives 44 on purpose. The checked version gives the same 44 but also reports overflow. The small addition gives 42 with no overflow, so all three results are defined.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

Which operator wraps a u8 addition on purpose?