정수 오버플로
정수 오버플로 (Integer Overflow)
Zig는 정수 산술이 범위를 넘을 때 조용히 넘어가게 두지 않아요. 대신 연산자가 오버플로를 일으키면 안전 검사로 잡아내고, 꼭 감싸서(wrap) 계산하고 싶다면 명시적인 대체 연산자를 쓰게끔 해요. 이번 장에서는 어떤 연산이 오버플로를 일으키는지, 그리고 표준 라이브러리와 내장(builtin) 함수로 어떻게 대응하는지 하나씩 살펴볼게요.
본문
기본 연산 (Default Operations)
다음 연산자는 정수 오버플로를 일으킬 수 있어요.
+(덧셈)-(뺄셈)-(부호 반전)*(곱셈)/(나눗셈)- @divTrunc (나눗셈)
- @divFloor (나눗셈)
- @divCeil (나눗셈)
- @divExact (나눗셈)
컴파일 타임에 덧셈이 오버플로를 일으키는 예를 볼게요.
comptime {
var byte: u8 = 255;
byte += 1;
}
$ zig test test_comptime_overflow.zig
/home/ci/work/zig-bootstrap/zig/doc/langref/test_comptime_overflow.zig:3:10: error: overflow of integer type 'u8' with value '256'
byte += 1;
~~~~~^~~~
u8에 255까지 들어갈 수 있는데 여기에 1을 더해 256이 되려 하니, 컴파일러가 컴파일 타임에 바로 오버플로 오류를 알려줘요. 이 값은 컴파일 타임에 이미 정해져 있기 때문에, 코드를 실행하기도 전에 잡히는 거예요.
이제 런타임의 예를 볼게요.
const std = @import("std");
pub fn main() void {
var byte: u8 = 255;
byte += 1;
std.debug.print("value: {}\n", .{byte});
}
$ zig build-exe runtime_overflow.zig
$ ./runtime_overflow
thread 975726 panic: integer overflow
/home/ci/work/zig-bootstrap/zig/doc/langref/runtime_overflow.zig:5:10: 0x11e82b5 in main (runtime_overflow.zig)
byte += 1;
^
/home/ci/work/zig-bootstrap/out/host/lib/zig/std/start.zig:788:64: 0x11e7bbb 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)
값이 컴파일 타임에 정해지지 않고 런타임에만 알려질 때는 컴파일러가 미리 잡지 못해요. 그래서 실제로 실행하는 순간 integer overflow 패닉으로 터지면서 죽는 거죠. 즉, 오버플로는 컴파일 타임에는 컴파일 오류로, 런타임에는 패닉으로 처리되는 안전 검사를 갖고 있어요.
표준 라이브러리 수학 함수 (Standard Library Math Functions)
표준 라이브러리가 제공하는 다음 함수들은 오버플로가 나면 해당 오류를 돌려줘요.
@import("std").math.add@import("std").math.sub@import("std").math.mul@import("std").math.divTrunc@import("std").math.divFloor@import("std").math.divCeil@import("std").math.divExact@import("std").math.shl
덧셈에서 오버플로를 잡아내는 예를 볼게요.
const math = @import("std").math;
const print = @import("std").debug.print;
pub fn main() !void {
var byte: u8 = 255;
byte = if (math.add(u8, byte, 1)) |result| result else |err| {
print("unable to add one: {s}\n", .{@errorName(err)});
return err;
};
print("result: {}\n", .{byte});
}
$ zig build-exe math_add.zig
$ ./math_add
unable to add one: Overflow
error: Overflow
/home/ci/work/zig-bootstrap/out/host/lib/zig/std/math.zig:571:21: 0x114b214 in add__func_525 (std.zig)
if (ov[1] != 0) return error.Overflow;
^
/home/ci/work/zig-bootstrap/zig/doc/langref/math_add.zig:8:9: 0x11e769f in main (math_add.zig)
return err;
^
math.add는 패닉 대신 에러 유니온으로 결과를 돌려줘요. 그래서 오버플로가 났을 때 프로그램을 죽이지 않고 Overflow라는 에러를 잡아 원하는 방식으로 처리할 수 있어요.
내장 오버플로 함수 (Builtin Overflow Functions)
다음 내장 함수들은 오버플로가 있었는지(u1 값으로)와, 연산의 (어쩌면 오버플로된) 비트 결과를 묶은 튜플을 돌려줘요.
@addWithOverflow의 예를 볼게요.
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]});
}
}
$ zig build-exe addWithOverflow_builtin.zig
$ ./addWithOverflow_builtin
overflowed result: 9
255 + 10은 u8 범위를 넘으므로 ov[1](오버플로 플래그)이 0이 아니에요. 그래서 오버플로된 결과 9를 출력하게 되죠. 오버플로가 났는지와 감싸인 결과를 동시에 얻으므로, 오버플로를 스스로 판단해 처리하고 싶을 때 유용해요.
래핑 연산 (Wrapping Operations)
다음 연산들은 감싸기(wraparound) 시맨틱이 보장돼요.
+%(래핑 덧셈)-%(래핑 뺄셈)-%(래핑 부호 반전)*%(래핑 곱셈)
const std = @import("std");
const expectEqual = std.testing.expectEqual;
const minInt = std.math.minInt;
const maxInt = std.math.maxInt;
test "wraparound addition and subtraction" {
const x: i32 = maxInt(i32);
const min_val = x +% 1;
try expectEqual(minInt(i32), min_val);
const max_val = min_val -% 1;
try expectEqual(maxInt(i32), max_val);
}
$ zig test test_wraparound_semantics.zig
1/1 test_wraparound_semantics.test.wraparound addition and subtraction...OK
All 1 tests passed.
%가 붙은 래핑 연산자들은 오버플로를 검사하지 않고 값이 그냥 범위를 감싸서(wrap) 돌아와요. maxInt(i32)에 +% 1을 하면 minInt(i32)가 되고, 다시 -% 1을 하면 maxInt(i32)로 돌아오는 걸 테스트로 확인할 수 있어요. 안전 검사 없이 의도적으로 감싸는 동작이 필요할 때 쓰는 연산자예요.
더 알아보기 (Learn more)
- Wrapping Operations — 래핑 산술 연산에 대해 더 자세히 보고 싶다면 이어서 살펴보세요.