[주간 회고] 7주차: 가상 메모리의 심장, Malloc Lab 구현과 OS의 이해

이번 7주차는 Malloc Lab 프로젝트를 통해 C언어로 직접 동적 메모리 할당기를 구현하며 시스템 프로그래밍의 깊은 곳을 탐험한 시간이었습니다. 단순히 코드를 짜는 것을 넘어, 메모리를 어떻게 하면 더 효율적으로 관리할 것인가에 대해 치열하게 고민했던 기록들을 정리합니다.

1. 힙(Heap)의 구조와 경계 처리

메모리 할당기를 구현하기 위해 가장 먼저 힙의 물리적 및 논리적 구조를 설계했습니다.

  • 정렬(Alignment)과 패딩: 데이터 접근 성능 향상을 위해 8바이트 혹은 16바이트 경계를 맞추는 패딩(Padding)의 중요성을 배웠습니다.
  • 프롤로그 및 에필로그: 힙의 시작과 끝을 알리는 파수꾼(Sentinel) 블록을 두어, 메모리 순회 시 발생할 수 있는 경계 오류를 방지했습니다.

2. 가용 리스트 관리 전략: Implicit vs Explicit

가용 블록(Free Block)을 어떻게 찾아낼 것인가에 따라 할당기의 성능이 극명하게 갈리는 것을 확인했습니다.

  • Implicit Free List (묵시적 가용 리스트): 구현은 단순하지만 힙이 커질수록 탐색 비용이 블록 수에 비례하여 증가하는 비확장적 자료구조임을 체감했습니다.
  • Explicit Free List (명시적 가용 리스트): 가용 블록 내에 next, prev 포인터를 두어 탐색 속도를 높였습니다.
    • LIFO (Last-In-First-Out): 삽입 속도가 매우 빠르지만 단편화에 취약할 수 있습니다.
    • Address-Ordered: 주소 순으로 정렬하여 병합(Coalescing) 효율을 극대화하고 단편화를 방지했습니다.

 

implicit first fit/ explicit first fit/ explicit best fit 비교

3. 더 나은 성능을 위한 고도화 기법

기본적인 할당기를 넘어 실제 시스템에서 사용되는 고도화된 전략들을 학습했습니다.

  • 분리 가용 리스트(Segregated Free List): 가용 블록을 크기 구간별로 여러 리스트로 나누어 관리하여 탐색 시간을 단축했습니다.
  • 버디 시스템(Buddy System): 블록 크기를 2의 거듭제곱 단위로 관리하여 빠른 병합과 분할을 가능하게 하는 구조를 이해했습니다.

4. 운영체제 핵심 개념 정리

메모리 관리의 배경이 되는 커널의 동작 원리도 함께 정리했습니다.

  • System Call: sbrk나 mmap을 통해 사용자 프로세스가 커널에 메모리 자원을 요청하는 인터페이스를 학습했습니다.
  • 가상 메모리(Virtual Memory): 각 프로세스가 독립적인 메모리 공간을 가진 것처럼 추상화해주는 원리와 보안 및 안정성을 위한 격리의 중요성을 배웠습니다.

 

'Jungle > WIL(Weekly I Learned)' 카테고리의 다른 글

[WIL] 6주  (0) 2026.04.06
[WIL] 5주  (0) 2026.03.27
[WIL] 4주  (0) 2026.03.19
[WIL] 3주  (0) 2026.03.19
[WIL] 2주  (0) 2026.03.12

Explicit Free List: Unordered vs. Address-Ordered

  • Explicit Free List(명시적 가용 리스트)는 가용 블록(Free Block) 내에 next와 prev 포인터를 포함시켜, 가용 블록들끼리만 연결 리스트 형태로 관리하는 방식입니다. 이때 리스트를 유지하는 두 가지 주요 전략을 비교해 드립니다.

1. Unordered (LIFO - Last-In-First-Out)

가용 블록을 리스트에 삽입할 때 특별한 순서 없이, 보통 리스트의 맨 앞(Root)에 가장 최근에 반환된 블록을 넣는 방식입니다.

  • 동작: free()가 호출되면 해당 블록을 리스트의 시작 부분에 즉시 연결합니다.
  • 장점 (O(1)): 삽입 속도가 매우 빠릅니다. 단순히 포인터 몇 개만 바꿔주면 끝납니다.
  • 단점: 메모리 주소와 상관없이 리스트가 구성되므로, 인접한 가용 블록들을 합치는 Coalescing(병합) 효율이 떨어질 수 있으며, Address-ordered 방식에 비해 메모리 단편화(Fragmentation)가 더 많이 발생할 수 있습니다.

2. Address-Ordered (주소 순서 정렬)

가용 리스트의 블록들을 항상 메모리 주소 순서대로 정렬된 상태로 유지하는 방식입니다. (예: Addr(A) < Addr(B) < Addr(C))

  • 동작: free()가 호출되면 리스트를 처음부터 순회하며, 해당 블록의 주소가 들어갈 적절한 위치를 찾아 삽입합니다.
  • 장점: First-fit 검색 시 메모리 낮은 주소부터 효율적으로 채울 수 있고, 인접한 블록이 리스트 상에서도 앞뒤에 위치할 가능성이 높아 병합(Coalescing) 성능이 우수합니다. 결과적으로 단편화가 적습니다.
  • 단점 (O(n)): 삽입할 때마다 리스트를 탐색해야 하므로 반환(free) 작업 시 시간이 더 소요됩니다.

💡 한눈에 비교하기

구분 Unordered (LIFO) Address-Ordered
삽입 속도 매우 빠름 (O(1)) 상대적으로 느림 (O(n))
단편화 방지 보통 수준 우수함
병합 효율 보통 매우 높음
구현 난이도 쉬움 리스트 순회 로직 필요

 

Implicit VS Explicit 비교

 

🖥️ 운영체제 및 메모리 관리 핵심 정리

1. 시스템 콜 (System Call)

개념: 사용자 프로세스가 운영체제(OS) 커널이 제공하는 서비스를 이용하기 위해 커널 함수를 호출하는 인터페이스입니다.

  • 필요성: 응용 프로그램은 하드웨어(CPU, 메모리, 디스크 등)에 직접 접근할 수 없습니다. 보안과 안정성을 위해 OS가 자원을 관리하며, 응용 프로그램은 ‘요청’을 통해서만 자원을 사용할 수 있습니다.
  • 작동 원리:
    1. 사용자 모드(User Mode)에서 실행 중인 프로그램이 시스템 콜을 호출합니다.
    2. 트랩(Trap)이 발생해 커널 모드(Kernel Mode)로 전환됩니다.
    3. 커널이 요청을 처리한 뒤 사용자 모드로 복귀합니다.
  • 주요 예시: fork()(프로세스 생성), read()/write()(파일 I/O), exit()(종료).

2. 데이터 세그먼트 (Data Segment)

개념: 프로세스의 가상 메모리 공간 중 전역 변수와 정적 변수(static)가 저장되는 영역입니다.

  • 메모리 구조 내 위치: 보통 코드(Text) 세그먼트 위에 있고, 힙(Heap) 세그먼트 아래에 위치합니다.
  • 세부 분류:
    • Data 세그먼트 (Initialized): 초기화된 전역 변수 및 정적 변수가 저장됩니다. (예: int a = 10;)
    • BSS 세그먼트 (Uninitialized): 초기화되지 않은 전역 변수 및 정적 변수가 저장됩니다. 프로그램 시작 시 0으로 초기화됩니다. (예: int b;)
  • 특징: 프로그램 시작 시 할당되며 종료 시 해제됩니다.

3. 메모리 단편화 (Memory Fragmentation)

개념: 메모리가 할당·해제되는 과정에서 총 메모리 공간은 충분하지만, 실제로는 할당할 수 없는 작은 조각들이 생기는 현상입니다.

① 내부 단편화 (Internal Fragmentation)

  • 상황: 할당된 메모리 블록이 요청한 크기보다 클 때 발생합니다.
  • 결과: 할당된 블록 내부에 남는 공간이 생겨 낭비됩니다.

② 외부 단편화 (External Fragmentation)

  • 상황: 메모리 사이사이에 남은 작은 공간들의 합은 충분하지만, 연속된 공간이 아니라서 큰 프로세스를 할당하지 못하는 상태입니다.
  • 해결책: 페이징(Paging), 압축(Compaction), 세그멘테이션(Segmentation).

4. sbrk와 mmap (힙 확장 방식)

메모리 할당자(malloc 등)가 커널로부터 더 많은 메모리를 확보하기 위해 사용하는 시스템 콜입니다.

① sbrk (Set Program Break)

  • 방식: 데이터 세그먼트의 끝을 나타내는 ‘program break’ 주소를 위로 올리거나 아래로 내려 힙 공간을 확장·축소합니다.
  • 특징: 메모리가 연속적으로 할당되며, 주로 작은 크기의 메모리 할당에 사용됩니다.

② mmap (Memory Map)

  • 방식: 특정 파일이나 익명 메모리 공간(Anonymous Memory)을 프로세스의 가상 메모리 주소 공간에 직접 매핑합니다.
  • 특징:
    • 힙 영역이 아닌 별도의 메모리 매핑 영역에 할당됩니다.
    • 주로 큰 크기의 메모리 할당(보통 128KB 이상)에 사용됩니다.
    • 메모리를 해제하면 즉시 커널에 반환할 수 있어 단편화 방지에 유리합니다.

💡 요약 비교표

구분 sbrk mmap

주요 용도 작은 메모리 할당 (연속적) 큰 메모리 할당 (비연속적/독립적)
작동 방식 heap의 끝 포인터를 이동 가상 메모리에 새 매핑 영역 생성
장점 오버헤드가 적음 메모리 반환이 유연함, 공유 메모리 가능

유튜브 영상 링크

🧠 동적 메모리 할당 (Dynamic Memory Allocation) 총정리

1. 개요 및 기본 개념

1.1. 동적 메모리 할당이란?

프로그램 실행 중(Runtime) 가상 메모리를 할당받는 방식입니다. 컴파일 타임에 크기를 알 수 없는 가변적인 데이터 구조를 다룰 때 필수적입니다.

  • 메모리 영역 (Heap): 동적 할당은 힙(Heap) 영역에서 이루어집니다.
    • 가상 주소 공간 내에 존재하며, 스택(Stack)과 반대 방향으로 성장합니다.
    • 시스템 포인터(BRK)에 의해 경계가 정해지며, 매우 큰 공간을 가질 수 있습니다.
  • 힙 내의 블록: 할당자는 힙을 가변 크기의 블록으로 관리하며, 각 블록은 할당(Allocated) 또는 자유(Free) 상태 중 하나입니다.

1.2. 할당자의 분류

구분 특징 대표 언어

명시적 할당자 개발자가 직접 malloc으로 할당하고 free로 해제 C, C++
암시적 할당자 가비지 컬렉터(GC)가 미사용 메모리를 자동 해제 Java, Python

1.3. C 언어 주요 함수

함수 역할 비고

malloc(size) 요청 크기 이상의 블록 포인터 반환 16-바이트 정렬 보장, 실패 시 NULL
free(ptr) 할당된 메모리 블록 해제 malloc/realloc으로 할당된 주소여야 함
realloc(ptr, size) 이미 할당된 영역의 크기 변경 기존 데이터 보존 시도
calloc(n, size) 메모리 할당 및 0으로 초기화 요소 개수와 크기를 인자로 받음
sbrk(incr) BRK 포인터를 이동시켜 힙 크기 조절 시스템 내부에서 주로 사용

1.4. 설계 가상 및 예시

  • 주소 지정: 단어(Word) 단위 주소 지정 가능.
  • 정렬(Alignment): 2단어(Double Word) 단위 정렬 (예시에서는 2-바이트 경계).
  • 시각화 규칙:
    • 🟩 녹색: 할당된 블록 (Allocated)
    • 무색: 자유 블록 (Free)
  • 공간 낭비 예시: 정렬 경계 요구사항 때문에 할당 요청 사이에 사용하지 못하는 틈새 공간이 발생할 수 있습니다.

2. 제약 조건 및 성능 목표

2.1. 할당자의 제약 조건

할당자는 프로그램의 요청에 즉각 응답해야 하며, 메모리를 임의로 옮길 수 없습니다.

  1. 요청 제어 불가: 프로그램이 어떤 순서로 할당/해제를 요청할지 알 수 없음.
  2. 즉각 응답: 요청을 재정렬하거나 기다릴 수 없음.
  3. 정렬 준수: 아키텍처별 정렬 요구사항을 반드시 만족해야 함.
  4. 이동 불가(No Moving): 할당된 블록을 다른 위치로 옮길 수 없음 (free 시 위치를 알 수 없게 되기 때문).

2.2. 성능 목표 (Trade-off 관계)

  • 처리량 (Throughput): 단위 시간당 완료된 요청 수

$$ 10,000\ operations / 10\ sec = 1,000\ ops/sec $$

  • 최대 메모리 활용률 (Utilization): 힙을 얼마나 알뜰하게 사용하는가?
    • 낭비 요인: 패딩(Padding), 관리용 오버헤드(Header), 단편화.
  • $$ \frac{\max(\text{Payload 합계})}{\text{Current Heap Size}} $$

3. 메모리 단편화 (Fragmentation)

3.1. 내부 단편화 (Internal Fragmentation)

  • 원인: 요청한 크기보다 더 큰 블록이 할당될 때 발생 (정렬 패딩, 탐색 정책의 한계).
  • 특징: 할당된 블록 내부에 존재하므로 측정이 명확함.

3.2. 외부 단편화 (External Fragmentation)

  • 원인: 자유 공간의 총합은 충분하지만, 연속적이지 않고 조각나 있어 할당 요청을 수용하지 못하는 상태.
  • 특징: 미래의 요청 패턴에 따라 달라지므로 예측이 어려움.

4. 할당자 구현 시 고려 사항

4.1. 정보 저장 (Header)

  • free(ptr) 시 크기를 알기 위해 블록 시작점에 길이 필드(Length Field)를 저장합니다.
  • 이 필드에는 (페이로드 + 헤더) 크기가 포함되며, 하위 비트(LSB)를 사용하여 할당 여부를 표시합니다 (0: Free, 1: Allocated).

4.2. 자유 블록 관리 (Free List)

  1. 암시적 리스트 (Implicit List): 모든 블록(할당/자유)을 헤더 정보를 따라 순차 탐색. 구현이 간단하나 탐색이 느림(O(N)).
  2. 명시적 리스트 (Explicit List): 자유 블록들만 포인터로 연결. 할당된 블록을 건너뛰어 탐색이 빠름.
  3. 분리 자유 리스트 (Segregated List): 크기별로 리스트를 따로 관리하여 속도 향상.
  4. 균형 트리 (Balanced Tree): 크기순으로 정렬하여 최적의 핏(Best-fit)을 빠르게 탐색.

4.3. 블록 분할 (Splitting)

자유 블록이 요청보다 클 경우, 필요한 만큼만 할당하고 남은 공간을 새로운 자유 블록으로 쪼개어 단편화를 방지합니다.

4.4. 블록 병합 (Coalescing)

해제된 블록 주위에 다른 자유 블록이 있다면 하나로 합칩니다.

  • 경계 태그 (Boundary Tags): 각 블록의 끝에 푸터(Footer)를 두어 이전 블록의 정보를 즉시 확인합니다 (O(1)병합 가능).
  • 4가지 경우: (앞/뒤 상태에 따라) ①둘 다 할당됨 ②뒤만 자유 ③앞만 자유 ④둘 다 자유.
  • 시점: free 시 즉시 수행하거나(Immediate), 나중에 필요할 때 한꺼번에 수행(Deferred).

5. 할당 정책 요약

  • 배치 정책 (Placement):
    • First Fit: 첫 번째로 맞는 블록 선택.
    • Next Fit: 이전 탐색 종료 지점부터 시작.
    • Best Fit: 크기가 가장 딱 맞는 블록 선택 (메모리 효율 최상, 탐색 속도 최저).
  • 암시적 리스트의 한계: 구현은 쉬우나 할당 속도가 전체 블록 수에 비례하므로 실제 시스템에서는 다른 구조와 병행하여 사용됩니다.

'프로그래밍 언어 > C언어' 카테고리의 다른 글

Explicit Free List  (0) 2026.04.16
CSAPP 9.9장 모르는 부분 정리  (0) 2026.04.10
pintos를 위한 c언어 강의  (1) 2026.04.08

📌 1. 힙 확장과 sbrk 동작 구조

핵심 개념

  • sbrk(incr) = 힙의 끝(brk)을 늘림
  • 반환값 = 이전 brk (새 메모리 시작 주소)

핵심 포인트

  • allocator는 “새 공간 시작 주소”가 필요
  • 그래서 old brk 반환

📌 2. 메모리 정렬과 패딩

핵심

👉 정렬(alignment) 때문에 필요

  • 요청: 13 bytes → 실제: 16 bytes
  • 남는 공간 = padding

효과

  • 주소 계산 단순화
  • 성능 향상

📌 3. 힙 구조 (Prologue / Epilogue)

구조

[padding][prologue][block...][epilogue]

핵심

  • epilogue = 힙 끝 표시
  • coalesce 오류 방지

📌 4. 가상 메모리

정의

👉 프로세스마다 독립된 주소공간처럼 보이게 하는 기술

핵심

  • 실제: 물리 메모리 분산
  • 보이는 것: 연속 공간

📌 5. 비확장성 구조 (Implicit Free List)

문제

  • 모든 블록 순회
  • O(n)

결론

👉 힙 커질수록 성능 붕괴


📌 6. 고급 할당 전략


🔹 Segregated Free List

  • 크기별 리스트 분리
  • 탐색 속도 ↑

🔹 Buddy System

  • 2의 거듭제곱 기반
  • merge 빠름

📌 7. extern

핵심

👉 다른 파일 변수 참조

extern int count;

 

  • 메모리 생성 X
  • 참조만 O

📌 전체 흐름

가상메모리
   ↓
힙
   ↓
sbrk 확장
   ↓
정렬 + 패딩
   ↓
prologue / epilogue
   ↓
implicit list (느림)
   ↓
segregated / buddy (개선)


prologue와 epliogue 왜 필요할까????

'프로그래밍 언어 > C언어' 카테고리의 다른 글

Explicit Free List  (0) 2026.04.16
동적 메모리 할당 basic 영상 요약  (0) 2026.04.16
pintos를 위한 c언어 강의  (1) 2026.04.08

Week 6 Project

C로 구현한 파일 기반 SQL 처리기 프로젝트

입력된 SQL을 파싱하고 실행해 실제 파일에 반영하는 작은 SQL Processor를 직접 구현했다.

프로젝트 한 줄 소개

이번 프로젝트에서는 C 언어로 파일 기반 SQL 처리기(SQL Processor)를 구현했다. 목표는 단순히 SQL 문법을 읽는 데서 끝나는 것이 아니라, 입력된 SQL을 파싱하고 실행해 실제 CSV 파일에 반영하는 전체 흐름을 만드는 것이었다.

특히 이번 과제는 학습보다는 구현이 우선이었기 때문에, AI를 적극적으로 활용해 빠르게 기능을 완성하되, 핵심 로직은 직접 이해하고 설명할 수 있는 수준까지 정리하는 데 집중했다.

1. 프로젝트 목표

이번 과제의 핵심은 아래 흐름을 하나의 프로그램으로 완성하는 것이었다.

입력(SQL) → 파싱 → 실행 → 저장
  • CLI(Command Line Interface)를 통한 SQL 입력 처리
  • INSERT, SELECT SQL 파싱
  • 파싱 결과를 기반으로 한 실행 로직
  • 파일 기반 데이터 저장소 설계
  • schema와 table이 이미 존재한다고 가정한 구조 처리

2. 요구사항 기준으로 정리한 구현 내용

2-1. CLI 기반 SQL 입력 처리

텍스트 파일로 작성된 SQL문을 Command Line에서 전달할 수 있도록 구현했다. SQL 파일 경로를 넘겨 실행할 수 있고, SQL 문자열 자체를 직접 인자로 입력하는 방식도 함께 지원했다.

sqlparser.exe .\examples\select_all_users.sql
sqlparser.exe "SELECT name, age FROM student;"

과제에서 요구한 “텍스트 파일 기반 SQL 전달” 조건을 충족하면서, 실제 사용 편의성을 위해 직접 입력 방식도 확장했다.

2-2. SQL 파싱 구현

입력된 SQL 문장을 구조화하기 위해 Lexer와 Parser를 분리해 구현했다. 먼저 Lexer가 SQL을 토큰 단위로 분리하고, 이후 Parser가 이를 읽어 문장의 의미를 해석한다.

  • INSERT
  • SELECT

파싱 결과는 AST(Abstract Syntax Tree) 구조체에 담아 실행 단계에서 활용할 수 있도록 구성했다.

2-3. Execution 구현

파싱된 결과를 실제 동작으로 연결하는 실행 모듈도 구현했다. 이 부분이 SQL 처리기의 핵심 역할을 담당한다.

  • INSERT: 파싱된 컬럼/값 정보를 바탕으로 파일에 데이터 추가
  • SELECT: 파일에서 데이터를 읽고 요청한 컬럼을 출력

2-4. File 기반 DB 설계

데이터 저장 포맷은 자유 설계가 가능했기 때문에, 구현과 디버깅이 쉬운 CSV 형식을 선택했다.

  • schema/<table>.meta : 테이블 구조 정보
  • data/<table>.csv : 실제 데이터
table=student
columns=id,department,student_number,name,age

3. INSERT와 SELECT는 어떻게 동작하게 만들었나

INSERT 처리

INSERT 실행 시에는 먼저 schema를 읽어 컬럼 구성을 확인한 뒤, 입력된 값을 올바른 순서에 맞춰 CSV의 새 행으로 추가하도록 구현했다.

  • 입력 컬럼 수와 값 수 일치 여부 확인
  • 존재하지 않는 컬럼 사용 방지
  • 부분 컬럼 INSERT 지원
  • 비어 있는 컬럼은 빈 문자열로 채우기

SELECT 처리

SELECT 실행 시에는 CSV 파일을 읽어 헤더를 기준으로 컬럼 위치를 찾고, 사용자가 요청한 컬럼만 골라 출력하도록 만들었다.

  • SELECT * FROM table; 지원
  • SELECT col1, col2 FROM table; 지원
  • 컬럼 헤더와 함께 결과 출력

4. 내부 구조 설계

프로젝트는 Top-down으로 이해하고 확장하기 쉽도록 기능을 모듈 단위로 분리했다.

main
입력 처리와 전체 실행 흐름 연결
lexer
SQL 문자열 토큰화
parser
SQL 문장 구조 해석
ast
파싱 결과 저장
executor
INSERT / SELECT 실행
schema
테이블 구조 검증
storage
CSV 읽기/쓰기

5. 추가 구현 요소

기본 요구사항을 충족하는 데서 끝나지 않고, 차별화 요소도 일부 반영했다.

  • SQL 파일 입력뿐 아니라 SQL 문자열 직접 실행 지원
  • 간단한 interactive CLI 모드 지원
  • 부분 컬럼 INSERT 지원
  • CSV escape 처리로 쉼표와 따옴표 포함 값 저장 가능
  • 한글 식별자와 별칭 기반 테이블 처리 확장

6. 이번 프로젝트에서 중요했던 점

이번 과제는 단순히 결과물을 제출하는 것이 아니라, AI가 만든 코드까지 포함해 결국 내 코드처럼 이해하고 설명할 수 있어야 한다는 점이 중요했다.

그래서 구현 과정에서는 AI를 활용해 빠르게 초안을 만들고 구조를 확장했지만, 최종적으로는 입력된 SQL이 어떤 단계로 처리되는지, 왜 Parser와 Executor를 분리했는지, 왜 CSV를 선택했는지까지 직접 설명할 수 있도록 정리했다.

마무리

이번 SQL 처리기 프로젝트에서는 요구사항에 맞춰 CLI 입력, SQL 파싱, 실행 로직, 파일 기반 저장 구조를 모두 직접 연결했다. CREATE TABLE 같은 고급 기능은 제외했지만, 대신 최소 요구사항인 INSERT와 SELECT를 끝까지 동작하도록 완성하는 데 집중했다.

작은 범위의 SQL 엔진이지만, 입력된 SQL이 실제 데이터 파일에 반영되는 흐름을 직접 구현해보면서 DB 처리기의 핵심 구조를 훨씬 선명하게 이해할 수 있었다. 구현 우선의 과제였던 만큼, AI를 활용해 빠르게 만들고 동시에 핵심 로직은 스스로 설명할 수 있게 정리한 점이 이번 프로젝트의 가장 큰 의미였다.

[강의 요약] C언어의 심장: 포인터와 메모리 관리 완벽 정리

안녕하세요! 오늘은 프로그래밍의 '근본'이라 불리는 C언어, 그중에서도 많은 학습자가 어려워하지만 반드시 넘어야 할 산인 메모리와 포인터에 대해 정리해 보겠습니다. 파이썬처럼 친절한 언어와 C언어는 무엇이 다른지, 왜 우리가 직접 메모리를 만져야 하는지 핵심만 짚어드릴게요.


1. C언어는 왜 '불친절'할까? (vs Python)

파이썬이 모든 것을 알아서 해주는 '자동 자가용'이라면, C언어는 운전자가 엔진 노즐 하나하나를 조절해야 하는 '수동 레이싱카'와 같습니다.

  • 하드웨어 제어: CPU와 메모리를 직접 컨트롤하기 때문에 성능이 강력합니다.
  • 명시적 타입: 모든 데이터의 크기를 프로그래머가 직접 결정해야 합니다.
  • 불친절한 에러: 잘못된 메모리에 접근하면 친절한 설명 대신 Segmentation Fault와 함께 프로그램이 종료됩니다.

2. 변수를 바라보는 새로운 시각: 주소, 값, 그리고 크기

C언어에서 변수는 단순히 값을 담는 그릇이 아니라, 메모리 상의 특정 위치를 가리키는 별명(Symbol)입니다.

  • 주소(&a): 메모리 어디에 있는가?
  • 값(a): 그 위치에 무엇이 들어있는가?
  • 크기(sizeof): 그 위치부터 몇 바이트를 사용하는가?

💡 중요 팁: C언어에서 데이터 타입(int, long 등)의 크기는 시스템마다 다를 수 있습니다. 따라서 이식성 있는 코드를 위해 stdint.h의 고정 너비 타입(int32_t, int64_t)을 사용하는 습관이 좋습니다.


3. 통곡의 벽, '포인터(Pointer)' 제대로 이해하기

포인터는 어렵지 않습니다. "주소를 저장하는 변수"일 뿐입니다.

  • 역참조(*): 포인터가 가리키는 주소로 '직접 찾아가서' 값을 읽거나 쓰는 행위입니다.
  • 포인터 타입의 비밀: int*char*는 주소 값 자체의 크기(64비트 기준 8바이트)는 같지만, 찾아갔을 때 몇 바이트를 읽어야 할지를 결정합니다.
  • 포인터 연산: p + 1은 주소 값에 1을 더하는 게 아니라, 가리키는 타입의 크기만큼 건너뛰는 것을 의미합니다.

4. 메모리 관리의 핵심: 힙(Heap)과 동적 할당

함수가 종료되면 사라지는 지역 변수(Stack)와 달리, 프로그램 실행 중에 원하는 만큼 메모리를 쓰고 싶을 때 힙(Heap) 영역을 사용합니다.

  • malloc: 메모리 빌려오기. (반환 타입이 void*이므로 적절한 형변환이 필요합니다.)
  • free: 빌린 메모리 반납하기.
  • 메모리 누수(Memory Leak): 빌려놓고 반납(free)하지 않으면 시스템의 메모리가 고갈됩니다. "malloc과 free는 항상 짝꿍"임을 잊지 마세요!

5. 구조체(Struct)와 메모리 정렬의 마법

서로 다른 타입을 묶는 구조체에는 숨겨진 원리가 있습니다. 바로 패딩(Padding)입니다.

CPU는 메모리 접근 효율을 위해 데이터를 특정 단위로 정렬합니다. 이 과정에서 빈 공간(Padding)이 생겨, 실제 구조체 크기가 멤버들의 합보다 커질 수 있습니다.

  • 최적화 팁: 크기가 큰 타입부터 작은 타입 순서로 멤버를 선언하면 패딩을 줄여 메모리를 아낄 수 있습니다.

✅ 오늘 강의의 핵심 '코딩 습관' 5가지

  1. sizeof 생활화: 타입 크기를 짐작하지 말고 항상 sizeof()로 확인하세요.
  2. 포인터 초기화: 선언과 동시에 NULL이나 유효한 주소로 초기화하세요.
  3. 중괄호({}) 생략 금지: if/for문 뒤에 코드가 한 줄이라도 중괄호를 쓰는 것이 안전합니다.
  4. 우선순위는 괄호로: 연산자 우선순위가 헷갈릴 땐 무조건 ()를 쓰세요.
  5. 문자열 끝은 \0: C언어 문자열은 마지막 널 문자를 위한 1바이트가 더 필요함을 명심하세요.

'프로그래밍 언어 > C언어' 카테고리의 다른 글

Explicit Free List  (0) 2026.04.16
동적 메모리 할당 basic 영상 요약  (0) 2026.04.16
CSAPP 9.9장 모르는 부분 정리  (0) 2026.04.10

[에세이] AI 시대, 정답보다 '고민의 시간'이 더 가치 있는 이유

AI에게 물어보면 10초 만에 코드가 나오고 답이 나오는 시대입니다. 상황이 이렇다 보니, 가끔 이런 회의감이 들곤 합니다.

"어차피 AI가 다 해주는데, 굳이 내가 머리 싸매며 고통받을 필요가 있을까?"

결론부터 말씀드리면, 그 '고통스러운 과정'은 단순히 답을 찾는 시간이 아니라 당신의 뇌를 업데이트하는 필수적인 과정입니다. 왜 우리가 여전히 스스로 고민해야 하는지 그 이유를 정리해 보았습니다.


1. '도구'를 다루는 '근육'은 고통 속에서 자랍니다

AI를 사용하는 것은 마치 고성능 엑셀 프로그램을 쓰는 것과 같습니다. 하지만 수의 개념과 연산 원리를 모르는 상태에서 엑셀만 쓰면, 수치가 조금만 틀려도 왜 틀렸는지 알 수 없고 응용도 불가능합니다.

  • 직접 고민하는 과정: 뇌의 뉴런이 연결되며 '문제 해결 근육'을 만드는 과정입니다.
  • AI의 정답: 결과물(Output)만 얻는 과정입니다. 내 안의 근육은 생기지 않습니다.

결국 기초 체력이 없으면 AI라는 강력한 도구도 제대로 다룰 수 없게 됩니다.

2. AI의 '그럴듯한 거짓말'을 가려내는 안목

AI는 완벽하지 않습니다. 때로는 '환각(Hallucination)' 현상으로 틀린 답을 아주 당당하게 제시하곤 하죠. 스스로 깊게 고민해 본 사람은 AI가 준 답이 '왜 맞는지' 혹은 '어디가 틀렸는지'를 파악할 수 있는 눈을 갖게 됩니다.

직접 바닥까지 내려가 삽질(?)해 본 데이터가 머릿속에 쌓여 있어야만, 비로소 AI를 도구로서 부릴 수 있는 '진정한 주인'이 됩니다.

3. '학습의 구덩이(The Learning Pit)'가 주는 선물

교육학에는 '학습의 구덩이'라는 개념이 있습니다. 새로운 개념을 배울 때 우리는 반드시 혼란과 고통의 구덩이에 빠집니다. 여기서 빠져나오기 위해 애쓰는 과정에서 비로소 진정한 학습과 깊은 이해가 일어납니다.

AI로 곧장 점프하는 것은 이 구덩이를 건너뛰는 것입니다. 당장은 편하겠지만, 장기적으로는 지적 성장을 방해하는 독이 될 수 있습니다.


💡 AI 시대, 지혜롭게 '삽질'하는 법

무조건 고통받는 것만이 답은 아닙니다. 효율적인 성장을 위해 다음의 '의도적인 고군분투' 전략을 추천합니다.

  1. 타임박스(Time-boxing) 설정: 문제를 마주하면 최소 30분은 AI 없이 스스로 고민해 보세요. 이때 당신의 뇌가 가장 활발하게 움직입니다.
  2. 답이 아닌 '힌트' 요청하기: "정답을 알려줘" 대신 "내가 여기까지 생각했는데, 다음 단계로 가기 위한 실마리만 줘"라고 질문해 보세요.
  3. 사후 복기: AI의 답을 확인했다면, "왜 나는 이 생각을 못 했을까?"를 다시 반추하며 그 로직을 내 것으로 흡수해야 합니다.

마치며

AI는 우리의 '가속기'가 되어야지, '엔진' 자체를 대체하게 두어서는 안 됩니다.

지금 문제를 붙잡고 씨름하며 느끼는 그 막막함과 고통은, 당신의 능력이 확장되고 있다는 아주 건강한 신호입니다. 정답을 빨리 찾는 것보다, '정답에 이르는 사고의 경로'를 내 것으로 만드는 데 집중해 보세요.

결국 마지막에 웃는 사람은 AI를 잘 쓰는 사람이 아니라, AI 없이도 문제를 해결할 줄 아는 능력을 갖춘 채 AI를 도구로 부리는 사람일 테니까요.

이동석코치님과 커피챗을 진행했다. 

같은 동기 한명과 진행했고, 요약해보자면

 

1. c언어는 portability가 약하고, 요즘은 rust를 많이 사용한다고한다. 다만 미국 이야기이고 한국은 아직이라고한다.

2. c언어는 매크로를 많이 사용하여 오픈소스 읽기가 힘들고, 여러가지 규칙이 있어서 이것을 먼저 아는 것이 이해하는데 도움이 된다고한다.

3. c언어의 성능이 느린부분을 어셈블리어로 교체하는 작업을 하는데, 함수마다 성능을 측정하는 것을 프로파일링이라고한다.

    프로파일링툴이 많으며 이것으로 성능을 측정하여 느리부분을 어셈블리어로 교체하여 성능을 개선한다.

4. 좋은 코드란 확장성과 다른사람이 읽기 쉬운 코드이다

 

 

 

'Jungle > Everyday' 카테고리의 다른 글

4/1 수요코딩 리액트(2)  (0) 2026.04.05
3/30 파이썬 비동기 프로그래밍  (0) 2026.03.31
3/27 DP(Dynamic Programming)  (0) 2026.03.28
3/21  (0) 2026.03.22
3/18  (0) 2026.03.19
카테고리 주간 실행 목표 (Action Item) 결과
01. 문제해결 매일 자료구조 c언어 구현 문제풀이로 논리적 사고 훈련
02. 설계 수요코딩시 아키텍쳐를 먼저 생각해보기
03. 구현 자료구조 구현시 엣지 케이스 생각하며 구현하기
04. 품질 자료구조 검증 테스트 케이스 추가 해보기
05. 유지보수 변수 명명에 의미를 담아 가독성 높은 코드 작성하기
06. 협업 작업 현황을 팀원들에게 즉시 공유하여 투명한 소통 유지
07. 태도 과제 부여 시 의도와 상위 카테고리를 먼저 분석하는 습관
08. 비즈니스 이해 사용성을 생각하며 코드 생산
09. AI & 생산성 하네스 사용해서 바이브 코딩 진행해보기
10. 학습 민첩성 SQL이 뭔지 이해하고 SQL 문장을 읽고, 파싱하고, 실행하는 처리기를 만들기

 

📅 이번 주 학습 및 프로젝트 요약 (4월 2주차)

이번 주 블로그에 기록했던 주요 프로젝트와 학습 내용을 한눈에 볼 수 있도록 정리했습니다.

1. C로 구현한 파일 기반 SQL 처리기

직접 SQL 파서와 실행기를 구현해 보는 6주차 프로젝트를 진행했습니다. Lexer와 Parser를 통해 SQL을 분석하고, 이를 바탕으로 실제 CSV 파일에 데이터를 INSERT하거나 SELECT 하는 흐름을 완성했습니다. AI를 전략적으로 활용해 빠르게 기능을 구현하면서도 핵심 로직을 스스로 이해하는 데 집중했습니다.

프로젝트 상세 보기 →

2. Pintos를 위한 C언어 핵심 정리

Pintos 과제 수행 전 필수적인 C언어의 메모리 관리와 포인터 개념을 정리했습니다. 파이썬과 대비되는 C언어의 특징, 주소와 값의 차이, malloc/free 활용법, 그리고 구조체 패딩(Padding) 최적화 등 실무적인 팁을 다루었습니다.

C언어 정리 확인하기 →

3. AI 시대, '고민의 시간'이 주는 가치

AI가 정답을 알려주는 시대에 왜 직접 고민하는 과정이 필요한지 정리한 에세이입니다. 문제 해결 근육을 키우는 '학습의 구덩이' 개념과 더불어, AI를 단순 정답기가 아닌 지적 가속기로 활용하는 전략(Time-boxing 등)을 공유했습니다.

에세이 읽어보기 →

4. 이동석 코치님과의 커피챗 (4/6)

현직 코치님과의 대화를 통해 Rust의 동향, C언어 오픈소스 분석 팁, 그리고 성능 최적화를 위한 프로파일링(Profiling)의 중요성을 배웠습니다. '좋은 코드란 무엇인가'에 대한 근본적인 고민을 할 수 있었던 유익한 시간이었습니다.

커피챗 요약 보기 →

#정글 #Jungle #SQL처리기 #C언어 #AI학습 #커피챗 #개발자성장기

'Jungle > WIL(Weekly I Learned)' 카테고리의 다른 글

[WIL] 7주  (0) 2026.04.16
[WIL] 5주  (0) 2026.03.27
[WIL] 4주  (0) 2026.03.19
[WIL] 3주  (0) 2026.03.19
[WIL] 2주  (0) 2026.03.12

+ Recent posts