정렬
정렬 (Alignment)
모든 타입에는 정렬(alignment) 이라는 값이 있어요. 이건 그 타입의 값을 메모리에서 읽거나 쓸 때, 해당 메모리 주소가 몇 바이트 단위로 나누어떨어져야 하는지를 나타내는 수예요. 어떤 타입의 정렬 값이 궁금하다면 @alignOf를 쓰면 쉽게 알아낼 수 있어요.
본문
정렬 값은 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 — 정렬이 어긋난 포인터를 다룰 때의 안전 검사와 위반 사례