C언어 시스템 프로그래밍 단계별 실습 가이드 & 마스터 로드맵

NOTE

본 실습은 상위 폴더(../)에 정리된 C언어 기초 문법, 저수준 메모리 조작, 실무 C 관례(UB 방지), 멀티스레드 동시성 및 소켓/네트워크 프로그래밍 이론을 레고 블록처럼 단계별로 직접 구현해보는 핸즈온(Hands-on) 실습 프로젝트입니다.

각 단계(Stage)별로 독립된 소스코드를 작성하고 테스트할 수 있으며, 최종적으로는 모든 부품이 결합된 고성능 비동기 패킷/메시지 프로세싱 엔진으로 완성됩니다.


템플릿을 손에 붙이려면 → 템플릿 체화 드릴

이 가이드는 개념 노트를 동작시켜 보는 실습이다. 반면 템플릿 체화 드릴/ 은 요구사항 한 장 → 표 3장 → 코드 절차를 3회차씩 반복해 c코드 골격의 뼈대를 손에 붙이는 드릴이다. 개념이 흔들리면 이 문서, 손이 안 움직이면 드릴.

같은 방식으로 시큐어코딩가이드의 Bad→Good 치환을 손에 붙이는 드릴은 → 시큐어코딩 체화 드릴 — 뚫린 코드를 실제로 빌드해 취약점을 관찰한 뒤 고쳐 쓴다.

🗺️ 전체 실습 아키텍처

[Stage 1: 저수준 기반] ────► [Stage 2: 실무 관례 & 파서] ────► [Stage 3: 캡슐화 & 자료구조]
- 정수형 바이트/엔디안        - do-while(0) 안전 로거           - Opaque Pointer 링버퍼
- 인터넷 체크섬 알고리즘      - goto cleanup 단일 출구          - Struct Hack 가변 패킷
- 비트 패킹/언패킹            - strtok_r & URL 디코더           - 함수 포인터 디스패처
- 메모리 정렬/1바이트 패킹     - char** 2단계 동적 할당
                                                                       │
                                                                       ▼
[Stage 5: 동시성 & 네트워크 엔진] ◄────────────────────── [Stage 4: 영속성 & 시간 안정성]
- intptr_t 스레드 인자 전달                                - 바이너리 저널 I/O (fseek/락)
- C11 Atomic 무락 카운터                                    - 64비트 time_t 오버플로 방어
- Check-in/out 비동기 타임아웃 감시                        - Coverity 정적 분석 대응
- 세션 해시 샤딩 워커 큐
- 소켓 통신 & select/epoll 다중화

📊 단계별 실습 개요 요약표

단계실습 모듈핵심 연계 노트 (개념)최종 구현 목표
Stage 1저수준 바이트·비트 & 메모리 정렬uint16_t 바이트 저장, 엔디안/체크섬, Union, 비트플래그, 필드 패킹, 메모리 정렬바이너리 프로토콜 헤더 인코더/디코더
Stage 2실무 C 관례 & 안전한 문자열 처리do-while(0), 가변인자 로거, UB 방어/단일출구, snprintf/sscanf, URL 디코딩, char** 2중포인터안전한 로깅 프레임워크 & 프로토콜 텍스트 파서
Stage 3캡슐화 & 모듈형 자료구조 설계불투명 포인터(Opaque), 공개/코어 분리, Struct Hack, 디스패치 테이블, Ring Buffer불투명 원형 큐 & 가변 패킷 디스패처
Stage 4파일 영속성 & 64비트 시간 대응파일 락, 버퍼링, fseek, 64비트 time_t(2038년/Coverity)트랜잭션 저널 로거 & 오버플로 방어 타이머
Stage 5동시성 & 비동기 I/O 네트워크 엔진pthread+intptr_t, C11 Atomic, Check-in/out 타임아웃, 해시 샤딩 큐, select/epoll, 소켓락 경합 없는 이벤트 기반 에코/메시지 서버

🛠️ 실습 환경 및 빌드 가이드

디렉터리 구성

실습/
├── README.md                 # 본 실습 가이드 문서
├── Makefile                  # 전체 단계 통합 빌드/실행 스크립트
├── stage1_lowlevel/          # 1단계: 비트, 엔디안, 정렬, 헤더 패킹
│   ├── step1_endian_checksum.c
│   ├── step2_bit_packing.c
│   └── step3_alignment.c
├── stage2_robust/            # 2단계: 실무 C 관례, 안전한 문자열, 2차원 배열
│   ├── step1_logger.c
│   ├── step2_parser.c
│   └── step3_str_list.c
├── stage3_architecture/      # 3단계: Opaque Pointer, Struct Hack, Ring Buffer
│   ├── ring_buffer.h
│   ├── ring_buffer.c
│   ├── dispatcher.c
│   └── struct_hack.c
├── stage4_persistence/       # 4단계: 바이너리 I/O, 파일 락, 64비트 time_t
│   ├── journal_logger.c
│   └── safe_timer.c
└── stage5_concurrency/       # 5단계: pthread, atomic, 샤딩 큐, 소켓/select
    ├── worker_sharding.c
    ├── timeout_watcher.c
    └── event_server.c

권장 컴파일러 옵션 (Mac Clang 기준)

clang -Wall -Wextra -Werror -pedantic -std=c11 -g -fsanitize=address,undefined <source.c> -o <binary>
  • -Wall -Wextra -pedantic: 사소한 문법적 실수와 암시적 형변환 경고를 최대로 활성화
  • -fsanitize=address,undefined: 메모리 누수, Out-of-bounds, 댕글링 포인터(use-after-free), 정수 오버플로 등 UB를 런타임 즉시 검출

🚀 단계별 상세 구현 명세서

[Stage 1] 저수준 바이트·비트 조작 & 패킷 헤더 엔진

Step 1-1. 바이트 순서(Endianness) 변환 및 인터넷 체크섬 계산기

  • 파일 위치: stage1_lowlevel/step1_endian_checksum.c
  • 연계 학습 노트:
  • 구현 미션:
    1. union을 활용하여 시스템이 리틀 엔디안인지 빅 엔디안인지 판별하는 함수 is_little_endian() 작성.
    2. 시스템 라이브러리(htons, ntohs) 대신 비트 시프트(<<, >>)와 마스킹(&)만으로 동작하는 my_htons(), my_ntohs(), my_htonl(), my_ntohl() 구현.
    3. IP/ICMP 표준 16비트 One’s Complement 인터넷 체크섬 알고리즘(uint16_t calc_checksum(const void *buf, size_t len)) 구현.
  • 검증 시나리오:
    • 0x12345678 정수를 변환하여 바이트 배열 [0x12, 0x34, 0x56, 0x78]로 출력되는지 확인.
    • 계산된 체크섬을 포함한 패킷 전체를 다시 체크섬 계산 시 0x0000 (또는 0xFFFF)가 나오는지 무결성 검증.

Step 1-2. 비트 플래그 연산 및 비트 필드 패킹(Bit Packing)

  • 파일 위치: stage1_lowlevel/step2_bit_packing.c
  • 연계 학습 노트:
  • 구현 미션:
    1. 다음 비트 플래그 enum 정의:
      • FLAG_COMPRESSED = 1 << 0 (0x01)
      • FLAG_ENCRYPTED = 1 << 1 (0x02)
      • FLAG_RETRY = 1 << 2 (0x04)
      • FLAG_KEEP_ALIVE = 1 << 3 (0x08)
    2. 하나의 32비트 정수(uint32_t) 안에 복합 필드를 패킹/언패킹하는 함수 작성:
      • version (4비트, bits 28~31)
      • type (4비트, bits 24~27)
      • flags (8비트, bits 16~23)
      • seq_id (16비트, bits 0~15)
  • 검증 시나리오:
    • 임의의 필드 값을 채워 넣고 pack_header() 후 unpack_header()로 복원했을 때 원본 값과 100% 일치함을 assert()로 검증.

Step 1-3. 메모리 정렬(Alignment)과 1바이트 패킹 구조체

  • 파일 위치: stage1_lowlevel/step3_alignment.c
  • 연계 학습 노트:
  • 구현 미션:
    1. 동일한 멤버(uint8_t, uint32_t, uint16_t, char[5])를 갖는 두 구조체 정의:
      • struct DefaultHeader (컴파일러 기본 정렬)
      • struct PackedHeader (#pragma pack(push, 1) 적용)
    2. sizeof 및 offsetof() 매크로로 각 필드의 실제 오프셋과 패딩 바이트 크기를 계산하여 콘솔에 시각화 출력.
    3. POSIX posix_memalign() 또는 C11 aligned_alloc()을 안전하게 래핑한 safe_aligned_alloc(size_t alignment, size_t size) 작성.
  • 검증 시나리오:
    • 반환된 주소가 지정한 정렬 단위(예: 64바이트 캐시 라인)의 배수인지 확인 (((uintptr_t)ptr % 64) == 0).

[Stage 2] 실무 C 코딩 관례 & 안전한 메모리/문자열 조작

Step 2-1. 안전한 로깅 매크로 & 단일 출구(Single Exit) 에러 정리기


Step 2-2. 안전한 프로토콜 문자열 파서 & URL 퍼센트 디코더


Step 2-3. 동적 2차원 문자열 리스트 관리자 (char**)


[Stage 3] 캡슐화 & 모듈형 자료구조 설계

Step 3-1. 불투명 포인터(Opaque Pointer) & 공개/코어 분리


Step 3-2. 가변 길이 구조체(Struct Hack) 기반 가변 패킷 컨테이너

  • 파일 위치: stage3_architecture/struct_hack.c
  • 연계 학습 노트:
  • 구현 미션:
    1. C99 유연 배열 멤버(Flexible Array Member) 구조체 정의:
      typedef struct {
          uint32_t magic;
          uint16_t cmd_type;
          uint16_t payload_len;
          uint8_t  payload[]; /* Struct Hack: 헤더 뒤에 연속 할당 */
      } Packet;
    2. packet_create(uint16_t cmd, const void *data, uint16_t len) 함수 작성:
      • malloc(sizeof(Packet) + len) 단 한 번의 호출로 헤더와 페이로드를 단일 메모리 블록에 생성.
  • 검증 시나리오:
    • &packet->payload[0]의 메모리 주소가 (uint8_t*)packet + sizeof(Packet)와 완전히 일치함을 확인.

Step 3-3. 함수 포인터 디스패치 테이블 (Command Handler)

  • 파일 위치: stage3_architecture/dispatcher.c
  • 연계 학습 노트:
  • 구현 미션:
    1. 핸들러 함수 포인터 타입 정의: typedef int (*CommandHandler)(const Packet *pkt);
    2. 명령 코드와 함수 포인터를 매핑한 디스패치 테이블 구조체 배열 작성:
      typedef struct {
          uint16_t cmd;
          const char *name;
          CommandHandler handler;
      } CommandEntry;
    3. dispatch_packet(const Packet *pkt) 함수를 작성하여 명령 코드로 핸들러를 O(1) 인덱싱 또는 테이블 조회 후 호출.
  • 검증 시나리오:
    • 등록되지 않은 명령 코드가 입력되었을 때 안전하게 Fallback 핸들러(handle_unknown)가 호출되는지 확인.

[Stage 4] 파일 영속성 & 64비트 시간/정적 분석 대응

Step 4-1. 바이너리 트랜잭션 저널 로거 & 파일 락(File Locking)


Step 4-2. 2038년 문제 대응 및 정적 분석(Coverity) 호환 타이머

  • 파일 위치: stage4_persistence/safe_timer.c
  • 연계 학습 노트:
  • 구현 미션:
    1. 64비트 time_t 값을 32비트 정수 시스템에 전달하거나 연산할 때 발생하는 Y2038 오버플로를 방지하는 모듈러 연산(% INT_MAX) 구현.
    2. Coverity High 등급 결함(정수 절삭 경고)을 방지하는 안전한 경과 시간(Elapsed Delta) 계산 함수 작성:
      uint32_t safe_elapsed_seconds(time_t start, time_t now);
  • 검증 시나리오:
    • 2038년 1월 19일 이후의 미래 타임스탬프(time_t > 0x7FFFFFFF) 입력 시 음수 변환 버그 없이 안전하게 처리되는지 확인.

[Stage 5] 동시성 & 비동기 I/O 네트워크 엔진

Step 5-1. pthread 인자 전달(intptr_t) & C11 Atomic 무락 통계 카운터


Step 5-2. 비동기 요청 타임아웃 감시 (Check-in / Check-out 타이머)

  • 파일 위치: stage5_concurrency/timeout_watcher.c
  • 연계 학습 노트:
  • 구현 미션:
    1. 요청 슬롯 배열(TimerSlot slots[MAX_SLOTS]) 정의 (상태: EMPTY, PENDING, EXPIRED).
    2. check_in(uint32_t req_id, uint32_t timeout_ms): 요청 등록.
    3. check_out(uint32_t req_id): 정상 응답 도착 시 타이머 해제.
    4. 백그라운드 타이머 스레드가 50ms마다 슬롯을 순회하며 만료된 요청 발견 시 on_timeout() 콜백 트리거.
  • 검증 시나리오:
    • 300ms 타임아웃으로 등록하고 100ms 시점에 check_out한 요청은 만료되지 않고, 응답이 오지 않은 요청만 정확히 300ms 이후 콜백이 호출되는지 검증.

Step 5-3. 세션 해시 샤딩 워커 큐 (Lock Contention 제거)

  • 파일 위치: stage5_concurrency/worker_sharding.c
  • 연계 학습 노트:
  • 구현 미션:
    1. N개의 워커 스레드마다 독립된 전용 큐(Stage 3의 Ring Buffer) 할당.
    2. 수신된 작업의 session_id를 해싱(hash(session_id) % N)하여 해당 워커의 전용 큐에만 인큐(Enqueue).
    3. 스레드 간 락 경합 없이 동일 세션의 메시지는 동일 워커가 순차 처리함을 보장.
  • 검증 시나리오:
    • 단일 전역 큐 방식과 해시 샤딩 방식의 100만 건 처리 속도(Throughput) 비교 측정.

Step 5-4. 소켓 통신 & I/O 멀티플렉싱 이벤트 서버 (select / epoll)