Skip to content
zig 版本:0.13.0

未定义操作

zig 本身有许多未定义行为,它们可以很方便地帮助开发者找出错误。

如果在编译期就检测到了未定义的行为,那么 zig 会发出编译错误并停止继续编译,大多数编译时无法检测到的未定义行为均会在运行时被检测到。这就是 zig 的安全检查!

WARNING

注意:本章节并没有 CI 检查,故可能存在内容过期的情况,具体可参考 官方手册

安全检查会在debug、ReleaseSafe 模式下开启,但可以使用 @setRuntimeSafety 来强制指定在单独的块中是否开启安全检查(这将忽略构建模式)。

当出现安全检查失败时,zig 会编译失败并触发堆栈跟踪:

zig
test "safety check" {
    unreachable;
}
sh
$ zig test test_undefined_behavior.zig
1/1 test.safety check... thread 892159 panic: reached unreachable code
/home/ci/actions-runner/_work/zig-bootstrap/zig/docgen_tmp/test_undefined_behavior.zig:2:5: 0x222c65 in test.safety check (test)
    unreachable;
    ^
/home/ci/actions-runner/_work/zig-bootstrap/out/host/lib/zig/test_runner.zig:181:28: 0x22da7d in mainTerminal (test)
        } else test_fn.func();
                           ^
/home/ci/actions-runner/_work/zig-bootstrap/out/host/lib/zig/test_runner.zig:36:28: 0x223c8a in main (test)
        return mainTerminal();
                           ^
/home/ci/actions-runner/_work/zig-bootstrap/out/host/lib/zig/std/start.zig:575:22: 0x22319c in posixCallMainAndExit (test)
            root.main();
                     ^
/home/ci/actions-runner/_work/zig-bootstrap/out/host/lib/zig/std/start.zig:253:5: 0x222cf1 in _start (test)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
error: the following test command crashed:
/home/ci/actions-runner/_work/zig-bootstrap/out/zig-local-cache/o/4558e134302b78f1a543202d094b3e06/test

以下说明各种未定义行为。

不可达代码

unreachable,如果程序执行到它,那么会触发完整的堆栈跟踪!

索引越界访问

无论是数组还是切片,发生越界访问会发生错误导致程序终止进而触发堆栈跟踪!

负数转换为无符号整数

这本身就是非法行为,故会直接出现报错,如果仅仅是想要将负数当作无符号整数看待,可以使用 @bitCast

如果想要获取到无符号整数的最大值,可以使用 std.math.maxInt

数据截断

注意我们这里指的是数据类型的范围变小了,不足以容纳数据的值,例如:

zig
const spartan_count: u16 = 300;
const byte: u8 = @intCast(spartan_count);

上面这段代码毫无疑问会失败,因为 u8 类型无法容纳下 300 这个数。

除非,我们显式强制截断位,使用 @truncate

整数溢出

常规的运算可能导致溢出,如加 +-*/ 取反 - 运算可能出现溢出。

还有 @divTrunc@divFloor@divExact,可能造成溢出。

标准库提供的函数可能存在溢出:

  • @import("std").math.add
  • @import("std").math.sub
  • @import("std").math.mul
  • @import("std").math.divTrunc
  • @import("std").math.divFloor
  • @import("std").math.divExact
  • @import("std").math.shl

为了处理这些情况,zig 提供了几个溢出检测函数来处理溢出问题:

以上这些内建函数会返回一个元组,包含计算的结果和是否发生溢出的判断位。

zig
const print = @import("std").debug.print;
pub fn main() void {
    const byte: u8 = 255;

    const ov = @addWithOverflow(byte, 10);
    if (ov[1] != 0) {
        print("overflowed result: {}\n", .{ov[0]});
    } else {
        print("result: {}\n", .{ov[0]});
    }
}

除此以外,我们还可以使用环绕(Wrapping)操作来处理计算:

  • +% 加法环绕
  • -% 减法环绕
  • -% 取否环绕
  • *% 乘法环绕

它们会取计算后溢出的值!

移位溢出

进行左移操作时,可能导致结果溢出,此时程序或者编译器会停止并发出警告!

除零操作

很显然,除零是非法操作,故会引起程序或者编译器报错!

当然,还包括求余运算,除数为零是也是非法的!

精确除法溢出

精确除法使用的是 @divExact,它需要保证被除数可以整除除数,否则会触发编译器错误!

尝试解开可选类型 Null

可选类型值是 null 时,如果直接使用 variable.? 语法来解开可选,那么会导致出现错误!

正确的处理方案是使用 if 语法来解开可选类型。

尝试解开错误联合类型 Error

错误联合类型如果是 error 时,直接使用它会导致程序或者编译器停止运行!

正确的处理方案是使用 if 语法来解开可选类型。

无效错误码

使用 @errorFromInt 获取错误时,如果没有对应整数的错误,那么会导致程序或编译器报错!

无效枚举转换

当使用 @enumFromInt 来获取枚举时,如果没有对应整数的枚举,那么会导致程序或者编译器报告错误!

无效错误集合转换

两个不相关的错误集不可以相互转换,如果强制使用 @errorCast转换两个不相关的错误集,那么会导致程序或者编译器报告错误!

指针对齐错误

指针对齐转换可能发生错误,如:

zig
const ptr: *align(1) i32 = @ptrFromInt(0x1);
const aligned: *align(4) i32 = @alignCast(ptr);

0x1 地址很明显是不符合 4 字节对齐,会导致编译器错误。

联合类型字段访问错误

如果访问的联合类型字段并非是它当前的有效字段,那么会触发非法行为!

可以通过重新分配来更改联合类型的有效字段:

zig
const Foo = union {
    float: f32,
    int: u32,
};

var f = Foo{ .int = 42 };
f = Foo{ .float = 12.34 };

🅿️ 提示

注意:packed 和 extern 标记的联合类型并没有这种安全监测!

浮点转换整数发生越界

当将浮点数转换为整数时,如果浮点数的值超出了整数类型的范围,就会发生非法越界,例如:

zig
const float: f32 = 4294967296;
const int: i32 = @intFromFloat(float);

指针强制转换为 Null

将允许地址为 0 的指针转换为地址不可能为 0 的指针,这会触发非法行为。

C 指针、可选指针、allowzero 标记的指针,这些都是允许地址为 0,但普通指针是不允许的。