2026.06
CJ 올리브네트웍스 AI SW WAVE 복기
Python 교육 2~3일차에 정리한 CS 질문들을 다시 복기하며 정리한 글입니다.
문법을 아는 것과 "왜 그렇게 동작하는지"를 아는 것은 다른 문제라서, 헷갈렸던 개념들을 예시와 함께 기록했습니다.
나중에 다시 볼 때 바로 이해할 수 있도록 결론부터 요약하고, 그 아래에 풀어서 설명하는 순서로 썼습니다.
1. 변수를 선언하면 메모리에서 무슨 일이 일어날까
한 줄 요약
변수 선언 시 값은 힙(Heap)에 저장되고, 함수 실행 중에는 스택(Stack)에 프레임이 생겨 그 값을 가리킨다. 함수가 끝나면 스택 프레임은 사라지고, 아무도 가리키지 않는 힙 객체는 가비지 컬렉터가 수거한다.
메모리는 세 공간으로 나뉜다
| 전역 영역 | 함수 밖에서 선언된 변수, 함수 이름 |
| 스택(Stack) | 함수 실행 중 사용하는 지역 변수 |
| 힙(Heap) | 실제 값(객체) |
┌──────────────┐ ┌─────────────────┐
│ Stack Frame │ │ Heap │
├──────────────┤ ├─────────────────┤
│ a : 0x1001 ─────────► │ 0x1001 : 10 │
│ b : 0x1002 ─────────► │ 0x1002 : 20 │
│ c : 0x1003 ─────────► │ 0x1003 : 30 │
└──────────────┘ └─────────────────┘
핵심은 변수 이름과 실제 값이 저장되는 곳이 다르다는 점입니다.
변수 이름은 어디에 있든 "값이 저장된 주소"만 들고 있고, 실제 값은 힙에 따로 존재합니다.
함수 선언 → 실행 → 종료 흐름
다음과 같은 함수가 있다고 가정합니다.
def add(a, b):
x = a + b
return x
y = add(3, 5)
① 함수를 선언하는 순간
함수 코드 자체가 힙에 저장되고, 전역 영역에는 add라는 이름이 등록되어 힙에 있는 함수 코드를 가리킵니다.
전역 영역 힙
add ─────────────→ [ 함수 코드 ]
② 함수를 호출하는 순간
스택에 이번 호출만을 위한 스택 프레임이 생성됩니다.
매개변수와 지역 변수(a, b, x)는 이 프레임 안에서 힙의 값을 가리킵니다.
전역 영역 힙
add ─────────────→ [ 함수 코드 ]
스택 프레임
┌────────────┐
│ a ─────────────→ [ 3 ]
│ b ─────────────→ [ 5 ]
│ x ─────────────→ [ 8 ]
└────────────┘
③ 함수가 종료되는 순간
스택 프레임이 통째로 사라집니다. return으로 돌려준 값(8)만 전역 영역의 y가 이어받습니다.
전역 영역 힙
add ─────────────→ [ 함수 코드 ]
y ───────────────→ [ 8 ]
가비지 컬렉션은 왜 일어나는가
스택 프레임이 사라지면서 a, b가 가리키던 3, 5는 더 이상 아무도 가리키지 않게 됩니다.
파이썬은 객체마다 "지금 나를 가리키는 이름표가 몇 개인지"를 세고 있는데(참조 카운트), 이 숫자가 0이 되는 순간 가비지 컬렉터가 힙에서 해당 객체를 정리합니다.
Garbage Collector
│
▼
[ 3 ] ← 참조 0개 → 수거
[ 5 ] ← 참조 0개 → 수거
[ 8 ] ← y가 가리키는 중 → 유지
실제 조회는 두 단계로 일어난다
변수를 하나 읽는 것처럼 보여도 내부적으로는 두 번 조회합니다.
- 스택(또는 전역)의 심볼 테이블에서 변수 이름으로 주소값을 찾는다
- 그 주소값으로 힙에 가서 실제 값을 읽어온다
이 구조 때문에 두 가지 특징이 나타납니다.
┌──────────────┐ ┌─────────────────┐
│ Stack Frame │ │ Heap │
├──────────────┤ ├─────────────────┤
│ x : 0x1001 ────────┐ │ 0x1001 : 5 │
│ y : 0x1001 ────────┘► │ │
└──────────────┘ └─────────────────┘
x = 5, y = 5를 각각 선언해도 파이썬은 5라는 객체를 두 번 만들지 않고, 이미 있는 객체 하나를 두 이름이 같이 가리키게 합니다.
그리고 리스트처럼 값이 바뀔 수 있는 객체를 y = x로 대입하면, 이때 복사되는 건 값이 아니라 주소값입니다.
┌──────────────┐ ┌─────────────────────┐
│ Stack Frame │ │ Heap │
├──────────────┤ ├─────────────────────┤
│ x : 0x2001 ────────┐ │ 0x2001 : [1, 2] │
│ y : 0x2001 ────────┘► │ │
└──────────────┘ └─────────────────────┘
그래서 y를 수정하면 같은 주소를 보고 있는 x도 함께 바뀝니다.
왜 굳이 스택과 힙을 나눠뒀을까
두 가지 이유로 정리했습니다.
- 크기가 유동적인 값 때문에: 리스트나 딕셔너리처럼 크기가 늘어나거나 줄어드는 값은 스택에 직접 담을 수 없습니다. 스택은 크기가 고정된 공간을 빠르게 쌓고 내리는 구조라서, 크기가 변하는 값은 힙에 두고 주소만 스택에서 들고 있는 편이 맞습니다.
- 같은 값의 재사용: 값이 같으면 객체를 하나만 만들고 여러 이름이 공유하게 해서 메모리 낭비를 줄입니다.
결국 스택은 "빠르게 접근하기 위한 주소 저장소", 힙은 "실제 데이터를 유연하게 관리하는 공간"으로 역할이 나뉘어 있는 구조입니다.
2. 컴퓨터는 소수점 아래 숫자를 어떻게 저장할까 (IEEE 754)
한 줄 요약
소수도 결국 이진수로 표현해야 해서, 32비트를 부호·지수·가수 세 부분으로 나눠 저장한다. 이 방식 때문에 0.1처럼 정확히 떨어지지 않는 값은 근사값으로만 저장된다.
이진수로 소수 표현하기
정수와 마찬가지로 소수점도 이진수 자릿값의 합으로 표현합니다. 소수점 왼쪽은 2⁰, 2¹, 2² 순서로 커지고, 오른쪽은 2⁻¹, 2⁻², 2⁻³ 순서로 작아집니다.
# 6.5 -> 110.1
1 1 0 . 1
↑ ↑ ↑ ↑
2² 2¹ 2⁰ 2⁻¹
4 2 1 0.5
4 + 2 + 0.5 = 6.5
110.1이라는 이진수가 6.5가 되는 이유입니다.
32비트 안에 어떻게 나눠 담는가
이 값을 컴퓨터에 저장하려면 소수점 위치와 실제 숫자를 분리해서 담아야 합니다.
IEEE 754 표준은 32비트를 아래 세 구역으로 나눕니다.
부호 (1bit) 지수 (8bit) 가수 (23bit)
0 10000001 10100000000000000000000
- 부호(1bit): 양수면 0, 음수면 1
- 지수(8bit): 소수점을 몇 자리 옮겼는지를 저장. 110.1을 1.101 × 2² 형태로 바꾸면 지수는 2인데, 음수 지수도 표현해야 하므로 8비트의 중간값인 127을 더해서 저장(2 + 127 = 129 → 10000001)
- 가수(23bit): 1.101에서 맨 앞의 1은 항상 고정이라 생략하고, 나머지 101만 저장한 뒤 남은 자리는 0으로 채움
해당 방식의 한계
0.1처럼 2의 거듭제곱 조합으로는 딱 떨어지지 않는 수가 있습니다.
아무리 조합해도 무한소수가 되고, 23비트 안에서 잘라 저장하다 보니 근사값이 될 수밖에 없습니다.
파이썬에서 0.1 + 0.2를 계산하면 0.3이 아니라 0.30000000000000004가 나오는 이유가 여기에 있습니다.
3. 컴퓨터는 음수를 어떻게 표현할까 (2의 보수)
한 줄 요약
맨 앞 비트를 부호로만 쓰면 +0과 -0이 따로 생기는 문제가 있어서, 실제로는 모든 비트를 반전한 뒤 1을 더하는 '2의 보수' 방식을 사용한다. 이 방식은 뺄셈을 덧셈 회로 하나로 처리할 수 있다는 장점이 있다.
2의 보수를 만드는 과정
-5를 예로 들면 다음 세 단계를 거칩니다.
① +5를 이진수로
00000000 00000000 00000000 00000101
② 모든 비트 반전 (1의 보수)
11111111 11111111 11111111 11111010
③ 1을 더함 (2의 보수)
11111111 11111111 11111111 11111011 ← -5
왜 이 방식을 쓸까
가장 단순한 방법은 맨 앞 비트를 부호 표시로만 쓰는 것이지만, 이 경우 +0과 -0이 서로 다른 값으로 존재하게 되고 덧셈과 뺄셈을 각각 다른 회로로 처리해야 합니다.
2의 보수 방식을 쓰면 덧셈 회로 하나만으로 뺄셈까지 처리할 수 있습니다.
예를 들어 5 + (-5)를 계산하면 결과값이 32비트를 넘어가는데, 32비트는 2³²를 넘는 순간 다시 0으로 돌아가는 순환 구조라서 넘친 비트는 그대로 버려지고 남는 값이 정확히 0이 됩니다.
회고
세 주제 모두 "결국 컴퓨터는 모든 걸 이진수로, 정해진 크기의 공간 안에 표현해야 한다"는 하나의 제약에서 출발합니다.
소수는 정확한 값 대신 근사값으로, 음수는 부호 비트 대신 보수 연산으로, 변수는 값 자체가 아니라 주소로 다루는 식입니다.
이 제약을 다시 짚고 나니 "왜 0.1 + 0.2가 0.3이 아닌지", "왜 리스트를 복사했는데 원본도 같이 바뀌는지" 같은 질문에 답을 자연스럽게 할 수 있게 되었습니다.
'for my job > AI SW WAVE 복기' 카테고리의 다른 글
| [AI 활용 기록] 하네스로 디자인 시스템과 프론트엔드를 이틀안에 개선하기 (0) | 2026.07.12 |
|---|---|
| [AI 활용 기록] 기획안을 문서화해서 AI에게 넘기면, 프론트 목업이 바로 나올까? (0) | 2026.07.06 |
| [AI 활용 기록] AI로 서비스 기획안을 만들며 다시 배운 것 (0) | 2026.07.04 |