정렬

정렬 (Alignment)

모든 타입에는 정렬(alignment) 이라는 값이 있어요. 이건 그 타입의 값을 메모리에서 읽거나 쓸 때, 해당 메모리 주소가 몇 바이트 단위로 나누어떨어져야 하는지를 나타내는 수예요. 어떤 타입의 정렬 값이 궁금하다면 @alignOf를 쓰면 쉽게 알아낼 수 있어요.

출처: Zig Documentation

본문

정렬 값은 CPU 아키텍처에 따라 달라질 수 있지만, 항상 2의 거듭제곱이면서 1 << 29보다는 작아요.

포인터 타입은 정렬을 바이트 단위로 명시할 수 있어요. 명시하지 않으면, 그 포인터가 가리키는 타입의 정렬과 같다고 간주돼요.

test_variable_alignment.zig 예제를 볼게요. 별도로 정렬을 지정하지 않은 변수의 포인터는 그 타입의 정렬 값을 그대로 따르는지, 직접 확인해요.

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

test "variable alignment" {
    var x: i32 = 1234;

    try expectEqual(*i32, @TypeOf(&x));

    try expect(@intFromPtr(&x) % @alignOf(i32) == 0);

    // The implicitly-aligned pointer can be coerced to be explicitly-aligned to
    // the alignment of the underlying type `i32`:
    const ptr: *align(@alignOf(i32)) i32 = &x;

    try expectEqual(1234, ptr.*);
}
$ zig test test_variable_alignment.zig
1/1 test_variable_alignment.test.variable alignment...OK
All 1 tests passed.

*i32*const i32강제 변환할 수 있는 것과 같은 방식으로, 더 큰 정렬을 가진 포인터는 더 작은 정렬을 가진 포인터로 암시적으로 캐스트될 수 있어요. 물론 그 반대는 안 되고요.

변수와 함수에도 정렬을 지정할 수 있어요. 이렇게 하면 그 대상에 대한 포인터들이 지정된 정렬을 갖게 돼요.

const expectEqual = @import("std").testing.expectEqual;

var foo: u8 align(4) = 100;

test "global variable alignment" {
    try expectEqual(4, @typeInfo(@TypeOf(&foo)).pointer.attrs.@"align");
    try expectEqual(*align(4) u8, @TypeOf(&foo));
    const as_pointer_to_array: *align(4) [1]u8 = &foo;
    const as_slice: []align(4) u8 = as_pointer_to_array;
    const as_unaligned_slice: []u8 = as_slice;
    try expectEqual(100, as_unaligned_slice[0]);
}

fn derp() align(@sizeOf(usize) * 2) i32 {
    return 1234;
}
fn noop1() align(1) void {}
fn noop4() align(4) void {}

test "function alignment" {
    try expectEqual(1234, derp());
    try expectEqual(fn () i32, @TypeOf(derp));
    try expectEqual(*align(@sizeOf(usize) * 2) const fn () i32, @TypeOf(&derp));

    noop1();
    try expectEqual(fn () void, @TypeOf(noop1));
    try expectEqual(*align(1) const fn () void, @TypeOf(&noop1));

    noop4();
    try expectEqual(fn () void, @TypeOf(noop4));
    try expectEqual(*align(4) const fn () void, @TypeOf(&noop4));
}
$ zig test test_variable_func_alignment.zig
1/2 test_variable_func_alignment.test.global variable alignment...OK
2/2 test_variable_func_alignment.test.function alignment...OK
All 2 tests passed.

가지고 있는 포인터나 슬라이스의 정렬이 작은데, 실제로는 더 큰 정렬을 가진다고 알고 있다면 @alignCast를 써서 더 정렬된 포인터로 바꿔요. 이것은 런타임에서는 아무 일도 하지 않지만, 정렬 안전 검사를 넣어줘요.

const std = @import("std");

test "pointer alignment safety" {
    var array align(4) = [_]u32{ 0x11111111, 0x11111111 };
    const bytes = std.mem.sliceAsBytes(array[0..]);
    try std.testing.expectEqual(0x11111111, foo(bytes));
}
fn foo(bytes: []u8) u32 {
    const slice4 = bytes[1..5];
    const int_slice = std.mem.bytesAsSlice(u32, @as([]align(4) u8, @alignCast(slice4)));
    return int_slice[0];
}
$ zig test test_incorrect_pointer_alignment.zig
1/1 test_incorrect_pointer_alignment.test.pointer alignment safety...thread 975727 panic: incorrect alignment
/home/ci/work/zig-bootstrap/zig/doc/langref/test_incorrect_pointer_alignment.zig:10:68: 0x1253b96 in foo (test_incorrect_pointer_alignment.zig)
    const int_slice = std.mem.bytesAsSlice(u32, @as([]align(4) u8, @alignCast(slice4)));
                                                                   ^
/home/ci/work/zig-bootstrap/zig/doc/langref/test_incorrect_pointer_alignment.zig:6:48: 0x12539ce in test.pointer alignment safety (test_incorrect_pointer_alignment.zig)
    try std.testing.expectEqual(0x11111111, foo(bytes));
                                               ^
/home/ci/work/zig-bootstrap/out/host/lib/zig/compiler/test_runner.zig:295:25: 0x1207381 in mainTerminal (test_runner.zig)
        if (test_fn.func()) |_| {
                        ^
/home/ci/work/zig-bootstrap/out/host/lib/zig/compiler/test_runner.zig:74:28: 0x1206b52 in main (test_runner.zig)
        return mainTerminal(init);
                           ^
/home/ci/work/zig-bootstrap/out/host/lib/zig/std/start.zig:789:88: 0x12032af in callMain (std.zig)
    if (fn_info.param_types[0].? == std.process.Init.Minimal) return wrapMain(root.main(.{
                                                                                       ^
/home/ci/work/zig-bootstrap/out/host/lib/zig/std/start.zig:248:5: 0x1202c81 in _start (std.zig)
    asm volatile (switch (native_arch) {
    ^
error: the following test command terminated with signal ABRT:
/home/ci/work/zig-bootstrap/out/zig-local-cache/o/576c866ce90db702b7dc4eb04c165766/test --seed=0x7d8a90dd

더 알아보기 (Learn more)

  • @alignOf — 어떤 타입의 정렬 값을 알아내는 내장 함수
  • @alignCast — 포인터를 더 큰 정렬의 포인터로 바꾸는 내장 함수
  • Incorrect Pointer Alignment — 정렬이 어긋난 포인터를 다룰 때의 안전 검사와 위반 사례