실행 시 메모리 배치 문서의 검증 코드다. if __name__ == "__main__": 아래가 자체 테스트다.
# 실행 시 메모리 배치: 코드, 전역 데이터, 힙, 스택.
# 실행: python3 50_runtime-memory-layout_verify.py (표준 라이브러리만 쓴다)
# 문서의 메모리 배치 그림을 작은 모형으로 옮겨, 스택과 힙이 서로 마주 보고 자랄 때의 성질을 확인한다.
import random
class Memory:
"""주소 0 ~ size-1. 낮은 주소부터 코드, 전역 데이터, 힙(위로 자람). 맨 위에서 스택(아래로 자람)."""
def __init__(self, size, code, static):
self.size = size
self.code_end = code # 코드: [0, code)
self.static_end = code + static # 전역 데이터: [code, code+static)
self.heap_top = self.static_end # 힙: [static_end, heap_top)
self.stack_top = size # 스택: [stack_top, size)
self.frames = []
def gap(self):
return self.stack_top - self.heap_top
def call(self, n): # 활성 레코드 n칸을 쌓는다
if n > self.gap():
return False
self.stack_top -= n
self.frames.append(n)
return True
def ret(self): # 맨 위 레코드를 걷는다
self.stack_top += self.frames.pop()
def malloc(self, n): # 힙에서 n칸을 받는다 (이 모형은 힙 꼭대기만 올린다)
if n > self.gap():
return None
addr = self.heap_top
self.heap_top += n
return addr
def ok(self):
return 0 <= self.code_end <= self.static_end <= self.heap_top <= self.stack_top <= self.size
def fits_shared(heap, stack, room):
"""마주 보고 자랄 때: 둘의 합이 빈 공간 이하면 들어간다."""
return heap + stack <= room
def fits_split(heap, stack, room):
"""빈 공간을 반으로 미리 갈라 줄 때: 저마다 자기 몫 이하여야 한다."""
return heap <= room // 2 and stack <= room - room // 2
if __name__ == "__main__":
m = Memory(size=100, code=20, static=10)
# 배치 순서: 낮은 주소부터 코드 < 전역 < 힙 < (빈 곳) < 스택
assert (m.code_end, m.static_end, m.heap_top, m.stack_top) == (20, 30, 30, 100) and m.gap() == 70
# 스택은 높은 주소에서 낮은 주소로: 부를 때마다 꼭대기 주소가 줄어든다
tops = []
for n in (8, 5, 12):
assert m.call(n)
tops.append(m.stack_top)
assert tops == [92, 87, 75] and tops == sorted(tops, reverse=True)
# 힙은 낮은 주소에서 높은 주소로: 받을 때마다 주소가 늘어난다
addrs = [m.malloc(10), m.malloc(10)]
assert addrs == [30, 40] and m.heap_top == 50
# 전역 변수 자리는 스택과 힙이 어떻게 움직여도 그대로다 ("fixed address")
assert (m.code_end, m.static_end) == (20, 30)
# 함수가 끝나면 스택은 줄지만 힙에서 받은 자리는 그대로 남는다 (free를 부를 때까지)
m.ret(); m.ret(); m.ret()
assert m.stack_top == 100 and m.heap_top == 50
# 가운데 빈 곳이 바닥나면 더 못 쌓는다
assert m.gap() == 50 and not m.call(51) and m.call(50) and m.gap() == 0 and m.malloc(1) is None
# ---- 무작위로 부르고 돌아가고 받아도 네 구역은 겹치지 않는다 ----
rnd = random.Random(4)
for _ in range(300):
m = Memory(200, rnd.randint(1, 30), rnd.randint(0, 30))
for _ in range(60):
r = rnd.random()
if r < 0.4:
m.call(rnd.randint(1, 20))
elif r < 0.6 and m.frames:
m.ret()
else:
m.malloc(rnd.randint(1, 20))
assert m.ok()
# ---- 마주 보고 자라면 빈 공간을 어떤 비율로든 나눠 쓴다. 반으로 갈라 두면 그렇지 못하다 ----
room = 12
lost = []
for heap in range(room + 1):
for stack in range(room + 1):
if fits_split(heap, stack, room):
assert fits_shared(heap, stack, room) # 갈라 줘서 되는 것은 마주 보고도 된다
elif fits_shared(heap, stack, room):
lost.append((heap, stack)) # 합은 들어가는데 갈라 줘서 못 쓰는 경우
assert (10, 2) in lost and (2, 10) in lost and len(lost) == 42
print("ALL CHECKS PASSED")