18 영역 — 값이 사는 곳과 한꺼번에 걷히는 메모리
먼저 알아야 할 것
effects alloc 은 cap allocator 와 짝이다돌아보기
12장에서 op 안의 지역을 가리키는 참조를 돌려주면 무엇으로 거절되었는가? 그리고 값을 op 밖으로 내보내야 할 때 권한 길은 무엇이었는가?
답. E-ESCAPE 로 거절되었다. 지역은 op 이 끝나면 사라지므로 그 참조는 없는 값을 가리킨다. 값을 내보내려면 참조가 아니라 값 자체를 돌려주거나, 호출자가 넘긴 저장소에 담는다. 이 장은 그 “저장소” 가 어디서 오는지를 다룬다.
이 장의 필요성과 맥락
이 장이 끝나면
region <이름> <종류> do … end 블록으로 영역을 열고 alloc_bytes 로 자리를 얻는 법, 영역을 매개변수로 건네는 법을 익힌다. 영역에서 얻은 값을 밖으로 들고 나가거나 안쪽 영역이 열린 채 바깥에서 깎으면 왜 거절되는지, 운영체제가 없는 기계에서 힙이 왜 막히는지도 보게 된다.이 장에서 답할 질문
make point do … end나some 7이 만드는 값은 어디에 사는가? 할당인가?
18.1 값이 사는 세 곳#
저장되는 값은 셋 가운데 한 곳에 산다.
| 갈래 | 설명 |
|---|---|
| 지역 | op 안에서 나고 op 이 끝나면 사라진다. 가장 흔하다 |
| 정적 | 프로그램이 사는 동안 계속 있다 |
| 얻은 것 | 뿌리에서 얻는다. 영역이나 소유가 언제 돌려줄지 정한다 |
표 18.1 — 값이 사는 곳
흔히 스택과 힙이라 부르는 것이 지역과 얻은 것에 해당한다. 이름을 달리 쓰는 것은 이 언어에서 그것이 기계의 구조가 아니라 값의 성질이기 때문이다. 그리고 어느 곳에 사는지는 소스에 적혀 있다. 처리기가 몰래 옮기지 않는다.
얻는 뿌리는 둘이고 서로 따로 깎이고 따로 되감긴다.
| 뿌리 | 무엇으로 깎나 | 효과 | 성질 |
|---|---|---|---|
| 고정 창 | cap allocator · heap 이 아닌 영역 | alloc | 자라지 않는다. 다 쓰면 none 이다 |
| 힙 | cap heap · region <이름> heap | heap | 자란다. 운영체제가 있는 기계에만 있다 |
표 18.2 — 두 뿌리
고정 창은 운영체제가 없는 기계에서도 된다. 그런 기계에서는 링커가 정한 두 경계 사이가 창이다(20장).
문. make point do … end 나 some 7 이 만드는 값은 어디에 사는가? 할당인가?
답. 처리기의 유한 풀에 산다. 그 op 의 암묵적인 틀(frame)이라서 효과가 없고 권한도 필요 없다. 풀은 반복을 한 바퀴 돌 때마다 되감기고, 동시에 살아 있는 값이 풀의 크기를 넘으면 그 자리에서 멈춘다. 크기는 기계가 정하고 짓는 사람이 조절할 수 있다. 작은 묶음 값을 만들 때마다 alloc 을 적지 않아도 되는 이유다.
18.2 영역을 열고 자리를 얻는다#
영역은 region <이름> <종류> do … end 로 연다. 블록 안에서 alloc_bytes <영역> capacity <n> 이 그 영역에서 n 바이트를 청한다.
examples/ch18/scratch.low
module scratch .
rem run: main
proc fill_count input n u64 . output u64 . effects alloc .
do
region work arena do
let g option mut slice u8 be alloc_bytes work capacity n .
guard is_some g . else return 0 .
let buf mut slice u8 be some_value g .
var i u64 be 0 .
while lt i (len buf) . do
set (index buf i) 7 .
set i (add i 1) .
end
var s u64 be 0 .
for b buf do
set s (add s (widen u64 b)) .
end
return s .
end
return 0 .
end
proc main input al cap allocator . output u8 . effects alloc .
do
return narrow u8 (fill_count 10) .
end
실행 결과
$ lowentc --run main scratch.low
main() = 70
region work arena do … end가 영역을 연다.arena는 “앞에서부터 깎고 한꺼번에 되돌린다” 는 종류다.alloc_bytes work capacity n은option mut slice u8을 준다. 자리가 모자라면none이다. 메모리가 모자란 것도 값이다.fill_count의 머리에effects alloc이 있다. 자리를 얻는 것은 효과다. 순수한fn은 영역을 쓸 수 없다.- 블록을 나가는 모든 길 — 끝에 닿기,
return, 감싼 반복의break·continue— 에서 영역은 되감긴다.return s .로 떠나도 버퍼는 걷힌다. 개별로 돌려주는 코드가 없다.
main 은 cap allocator 를 받는다. fill_count 의 alloc 효과가 번져 오므로, 그 효과를 허락할 권한이 필요하다 (16장). 영역 블록은 그 블록을 연 op 안에서 자리를 얻을 자격이 되지만, 효과는 부르는 쪽으로 번진다.
영역의 종류는 닫힌 여덟이다 — stack·frame·arena·static·heap·mmap·disk·device. 그 밖의 낱말은 거절된다.
examples/ch18/kinds.low
module kinds .
rem run: carve_stack
rem run: carve_static
rem 종류 낱말은 이 영역이 무엇을 위한 자리인지 말한다. 이 판에서는 heap 만 자라는 뿌리에서, 나머지는 고정 창에서 깎는다
proc carve_stack output u64 . effects alloc .
do
var n u64 be 0 .
region r stack do
let g option mut slice u8 . . be alloc_bytes r capacity 32 .
if is_some g . do
set n (len (some_value g)) .
end
end
return n .
end
proc carve_static output u64 . effects alloc .
do
var n u64 be 0 .
region r static do
let g option mut slice u8 . . be alloc_bytes r capacity 64 .
if is_some g . do
set n (len (some_value g)) .
end
end
return n .
end
실행 결과
$ lowentc --run carve_stack kinds.low
carve_stack() = 32
$ lowentc --run carve_static kinds.low
carve_static() = 64
종류 낱말은 이 영역이 무엇을 위한 자리인지를 코드에 남긴다. 이 판에서 실제로 다르게 동작하는 것은 heap 하나다 — 자라는 뿌리에서 깎는다. 나머지 일곱은 모두 고정 창에서 깎고 되감으므로 carve_stack 과 carve_static 은 같은 방식으로 32 와 64 를 얻는다. 그래도 종류를 적고 그 목록을 닫아 둔 까닭은 둘이다. 기계에 맞춰 실체를 달리할 때 코드를 고치지 않으려는 것, 그리고 아무 낱말이나 쓸 수 있으면 그 낱말이 아무것도 말해 주지 못하기 때문이다. region t arena 를 읽은 사람은 “앞에서 깎고 한꺼번에 돌려준다” 를 안다.
고정 창:
[ a 16 ][ b 32 ][ c 8 ][ ··········· 비어 있음 ··········· ]
▲ 커서 --- 다음 alloc_bytes 가 여기서부터 깎는다
end 에 닿으면:
[ ························································ ]
▲ 커서가 영역을 연 자리로 되감긴다 --- a · b · c 가 한꺼번에 사라진다값마다 돌려주는 코드가 없는데도 새는 것이 없는 까닭이 이 그림이다. 돌려주는 일은 커서 하나를 되돌리는 것이다.
18.3 영역을 건네받는다#
영역은 인자로도 건넨다. input <이름> region <타입> . 으로 받고, 타입은 그 영역을 부르는 이름이다.
examples/ch18/param.low
module param .
rem run: main
type scratch u64 .
proc sum_squares input temp region scratch . input n u64 . output u64 . effects alloc .
requires le n 100 .
do
let g option mut slice u8 be alloc_bytes temp capacity n .
guard is_some g . else return 0 .
let buf mut slice u8 be some_value g .
var i u64 be 0 .
var s u64 be 0 .
while lt i n . do
set s (add s (mul i i)) .
set i (add i 1) .
end
return s .
end
proc main input al cap allocator . output u8 . effects alloc .
do
region r arena do
let v u64 be sum_squares r 5 .
return narrow u8 v .
end
return 1 .
end
실행 결과
$ lowentc --run main param.low
main() = 30
sum_squares 는 스스로 영역을 열지 않고, 부르는 쪽이 연 영역 r 을 받아 그 안에서 깎는다. 그래서 받은 버퍼의 수명은 부르는 쪽의 영역이 정한다. 영역에서 얻은 것은 영역보다 오래 살 수 없고, 영역이 끝나면 거기서 얻은 것도 끝난다.
18.4 영역 밖으로 들고 나갈 수 없다#
영역에서 얻은 바이트를 영역 밖의 이름에 담으면 거절된다.
examples/ch18/escape.low
module escape .
rem expect: E-REGION-ESCAPE
proc leak output u64 . effects alloc .
do
var keep mut slice u8 be subslice "abcd" 0 0 .
region r arena do
let g option mut slice u8 be alloc_bytes r capacity 16 .
if is_some g . do
set keep (some_value g) .
end
end
return len keep .
end
실행 결과
$ lowentc --check escape.low
escape.low:10:0 E-REGION-ESCAPE: this value was allocated inside a `region` block and is being carried OUT of it. The block RECLAIMS its memory at `end` (SPEC-004 §4.5: the lifetime IS the scope — that is why there is no `free`), so the value would point at bytes the next allocation hands to somebody else. Copy what you need into memory that outlives the block, or move the block outward so it covers every use
영역이 end 에서 닫히면 그 바이트는 되감긴다. 밖에 남은 keep 은 없는 것을 가리키게 된다. 들고 나가는 자리는 return, 영역 밖 이름에 대입하기, 영역 밖 이름의 칸이나 원소에 대입하기다. 반대로 정수·참거짓처럼 영역의 바이트를 들지 않는 값 — len buf 나 합계 — 은 들고 나가도 된다. fill_count 가 합을 돌려준 것이 그 모양이다.
흔한 오해. 영역은 결국 수명을 사람이 관리하는 것이다
free 는 흩어져 있고 검사되지 않는다.18.5 뿌리마다 커서는 하나다#
같은 뿌리의 영역이 안쪽에 열려 있는 동안 바깥 영역의 이름으로 깎으면 거절된다.
examples/ch18/nested.low
module nested .
rem expect: E-ALLOC-NESTED
proc f output u64 . effects alloc .
do
region outer arena do
region inner arena do
let g option mut slice u8 be alloc_bytes outer capacity 8 .
end
end
return 0 .
end
실행 결과
$ lowentc --check nested.low
nested.low:8:0 E-ALLOC-NESTED: this allocation names an OUTER source while a region of the SAME root is open inside it. Each root (the fixed window, the heap) has ONE cursor, so the inner region's `end` rewinds past these bytes and hands them to the next allocation — the value would silently change under you (RFC-0112 D4). Allocate from the innermost region, move this allocation outside the inner block, or open the inner region on the other root
고정 창에는 깎는 자리(커서)가 하나뿐이다. 안쪽 영역이 끝날 때 커서를 되감으면 그 사이에 바깥 이름으로 받은 바이트까지 함께 되감긴다.
▼ outer 를 연 자리
▼ inner 를 연 자리
[ a ][ ··· ][ b ][ ✘ c ]
▲ inner 이 끝나면 커서가 여기로 되감긴다 → c 도 함께 사라진다
a = outer 의 것 · b = inner 의 것 · c = inner 가 열린 동안 outer 이름으로 깎은 것 (✘ E-ALLOC-NESTED)다른 뿌리 — 힙 영역 안에서 고정 창 권한으로 깎는 것 — 는 커서가 따로라서 상관없다.
같은 이유로 태스크로 띄우는 op 은 뿌리에서 곧장 얻지 못한다(E-ALLOC-TASK). 커서가 실행 흐름 사이에 나뉘지 않기 때문이다. 흐름 사이에서 할당기를 나누려면 커서를 원자적으로 옮기는 할당기가 필요하다(27장).
18.6 자라는 뿌리#
region <이름> heap 은 자라는 뿌리에서 깎는다.
examples/ch18/heap.low
module heap .
rem run: main
proc grow_twice output u64 . effects heap .
do
region big heap do
let a option mut slice u8 be alloc_bytes big capacity 4096 .
let b option mut slice u8 be alloc_bytes big capacity 4096 .
guard is_some a . else return 0 .
guard is_some b . else return 0 .
return add (len (some_value a)) (len (some_value b)) .
end
return 0 .
end
proc main input h cap heap . output u8 . effects heap .
do
let n u64 be grow_twice .
guard eq n 8192 . else return 1 .
return 0 .
end
실행 결과
$ lowentc --run main heap.low
main() = 0
효과는 alloc 이 아니라 heap 이고, 짝이 되는 권한은 cap heap 이다. 힙 영역도 블록을 나갈 때 한꺼번에 걷힌다. 다만 이미 준 바이트를 옮기지 않는다.
운영체제가 없는 기계를 대상으로 지으면 힙은 어느 자리로도 청할 수 없다.
examples/ch18/heap_mcu.low
module heap_mcu .
rem flags: --target cortex_m
rem expect: E-HEAP-NOHOST
proc grow input h cap heap . output u64 . effects heap .
do
let a option mut slice u8 be alloc_bytes h capacity 4096 .
guard is_some a . else return 0 .
return len (some_value a) .
end
실행 결과
$ lowentc --check --target cortex_m heap_mcu.low
heap_mcu.low:5:0 E-HEAP-NOHOST: this op asks for the GROWING root — the `heap` effect, a `cap heap` input or a `region <name> heap` block — but the build target is FREESTANDING (`machine.no_heap`, e.g. cortex_m). A bare-metal board has no allocator that can hand out more memory at run time. What it DOES have is the fixed window its linker script reserves: carve from that with `cap allocator` / `effects alloc` or any other region kind (RFC-0112 D2 · RFC-0038 — the gate is on what the MACHINE cannot do, and only that)
--target cortex_m 은 운영체제 없는 마이크로컨트롤러다. heap 효과, cap heap 입력, region … heap 블록이 모두 거절된다. 같은 기계에서 고정 창을 깎는 alloc 은 쓸 수 있다. 코드가 어느 뿌리를 딛는지가 머리에 적혀 있으므로, 어떤 라이브러리가 운영체제 없는 기계에서 도는지는 번역이 답한다.
18.7 영역 위의 스택#
영역에서 얻는 것은 바이트만이 아니다. stack_new <영역> capacity <n> 은 그 영역에 원소 n 개짜리 스택을 만든다. 되풀이로 나무나 그래프를 훑을 때처럼 “나중에 넣은 것을 먼저 꺼내는” 일에 쓴다.
examples/ch18/stack.low
module stack_demo .
rem run: main
type scratch u64 .
rem 영역에서 스택 하나를 얻어 자릿수를 거꾸로 쌓는다 --- 나중에 넣은 것이 먼저 나온다
proc reverse_digits input temp region scratch . input n u64 . output u64 . effects alloc .
requires le n 1000000 .
do
let work stack u64 be stack_new temp capacity 8 .
var v u64 be n .
while gt v 0 . do
push work (mod v 10) .
set v (div v 10) .
end
var out u64 be 0 .
var place u64 be 1 .
while pop work into d . do
set out (add out (mul d place)) .
set place (mul place 10) .
end
return out .
end
proc main input al cap allocator . output u8 . effects alloc .
do
region r arena do
return narrow u8 (reverse_digits r 47) .
end
return 0 .
end
실행 결과
$ lowentc --run main stack.low
main() = 74
let work stack u64 be stack_new temp capacity 8 .이 스택을 만든다. 영역에서 자리를 얻으므로effects alloc이다.push work x .는 넣는다.while pop work into d . do … end는 꺼낼 것이 있는 동안 하나씩 꺼내d에 묶는다. 비면 되풀이가 끝난다 — 빈 스택에서 꺼내다 멈추는 길이 문법에 없다.
47 의 자릿수 7·4 를 넣으면 4·7 차례로 나와 74 가 된다. 스택도 영역과 함께 걷히므로 따로 돌려주는 코드가 없다.
18.8 흔한 실수#
반례. 자리를 얻었는지 묻지 않고 some_value 로 꺼낸다
examples/ch18/mistake_nocheck.low
module mistake_nocheck .
rem run: buf_len 16
rem trap: buf_len 1000000
proc buf_len input n u64 . output u64 . effects alloc .
do
region work arena do
rem ✘ 자리를 얻었는지 묻지 않고 꺼낸다 --- 창이 모자라면 `none` 을 꺼내다 멈춘다
let buf mut slice u8 be some_value (alloc_bytes work capacity n) .
return len buf .
end
return 0 .
end
실행 결과
$ lowentc --run buf_len mistake_nocheck.low 16
buf_len(16) = 16
$ lowentc --run buf_len mistake_nocheck.low 1000000
== ir diagnostics (1) ==
0:0 E-VM-NONE: some_value of none (panic)
16 바이트는 얻지만 백만 바이트는 고정 창에 들어가지 않는다. alloc_bytes 는 그때 none 을 주고, 묻지 않고 꺼낸 some_value 가 E-VM-NONE 으로 멈춘다. C 의 malloc 결과를 NULL 과 비교하지 않는 것과 같은 실수인데, Lowent 는 없는 자리를 쓰는 대신 꺼내는 자리에서 멈춘다. 이 장의 예제처럼 guard is_some g . else return 0 . 으로 먼저 묻는다. 메모리가 모자란 것도 다뤄야 할 값이다.
반례. 영역에서 얻은 버퍼를 돌려준다
examples/ch18/mistake_returnbuf.low
module mistake_returnbuf .
rem expect: E-REGION-ESCAPE
proc make_buf input n u64 . output mut slice u8 . effects alloc .
do
region work arena do
let g option mut slice u8 be alloc_bytes work capacity n .
guard is_some g . else return subslice "" 0 0 .
rem ✘ 이 블록이 끝나면 걷힐 바이트를 돌려준다
return some_value g .
end
return subslice "" 0 0 .
end
실행 결과
$ lowentc --check mistake_returnbuf.low
mistake_returnbuf.low:10:0 E-REGION-ESCAPE: this value was allocated inside a `region` block and is being carried OUT of it. The block RECLAIMS its memory at `end` (SPEC-004 §4.5: the lifetime IS the scope — that is why there is no `free`), so the value would point at bytes the next allocation hands to somebody else. Copy what you need into memory that outlives the block, or move the block outward so it covers every use
mistake_returnbuf.low:12:0 E-TYPE-RETMUT: this op declares a MUTABLE output (`mut slice`/`mut_ref`) but RETURNS a read-only place — a shared parameter, a `let` without `mut`, a `ref X`, or a subslice/index/field of one. The caller would receive a mutable alias of storage that is only held read-only, and could write through it — laundering the shared / fn-purity guarantee (measured: a pure fn mutated its shared input via such a return). Return a mutable place — a fresh allocation, a `mut`/`owned` parameter, or a `var`/`mut` local — or drop `mut` from the output type
make_buf 가 돌려준 바이트는 end 에서 되감기므로, 부르는 쪽이 받는 순간 다음 할당이 그 자리를 남에게 줄 수 있다. C 에서 지역 배열의 주소를 돌려주는 결함과 같은 모양이고 E-REGION-ESCAPE 로 거절된다. 진단의 말대로 블록을 바깥으로 옮긴다. 부르는 쪽이 영역을 열어 건네고, 받은 쪽이 그 안에서 깎는다.
examples/ch18/returnbuf_fixed.low
module returnbuf_fixed .
rem run: main
type scratch u64 .
rem 영역을 연 쪽이 아니라 받은 쪽이 깎는다 --- 버퍼의 수명은 부르는 쪽의 블록이 정한다
proc filled_sum input temp region scratch . input n u64 . output u64 . effects alloc .
requires le n 100 .
do
let g option mut slice u8 be alloc_bytes temp capacity n .
guard is_some g . else return 0 .
let buf mut slice u8 be some_value g .
var s u64 be 0 .
for b buf do
set s (add s (widen u64 b)) .
end
return add s (len buf) .
end
proc main input al cap allocator . output u8 . effects alloc .
do
region r arena do
let v u64 be filled_sum r 5 .
return narrow u8 v .
end
return 1 .
end
실행 결과
$ lowentc --run main returnbuf_fixed.low
main() = 5
반례. 영역을 쓰는 op 을 fn 으로 적는다
examples/ch18/mistake_fnregion.low
module mistake_fnregion .
rem expect: E-EFFECT-CALC
rem ✘ 한꺼번에 돌려주니 바깥에 남는 것이 없다고 보고 `fn` 으로 적었다
fn scratch_len input n u64 . output u64 .
do
region work arena do
let g option mut slice u8 be alloc_bytes work capacity n .
guard is_some g . else return 0 .
return len (some_value g) .
end
return 0 .
end
실행 결과
$ lowentc --check mistake_fnregion.low
mistake_fnregion.low:6:1 E-EFFECT-CALC: this fn is declared pure but performs `alloc` — make it a `proc` with `effects …`, or remove the effect
블록이 끝나면 모두 되감기니 바깥에 남는 것이 없어 보인다. 그래도 자리를 얻는 것은 alloc 효과다. 창에 자리가 있는지에 따라 결과가 달라질 수 있고(none), 같은 창을 쓰는 다른 코드와 겹친다. 그래서 E-EFFECT-CALC 다. proc … effects alloc . 으로 적는다.
반례. 영역 밖에서 태어난 액터에게 영역의 바이트를 건넨다
examples/ch18/mistake_outlives.low
module mistake_outlives .
rem expect: E-ALLOC-OUTLIVES
use allocs .
proc carve_inside output u64 . effects alloc state .
do
var a allocs.bump_bytes be spawn actor allocs.bump_bytes .
region r arena do
let g option mut slice u8 . . be alloc_bytes r capacity 16 .
if is_some g . do
rem ✘ 영역 밖에서 태어난 액터에게 영역의 바이트를 건넨다
let c u64 be send a init (some_value g) .
end
end
return 0 .
end
실행 결과
$ lowentc --check mistake_outlives.low
mistake_outlives.low:13:0 E-ALLOC-OUTLIVES: this hands bytes from a `region` block to an actor that was born OUTSIDE the block. The actor outlives the region, and nothing here can see whether it keeps the slice — if it does, it will read bytes the region's `end` gave to somebody else (RFC-0112 D5). Create the actor inside the region, or give it memory that outlives it
액터 a 는 영역보다 먼저 태어났으니 영역이 닫힌 뒤에도 산다. 그 액터가 받은 슬라이스를 상태에 넣어 두는지는 번역할 때 알 수 없다. 넣어 둔다면 영역의 end 가 그 바이트를 다른 쪽에 준 뒤에도 옛 자리를 읽는다. 그래서 보수적으로 E-ALLOC-OUTLIVES 로 막는다. 액터도 영역 안에서 만들면 둘이 함께 사라진다.
examples/ch18/outlives_fixed.low
module outlives_fixed .
rem run: carve_inside
use allocs .
proc carve_inside output u64 . effects alloc state .
do
var n u64 be 0 .
region r arena do
let g option mut slice u8 . . be alloc_bytes r capacity 16 .
if is_some g . do
rem 액터도 영역 안에서 태어난다 — 둘이 함께 사라진다
var a allocs.bump_bytes be spawn actor allocs.bump_bytes .
let c u64 be send a init (some_value g) .
let p option mut slice u8 . . be send a reserve 10 .
if is_some p . do
set n (send a used) .
end
end
end
return n .
end
실행 결과
$ lowentc --run carve_inside outlives_fixed.low
carve_inside() = 10
흔한 오해. 반복 안에서 얻은 자리는 바퀴마다 돌려준다
examples/ch18/loop_region.low
module loop_region .
rem run: count_outer
rem run: count_inner
rem 영역이 반복 바깥에 있다 --- 바퀴마다 얻은 4096 바이트가 블록 끝까지 쌓인다
proc count_outer output u64 . effects alloc .
do
var got u64 be 0 .
region work arena do
var i u64 be 0 .
while lt i 100 . do
let g option mut slice u8 be alloc_bytes work capacity 4096 .
if is_some g . do set got (add got 1) . end
set i (add i 1) .
end
end
return got .
end
rem 영역을 반복 안에서 연다 --- 바퀴가 끝날 때마다 되감긴다
proc count_inner output u64 . effects alloc .
do
var got u64 be 0 .
var i u64 be 0 .
while lt i 100 . do
region work arena do
let g option mut slice u8 be alloc_bytes work capacity 4096 .
if is_some g . do set got (add got 1) . end
end
set i (add i 1) .
end
return got .
end
실행 결과
$ lowentc --run count_outer loop_region.low
count_outer() = 16
$ lowentc --run count_inner loop_region.low
count_inner() = 100
영역은 블록이 끝날 때 되감긴다. 바퀴가 끝날 때가 아니다. count_outer 는 영역이 반복 바깥에 있어서 4096 바이트씩 쌓이고, 이 판의 기본 고정 창(65536 바이트)에서 열여섯 번째 뒤로는 none 을 받는다. count_inner 는 영역을 바퀴 안에서 열어 매번 되감기므로 백 번 모두 얻는다. 한 바퀴에서만 쓰는 버퍼는 영역을 반복 안에서 연다.
18.9 이 장의 문법 한눈에#
| 모양 | 뜻 | 왜 이렇게 |
|---|---|---|
region work arena do … end | 영역을 연다 — 블록을 나가는 모든 길에서 한꺼번에 되감긴다 | free 가 없다 — 수명이 곧 블록 |
alloc_bytes work capacity n | 영역에서 n 바이트를 청한다 — option mut slice u8 | 모자람도 값이다 |
effects alloc · effects heap | 고정 창에서 얻는다 · 자라는 힙에서 얻는다 | 어느 뿌리를 딛는지 머리에 보인다 |
input al cap allocator . · cap heap | 시작점이 받는 할당 권한 | 효과를 허락하는 짝 |
input temp region scratch . | 부르는 쪽이 연 영역을 받는다 | 버퍼의 수명을 부르는 쪽이 정한다 |
stack·frame·arena·static·heap·mmap·disk·device | 닫힌 영역 종류 여덟 | 낱말이 뜻을 말하게 — 밖의 낱말은 E-REGION-KIND |
| 영역 밖 이름에 담기 · 돌려주기 | 거절(E-REGION-ESCAPE) | 걷힌 바이트를 가리키지 않게 |
| 안쪽 영역이 열린 동안 바깥 이름으로 깎기 | 거절(E-ALLOC-NESTED) | 뿌리마다 커서는 하나 |
--target cortex_m + heap | 거절(E-HEAP-NOHOST) | 운영체제 없는 기계에는 힙이 없다 |
영역 밖 액터에게 영역 바이트를 send | E-ALLOC-OUTLIVES | 액터가 바이트를 쥐고 영역보다 오래 살 수 있다 — 액터도 영역 안에서 만든다 |
표 18.3 — 영역의 문법 — 모양 · 뜻 · 왜 이렇게 생겼나
복습 정리
alloc)과 자라는 힙(heap)이다. region <이름> <종류> do … end 가 영역을 열고, 블록을 나가는 모든 길에서 한꺼번에 되감긴다. 영역은 매개변수로 건넬 수 있다. 영역에서 얻은 바이트는 밖으로 들고 나갈 수 없고, 같은 뿌리의 안쪽 영역이 열린 동안 바깥에서 깎을 수 없다. 운영체제가 없는 기계에서는 힙이 거절된다.