잘못된 포인터 정렬

잘못된 포인터 정렬 (Incorrect Pointer Alignment)

@alignCast를 쓰면 정렬 안전 검사가 들어간다고 했죠. 그 검사가 언제 발동하고 정확히 무슨 일을 하는지가 이번 섹션의 핵심이에요. 정렬이 어긋난 포인터를 만들려고 하면, 시점에 따라 두 가지 방식으로 잡혀요.

출처: Zig Documentation

본문

먼저 컴파일 타임(compile-time) 에 걸리는 경우예요. @ptrFromInt로 주소 0x1을 가리키는 *align(1) i32 포인터를 만들고, 이걸 @alignCast*align(4) i32로 바꿔 볼게요.

comptime {
    const ptr: *align(1) i32 = @ptrFromInt(0x1);
    const aligned: *align(4) i32 = @alignCast(ptr);
    _ = aligned;
}
$ zig test test_comptime_incorrect_pointer_alignment.zig
/home/ci/work/zig-bootstrap/zig/doc/langref/test_comptime_incorrect_pointer_alignment.zig:3:47: error: pointer address 0x1 is not aligned to 4 bytes
    const aligned: *align(4) i32 = @alignCast(ptr);
                                              ^~~

주소 0x1은 4로 나누어떨어지지 않으니(1 % 4 = 1) 컴파일조차 되지 않아요. 같은 실수를 런타임(runtime) 에 저지르면 이번엔 안전 검사가 패닉을 띄워요. mem.bytesAsSlice로 정렬이 어긋난 위치에서 u32 슬라이스를 만드는 예시를 볼게요.

const mem = @import("std").mem;
pub fn main() !void {
    var array align(4) = [_]u32{ 0x11111111, 0x11111111 };
    const bytes = mem.sliceAsBytes(array[0..]);
    if (foo(bytes) != 0x11111111) return error.Wrong;
}
fn foo(bytes: []u8) u32 {
    const slice4 = bytes[1..5];
    const int_slice = mem.bytesAsSlice(u32, @as([]align(4) u8, @alignCast(slice4)));
    return int_slice[0];
}
$ zig build-exe runtime_incorrect_pointer_alignment.zig
$ ./runtime_incorrect_pointer_alignment
thread 975613 panic: incorrect alignment
/home/ci/work/zig-bootstrap/zig/doc/langref/runtime_incorrect_pointer_alignment.zig:9:64: 0x11e8c66 in foo (runtime_incorrect_pointer_alignment.zig)
    const int_slice = mem.bytesAsSlice(u32, @as([]align(4) u8, @alignCast(slice4)));
                                                               ^
/home/ci/work/zig-bootstrap/zig/doc/langref/runtime_incorrect_pointer_alignment.zig:5:12: 0x11e763e in main (runtime_incorrect_pointer_alignment.zig)
    if (foo(bytes) != 0x11111111) return error.Wrong;
           ^
/home/ci/work/zig-bootstrap/out/host/lib/zig/std/start.zig:788:64: 0x11e7c66 in callMain (std.zig)
    if (fn_info.param_types.len == 0) return wrapMain(root.main());
                                                               ^
/home/ci/work/zig-bootstrap/out/host/lib/zig/std/start.zig:248:5: 0x11e75e1 in _start (std.zig)
    asm volatile (switch (native_arch) {
    ^
(process terminated by signal)

bytes[1..5]는 배열을 1바이트 옮긴 위치라서 4바이트 정렬이 깨져요. @alignCast 안전 검사가 이를 감지하고 incorrect alignment 패닉을 내며 프로세스가 종료돼요. 이렇게 컴파일 타임에는 컴파일 에러로, 런타임에는 패닉으로 잘못된 정렬을 막아주는 게 Zig의 정렬 안전 검사예요.

더 알아보기

  • @alignCast — 포인터를 더 큰 정렬의 포인터로 바꾸면서 정렬 안전 검사를 넣는 내장 함수
  • Alignment — 정렬의 기본 개념과 @alignOf 사용법