크래프톤 정글 6주차 - C언어 + (SQL parser) 수요코딩회 + WIL & 팀원 회고

jypark·2026년 4월 9일
post-thumbnail

6주차 정글 스케쥴 (4월 3일 ~ 4월 9일)

C Programming 시작 (1주차 / 총 3주 일정)

  • LinkedList
  • Stack & Queue
  • Binary Tree
  • BST

4월 3일 금요일

  • Week 6팀 구성
  • LL 1문제 & CS:APP 스터디 참관
  • 자체 팀 코어 타임 1 : LL 1번 풀이
  • 목 아파서 저녁 타임 직전에 이비인후과 갔다옴

4월 4일 토요일

  • 반에 많은 애들 집 갔다온다고 외출을 많이 써서 상당히 비어있던 날
  • LL All & S&Q 2문제
  • 자체 팀 코어 타임 2 : malloc, **(Double Pointer)

4월 5일 일요일

  • 사실상 뭐 BST 마지막 문제 빼고 모두 Solved, 반에 5명 정도 밖에 없어서 매우 집중이 잘 됬었고, 중간에 점심 먹으러 나갔다 왔는데도 널널했음.
  • C로 알고리즘 주간 문제 구현 느낌이라 나쁘지 않았음.

4월 6일 월요일

  • BST 마지막 문제 Solved
  • CS:APP 요약본 읽기
  • NotebookLLM으로 C언어 복습
  • 자체 팀 코어 타임 3 : LL 6번 예정 (재귀)

4월 7일 화요일

  • 동석 코치 Pintos 대비 C언어 강의
  • 수요 코딩회 Preview

4월 8일 수요일

  • 수요코딩회 - SQL Parser - C로 개발
  • 수요코딩회 - 팀 리딩 및 PM (Parser 영역 직접 구현)
  • 개발 및 시연 정리

4월 9일 목요일

  • 수요코딩회 개발한 것 발표
  • Week6 팀 회고
  • WIL 작성

WIL

1. 이번 주 목표

이번주부터는 이제 4로 고정이다. 우리 반에 한 명이 저번주 화요일 랜덤 런치 기준으로 나가신 걸로 안다. (3-3-4-3-3-4), 저번주와 동일하게, 수요코딩회는 비슷하게 준비하였고, (변수가 좀 존재하면 update)

C를 안 본지 너무 오래됬고, malloc, pointer 모두 무서웠던 개념인데, 학부 때와 다르게 지금은 AI도 있고, 모르는 건 다 물어보면 되서 개념적으로 학부 때에 비해 안정성이 매우 올라갔다. project는 docker에 ubuntu 24.04 환경으로, devcontainer로 실행해서 프로젝트 진행중이다.

이번 주는 새로운 챕터로 넘어가는 주이기에 운영 방침은 팀원들과 상의 하고, 코어 타임에 각자 부족한 것 배운 것에 대해서 나누기로 했다.

C언어 학습 목표

  • 구조체와 Malloc, 포인터에 대한 이해.
  • 메모리 할당할 시 초기 변수 초기화와 다 쓴 후, free()로 메모리 초기화하기
  • Python과 다른 내장 함수 구현.

수요코딩회 프로젝트 목표

  • 3주차에 걸쳐 최종 목표는 작동하는 mini-MySQL이라 하여, 확장성을 고려한 설계
  • 개발 단에 개입은 하되, 각자 할당량에 맡게 역할 분담.
  • 마지막 통합 때 정리하기

4월 7일 화요일 동석 코치 강의

4월 7일 동석 코치 강의

컴파일러가 컴파일은 해주는데, 에러는 코드 작성자에게 맡기는 부분이 있기에
segment fault가 뜬다.
-> 수시로 뜨고, 어디서 왜 떴는지 알려주지 않아서 난감했다.

파이썬만 하더라도 number int 1개가 28byte다.
C는 int type 기준 4byte라고 다들 알고 있겠지만,
사실상 C는 타입의 크기를 정해두지 않았다.
대신 64bit 운영 체제에서 얼추 4byte 된다 이런 것.
그래서 sizeof을 써야 제일 정확하다.

C에서 array는 사실상 syntax sugar이다. 파이썬의 List와 다르다. 그 점 인지.
strlen = python len
사실상 문자열은 마지막이 '\0'이 있는데
사실상 사이즈값이 sizeof 또는 strlen() + 1이 실제 크기.
"jungle" 이 있을 때 strlen이 6이 뜨는데 실제론 7이 사이즈다.

C는 3개의 속성이 있다고 기억하면 된다.

* 주소값 (&a)
* value
* size

값을 변경하고 싶을 때 포인터로 접근 해서 갱신해야 값이 바뀐다.

등등

2. 시도한 접근 방식

1. C언어 학습

4섹션 7, 7, 8, 5로 총 27문제였고, 처음부터 보니까 좀 생소하고 뭐하고, 라는 말이 바로 나올 법 했지만, pdf를 보면서 푸니까, 영어 + 중국어 힌트로 꽤나 풀만 했다. "CE1007/CZ1007 Data Structures" 이게 NTU - Singapore, 난양공과대학교 교육과정에서 쓰는 코드를 그대로 가져온거 같다. 난 학부 때 아래와 같이 처음부터 끝까지 다 짰었는데 약간 충격이긴 했다.

#include <stdio.h>
#include <stdlib.h>

int main(){
}

// 또는

int main(int argc, char *argv[]){
}

이번주는 Claude를 사용해서, Claude한테 받은 피드백은 다음과 같다.

강점

개념을 파이썬이랑 비교해서 이해하는 속도가 빠르고, 직관이 대체로 맞아요
"왜 이렇게 되는지"를 계속 물어보는 습관이 좋아요 — 그냥 코드 복붙하지 않고요
막히면 말로 먼저 설명하고 코드로 옮기는 방식이 효과적이에요

개선할 부분

포인터 타입 실수가 자주 나왔어요
("(" vs '(', int vs char 등) — C에서 타입에 더 주의가 필요해요
free 타이밍이나 malloc 초기화를 빠뜨리는 경우가 있었어요 —
새 자료구조 만들 때 체크리스트처럼 습관화하면 좋을 것 같아요
엣지 케이스(NULL 체크, 홀수/짝수 크기 등)를
나중에 발견하는 경우가 많았어요 —
코드 짜기 전에 먼저 생각하는 습관을 들이면 좋을 것 같아요

malloc은 오랜만이라, 그리고 기억도 잘 안나긴 했다.

2. 수요코딩회 (SQL Parser)

C 로 5종 SQL (CREATE / INSERT / SELECT / UPDATE / DELETE) 을 직접 토크나이즈 → 파싱 → 실행하는 미니 DBMS, SQL Parser를 만들었다.
나는 PM 겸 Parser 영역(parser.c, ast_print.c, json_out.c, sql_format.c, main.c)을 직접 구현했고,
석제님이 SELECT/CREATE storage 를, 원우님이 INSERT/DELETE/UPDATE storage 를 맡았다. 세인님은 원우님과 같이 경쟁 브랜치로 하다가, 마지막으로 CLI Design을 맡았다.
오전에 1차 완성을 끝낸 다음, 같은 날 오후에 같은 팀으로 후속 리팩토링까지 한 번 더 돌렸다.


3. 수요코딩회 (SQL Parser)

1. 프로젝트 소개

지난 주 React 프로젝트는 "내가 만든 추상화를 게임에 끼워 맞추다 어긋난" 경험이었다.
이번 주는 정반대로 갔다 — 목표를 작게 잡고, 인터페이스 계약을 먼저 못 박고, 거기에 맞춰 영역을 나누는 방식.

주제는 C 로 만드는 파일 기반 미니 DBMS.

  • CREATE / INSERT / SELECT / UPDATE / DELETE 5 종 SQL
  • 토크나이저 + 재귀 하강 파서 + AST + executor + 파일 백엔드
  • CLI 6 플래그 (--debug / --json / --tokens / --format / --help / --version)
  • Python stdlib HTTP 중계 서버 + CodeMirror 기반 브라우저 뷰어

3 주짜리 프로젝트의 1 주차였기 때문에, 확장 가능한 구조 가 더 중요했다.


2. 팀 구성과 협업 방식

  • 4 명 팀 (지용 / 석제 / 원우 / 세인)
  • 지용 (나) : Parser, AST/JSON/format, CLI, PM, CI, 머지 결정
  • 석제 : storage_select, storage_create, CSV 파서, WHERE/LIKE/ORDER BY
  • 원우 : storage_insert, storage_delete, storage_update + 단위 테스트 48
  • 후속 리팩토링은 3 명 (지용 + 석제 + 원우) 으로 dev2 브랜치에서 진행

협업에서 못 박은 것:

  • include/types.hstorage_* 시그니처는 절대 변경 금지 — 인터페이스 계약
  • 각자 자기 함수 본문만 채움 → 머지 충돌 최소화
  • executor.c 는 dispatcher 만 — 한 명이 망쳐도 나머지 작업 가능
  • 모든 PR 은 pull_request_template.md 양식 + GitHub Actions CI + PM 코드 리뷰 후 머지
  • main / dev / dev2 는 브랜치 보호 (직접 push 차단, admin 만 우회)

3. 기술적으로 배운 것

3-1. 인터페이스 계약을 먼저 못 박으면 병렬 작업이 가능하다

include/types.h 한 파일에 모든 자료구조 (ParsedSQL, WhereClause, ColumnType) 와 함수 시그니처를 다 박아두고, 그걸 PR 1 개로 먼저 머지 한 다음에 셋이 동시에 작업을 시작했다.

[D. 인터페이스 계약 PR — 지용]
              ↓ (먼저 머지)
   ┌──────────┼──────────────────────┐
   ↓          ↓                      ↓
[Parser]    [SELECT storage]      [DML storage]
 (지용)      (석제)                 (원우)

지난 주 React 때처럼 "각자 만들고 나중에 붙이자" 가 아니라,
붙일 모양을 먼저 정해놓고 그 안에서 각자 자유롭게 작업.
머지 충돌이 거의 0 이었다.

3-2. 1차 완성 → 사용/리뷰 → 후속 리팩토링 (저녁 이후)

오전에 끝낸 1차 완성을 직접 써보면서 약점이 드러났다.

발견원인
storage_select 가 결과를 stdout 에만 print다른 함수가 결과를 받아 쓸 수 없음 → JOIN/집계/subquery 모두 불가
WHERE 가 1~2 조건 + 단일 결합자만WHERE a=1 AND b=2 AND c=3 미지원
storage_delete/update 시그니처가 SELECT 와 다름API 비대칭 + N-ary 결합자 전달 불가
집계 함수가 COUNT(*) 한 종류만SUM/AVG/MIN/MAX 미지원
silent error 다수"존재하지 않는 테이블 SELECT" 등이 에러 메시지 없이 빈 결과

발표용 main 을 안정 상태로 보존한 채 새 통합 브랜치 dev2 를 따고, 같은 팀으로 한 번 더 작업했다.
1주차 호환 0 회귀 가 핵심 제약 — 후속 리팩토링 후에도 1차 단위 테스트 201 개가 모두 통과해야 했다.

3-3. N-ary WHERE — 이중 포인터로 푼 가장 임팩트 큰 변화

1주차 코드는 parse_where2칸짜리 배열을 미리 잡아놓고 for(i<2) 하드코딩 으로 돌고 있었다.
결합자도 char where_logic[8] 한 칸뿐.

/* 리팩토링 전 — include/types.h */
WhereClause *where;
int          where_count;    /* 0~2 */
char         where_logic[8]; /* "AND" 또는 "OR" — 단 하나만 */

/* 리팩토링 전 — src/parser.c */
static void parse_where(TokenList *t, ParsedSQL *sql) {
    sql->where = calloc(2, sizeof(WhereClause));
    for (int i = 0; i < 2; i++) {              /* 하드코딩 */
        const char *col = advance(t);
        const char *op  = advance(t);
        /* ... */
    }
}

/* 리팩토링 전 — DML 가드도 단일 조건만 통과 */
if (where_count != 1 || where == NULL) {
    return -1;
}

후속 리팩토링은 `char where_links` 이중 포인터** 로 풀었다.
N개 조건 사이에 N-1 개 결합자가 들어가는 가변 길이 배열.

/* 리팩토링 후 — include/types.h */
WhereClause *where;
int          where_count;
char       **where_links;   /* N-1 개 결합자 ("AND"/"OR") */

/* 리팩토링 후 — parse_where (개념 발췌) */
static void parse_where(TokenList *t, ParsedSQL *sql) {
    while (!at_stop(t)) {
        sql->where = realloc(sql->where,
                             sizeof(WhereClause) * (sql->where_count + 1));
        /* col / op / value 파싱 */
        sql->where_count++;

        const char *link = peek(t);
        if (eq_ci(link, "AND") || eq_ci(link, "OR")) {
            sql->where_links = realloc(sql->where_links,
                                       sizeof(char*) * sql->where_count);
            sql->where_links[sql->where_count - 1] = strdup(advance(t));
        } else break;
    }
}

이중 포인터가 무섭게 느껴졌는데, 막상 짜보니 "포인터를 가리키는 포인터" 가 결국 가변 길이 배열을 위한 도구 라는 게 손에 잡혔다.
realloc 으로 슬롯을 늘리고, 각 슬롯에 strdup 으로 문자열을 붙이고, free 할 때 거꾸로 푼다.
malloc/free 를 짝 맞춰 짜는 습관이 처음으로 자연스럽게 들었다.

3-4. 데이터 / 표시 분리 — RowSet 인프라

1차 완성의 storage_select 는 결과를 화면에 출력만 하는 "프린터" 였다.
후속 리팩토링에서 storage_select_result() 를 신설해서 결과를 메모리 RowSet 으로 반환 하게 만들었다.

typedef struct {
    int     row_count;
    int     col_count;
    char  **col_names;
    char ***rows;       /* ⭐ 또 다른 이중/삼중 포인터 */
} RowSet;

int  storage_select_result(const char *table, ParsedSQL *sql, RowSet **out);
void print_rowset(FILE *out, const RowSet *rs);
void rowset_free(RowSet *rs);

char ***rows — 행을 가리키는 포인터의 포인터의 포인터. 처음 봤을 때 멍했는데,
"행 배열 → 행 → 셀(문자열)" 로 한 단계씩 풀어서 그리니까 자연스러웠다.
이게 생기면서 향후 JOIN / GROUP BY / subquery 의 길이 다 열렸다.

기존 storage_selectstorage_select_resultprint_rowsetrowset_free 를 호출하는 얇은 wrapper 로 리팩토링.
외부 동작 변화 0 — 1주차 테스트 그대로 통과.

3-5. 시그니처 통일 — SELECT 와 동일 패턴

/* Before — 1주차 */
int storage_delete(const char *table, WhereClause *where, int where_count);
int storage_update(const char *table, SetClause *set, int set_count,
                   WhereClause *where, int where_count);

/* After — 후속 리팩토링 */
int storage_delete(const char *table, ParsedSQL *sql);
int storage_update(const char *table, ParsedSQL *sql);

호출부 (executor.c) 는 한 줄씩만 바뀌었다. API 가 일관되니 호출하는 쪽이 깔끔해지고, N-ary 결합자 같은 새 필드가 자연스럽게 흘러갔다.

3-6. valgrind 누수 0 — malloc/free 의 짝을 맞추는 훈련

1차 완성부터 후속 리팩토링까지 5 개 바이너리 모두 valgrind 누수 0 을 유지했다.

  • sqlparser (메인)
  • test_runner (parser/AST/JSON/format 단위 테스트)
  • test_storage_insert / test_storage_delete / test_storage_update

C 를 처음 제대로 만진 주에 valgrind 0 을 찍은 게 의외로 큰 자신감이 됐다.
"malloc 한 거 free 안 했구나" 를 valgrind 가 정확히 짚어주니까 디버깅이 무섭지 않았다.


4. CI / PR / 머지 워크플로

4-1. GitHub Actions

.github/workflows/build.yml 가 모든 push/PR 에 자동으로 돌았다.

- gcc/make/valgrind 설치
- make CFLAGS="-Werror"           # 경고도 빌드 실패 처리
- make test                        # 단위 테스트 (227)
- valgrind --leak-check=full ./test_runner
- valgrind --leak-check=full ./sqlparser query.sql

PR 페이지에 빨간/초록이 자동으로 떴고, CI 가 깨진 PR 은 머지 안 함 을 원칙으로 박았다.
후속 리팩토링 시작할 때 dev2 trigger 가 누락된 걸 발견해서 한 줄 fix 한 일도 있었다.

4-2. 머지 패턴 3 가지

이번 주에 3 가지 다른 머지 패턴을 직접 운영해봤다.

패턴적용 PR핵심
Winner-Takes-All1주차 INSERT (B vs C)두 PR 중 더 나은 쪽을 통째로 머지, 탈락 쪽엔 피드백
옵션 B Mixed MergePhase 1 PR #32 vs #33충돌 함수 단위로 비교 → 함수마다 베스트 코드 채택
Cherry-pick 부분 추출PR #31 (세인 CLI design)작업자 브랜치 보존, 가치 있는 부분(컬러+배너)만 골라 새 통합 커밋, Co-Authored-By 부착

세 패턴 공통 원칙:

  • 작업자의 시간/노력은 항상 존중 (탈락해도 피드백, 미머지여도 cherry-pick + 크레딧)
  • 머지 결정의 기준은 테스트 + 메모리 안전 + 인터페이스 일관성
  • "우선권을 가지는 코드" 를 PM 이 명시 (예: Phase 1 storage.c 보호)

특히 옵션 B Mixed Merge 는 처음 시도한 패턴이었다.
원우님 PR #32 와 석제님 PR #33 이 같은 parser 영역에서 5 군데 충돌했는데,
보통 같으면 한 명만 채택하고 한 명은 탈락이지만 — 함수 단위로 옆에 놓고 비교했다.

parse_where:        석제 (단일 while, 결합자 정규화, 깔끔)
parse_select stop:  석제 (신규 기능, 원우 없음)
ast_print:          석제 (inline 결합자 직관적)
json_out:           원우 (emit_str_array 헬퍼 재사용)
sql_format:         원우 (NULL 안 반환, robust)
test_parser.c:      두 사람 케이스 합집합

mixed merge commit 하나에 양쪽 Co-Authored-By 를 다 붙였다. 두 사람 작업 모두 살아남고, 결과 코드 품질도 함수마다 더 나은 쪽으로 올라갔다.

4-3. PR = 책임 소재 + 점수

PR 은 단순한 코드 묶음이 아니라 누가 / 어디까지 / 왜 를 동시에 기록한다.
이번 주 핵심 PR 3 개:

PR담당단계핵심
#18석제1차SELECT 영역 1차 완성 (storage_select / CSV 파서 / WHERE / LIKE / ORDER BY)
#32원우후속UPDATE/DELETE 시그니처 통일 + N-ary WHERE 평가
#33석제후속Parser stop set + N-ary parse_where

PR #18 이 1차 SELECT 의 책임 소재가 석제임을 영구히 기록한 덕분에,
후속 단계에서 같은 영역을 N-ary 로 확장할 때 누구한테 리뷰 청할지 자명했다.


5. 결과 — 1차 완성 vs 후속 리팩토링

지표1차 완성후속 리팩토링 후
단위 테스트201227 (+30 RowSet/N-ary/집계)
빌드 경고 (-Wall -Wextra -Wpedantic -Werror)00
valgrind 누수 (5 바이너리)00
storage 함수58 (+3 RowSet 신설)
WHERE 조건 수최대 2N개 (혼합 결합)
집계 함수1 (COUNT)5 (COUNT/SUM/AVG/MIN/MAX)
Pull Request 수2434
작업 기간약 7 시간+ 약 4 시간 (같은 날)

6. 팀 피드백

6-1. 브라우저 뷰어 피드백

"파싱 결과는 잘 나오는데 SELECT 결과가 표 형태로 보이면 좋겠다."

처음엔 카드 + JSON 토글로만 보여줬는데, 피드백 받고 진짜 HTML <table> 그리드로 렌더하게 고쳤다.
다크 테마 (Dracula) 와 기존 카드 UI 를 해치지 않는 선에서 조화롭게 — 컬럼 헤더 강조, 행 zebra striping.

6-2. PM 으로서의 자기 피드백

지난 주 React 때 "구현 먼저 → 설명 끼워 맞추기" 의 함정에 빠졌던 걸 의식하며,
이번 주는 의도적으로 "인터페이스 먼저 → 구현은 그 안에서" 순서로 갔다.
결과: 머지 충돌이 거의 0, 발표할 때 "여기까지가 의도, 여기까지가 구현" 의 경계가 명확했다.


7. 팀 회고 (6조)

Pros.

원우: GitHub Actions CI 자동 테스트 덕에 의견 수렴이 훨씬 수월했다 — 감 아닌 초록/빨강으로 판단할 수 있었다
석제: PR 리뷰 문화 + types.h 인터페이스 설계 덕에 memory alignment 이슈까지 팀 전체가 같이 잡을 수 있었다. 원우·석제 양쪽 다양한 풀이를 보는 것도 좋았다
지용: 멋진 팀원들 (feat. Zoom). 코어타임이 있어서 각자 어려웠던 점을 공유할 수 있었고, Anti-AI 작성 원칙 덕에 다양한 시각으로 자세한 코드 리뷰가 됐다
세인: 코어타임에 다양한 내용을 다룰 수 있었다

Cons.

세인: 코어타임에 팀원들에게 선뜻 얘기를 꺼내지 못했다
석제: 수요코딩회에서 low 레벨로 더 깊이 내려갔더라면 어땠을까 하는 아쉬움이 있다
원우: (석제와 동일)
지용: 세인이가 좀 더 자신감을 가지고 적극적으로 참여했으면 좋겠다

Feedback

석제: 세인님 기능 분담을 추가로 드리는 것 + low 레벨 구현까지 도전해보는 것 제안

Try.

다음 주 phase 2 (B+트리 / JOIN) 도 같은 패턴 — 인터페이스 먼저 박고 영역 분리
세인 팀원이 더 적극적으로 기여할 수 있도록 역할 분담 재조정
수요코딩회에서 low 레벨 구현까지 도전하는 방향 검토


8. 다시 프로젝트 회고

잘 된 것

지난 주 React 회고에서 "custom React(수단)로 잘 작동하는 것(목표)을 먼저 선택" 이라는 교훈을 적었었다.
이번 주는 그 교훈을 그대로 적용했다 — "수단(C 파일 백엔드)으로 잘 작동하는 목표(파일 기반 SQL 5 종)" 부터 잡고, 그 안에서 점진적으로 확장.

결과: 1주차 호환 0 회귀로 후속 리팩토링까지 같은 날에 돌렸다.
지난 주의 "구현 먼저 → 설명 끼워 맞추기" 함정을 한 주 만에 뒤집었다는 게 가장 의미 있었다.

새로 배운 것

이중 포인터 (char **)  ─── 가변 길이 배열을 위한 도구일 뿐 — 무서워할 게 없다
삼중 포인터 (char ***) ─── "행 배열 → 행 → 셀" 한 단계씩 풀면 자연스럽다
malloc/free 짝         ─── valgrind 가 무서웠는데, 짚어주니까 오히려 친구
인터페이스 계약        ─── 시그니처를 먼저 박으면 병렬 작업이 가능해진다
PR + CI                ─── PR 페이지가 코드 리뷰의 객관적 근거 + 책임 소재 영구 기록

그럼에도 아쉬운 것

  • 1차 완성 단계에서 silent error 를 처음부터 stderr 분리했어야 했다 — 후속에서 손이 더 간 부분
  • 후속 리팩토링을 같은 날에 끼워넣은 건 모험이었다 — 하지만 했죠?

9. 느낀 점

CI와 PR 리뷰를 도입해서 조금 더 현업 느낌이 날 수 있도록, 작업한 거 같다. 팀원들 모두가 만족스러워하는 부분이 좋았던거 같다.

팀 역할 분배 + PR + branch 전략 + CI 의 조합이 처음으로 자연스러웠다.

실패 → 회고 → 다음 주 적용 의 사이클이 한 번 돌았다는 게 가장 큰 수확.


GitHub 링크:
https://github.com/JYPark-Code/jungle_w6_mini_mysql_sql_parser


4. 차주 계획

C언어 주간

  1. malloc랩

수요코딩회

  • DB 개발 (Phase 2 — B+트리) 예상

목표

  1. 항상 과하지 않게 목표에 충실하게 수요 코딩회
  2. 열심히 공부하기 & 건강 챙기기

0개의 댓글