forum icon indicating copy to clipboard operation
forum copied to clipboard

【Zig 日报】2024-03-31 Zig 中的类型对齐

Open jiacai2050 opened this issue 1 year ago • 0 comments

在 Zig 中,每种类型都有一个对齐方式,当从内存加载或向内存存储该类型的值时,内存地址必须能被这个数平均整除。可以使用 @alignOf 查找任何类型的对齐方式。比如 @alignOf(u32) 会返回 4。

对齐取决于 CPU 架构,但始终是 2 的幂次,且小于 1 << 29

在 Zig 中,指针类型有一个对齐值。如果该值等于底层类型的对齐方式,则可以从类型中省略:

const std = @import("std");
const builtin = @import("builtin");
const expect = std.testing.expect;

test "variable alignment" {
    var x: i32 = 1234;
    const align_of_i32 = @alignOf(@TypeOf(x));
    try expect(@TypeOf(&x) == *i32);
    try expect(*i32 == *align(align_of_i32) i32);
    if (builtin.target.cpu.arch == .x86_64) {
        try expect(@typeInfo(*i32).Pointer.alignment == 4);
    }
}

从上面这句话可以反推出来,如果指针的对齐和底层类型不一致,则需要显式定义,怎么显式定义呢?答案是:在定义变量或函数时指定,这样指向他们的指针自然就是该对齐方式了。

test "set pointer alignment" {
    const foo: u32 = 0x11223344;
    const p = &foo;
    try std.testing.expectEqual(@TypeOf(p), *align(4) const u32);

    const foo2: u32 align(64) = 0x11223344;
    const p2 = &foo2;
    try std.testing.expectEqual(@TypeOf(p2), *align(64) const u32);
}

稍微利用一点数学知识我们可以知道,大的对齐方式,可以转成小的,但反之则不一定。 @alignCast 就是干这事的,它会在编译期进行安全检查,不会有运行时开销。

test "testBadAlignCast" {
    const foo: u32 = 0x11223344;
    const p = &foo;
    try std.testing.expectEqual(@TypeOf(p), *align(4) const u32);

    const p2: *align(1 << 15) const u32 = @alignCast(p);
    try std.testing.expectEqual(@TypeOf(p2), *align(1 << 15) const u32);
    std.debug.print("{x}\n", .{p2.*});
    std.debug.print("{*}\n", .{p2});
}

上面这个例子强制把对齐为 4 的 p 转成对齐是 32768 的 p2,测试竟然通过了,通过 p2.* 打印,值也是对的

11223344
p2 addr: u32@10051a500

但其地址明明不是 32768 的整数倍!(0x10051a500 / (1<<15) = 131235.2890625),这应该是 Zig 的 bug?

Zig 官网给了另一种对齐失败的例子,这个是能检测出来的:

test "alignCastPanic" {
    const array = [_]u32{ 0x11223344, 0x55667788 };
    const bytes = mem.sliceAsBytes(array[0..]);
    const slice4 = bytes[1..5];
    const int_slice = mem.bytesAsSlice(u32, @as([]align(4) const u8, @alignCast(slice4)));

    try std.testing.expectEqual(@TypeOf(int_slice), *align(4) const u32);
}

报错信息如下:

3/3 align-demo.test.alignCastFail... thread 7925321 panic: incorrect alignment
/Users/jiacai/.asdf/installs/zig/master/lib/compiler/align-demo.zig:37:70: 0x102dd92fb in test.alignCastFail (test)
/Users/jiacai/.asdf/installs/zig/master/lib/compiler/test_runner.zig:158:25: 0x102de2b0b in mainTerminal (test)
        if (test_fn.func()) |_| {

可见 Zig 只有在这种十分明确的情况下(slice4 相当于向前移动了一位,地址肯定是奇数),才能检查出来对齐失败。

jiacai2050 avatar Mar 31 '24 12:03 jiacai2050