🔟애플리케이션 프로그래밍과 시스템 프로그래밍

고수준 언어와 저수준 언어 프로그래밍 방식 비교

1. 애플리케이션 vs 시스템 프로그래밍: 동물 퀴즈로 알아보기

1) 웹 브라우저는 '고급 컴퓨터'다?

우리는 지난 장까지 브라우저를 마치 고차원의 가상 컴퓨터처럼 바라봤어요.

● 브라우저가 제공하는 고수준 인터페이스

  • DOM API

  • 이벤트 핸들러

  • Ajax / Fetch 등 통신 API

이러한 고수준 API는 운영체제와 I/O 장치의 복잡성을 감춰줍니다.

그럼 이번 장에서는 브라우저를 거치지 않고 같은 프로그램을 만들면 어떻게 다를지 직접 비교해 봅시다.

2) 애플리케이션 vs 시스템 수준의 프로그래밍 비교

● 그림 10-1의 구조 요약

[사용자 인터랙션 프로그램 구조 비교]

프로그램 A (고수준)
 └→ 웹 브라우저 (DOM, JS로 제어)
     └→ 운영체제
         └→ I/O 장치 (키보드, 화면 등)

프로그램 B (저수준)
 └→ 직접 OS API 호출 (예: C 언어)
     └→ 운영체제
         └→ I/O 장치

👉 웹 브라우저 기반은 사용자가 다루기 쉽고 빠르게 만들 수 있습니다. 👉 시스템 수준 프로그래밍(C 언어 등)은 하드웨어와 OS 동작을 더 정확히 이해하고 제어할 수 있습니다.

3) 예제 프로그램: '동물 맞히기 게임'

우리가 만든 프로그램은 사용자가 생각한 동물을 컴퓨터가 질문을 통해 추측하는 간단한 지식 기반 게임입니다.

● 예시 대화 흐름

컴퓨터: 동물을 생각해보세요.
컴퓨터: 동물이 짖습니까?
사용자: 예
컴퓨터: 개입니까?
사용자: 아니요
컴퓨터: 졌습니다. 무슨 동물입니까?
사용자: 케르베로스
컴퓨터: "머리가 3개입니까?" 라는 질문으로 구분하겠습니다.

● 핵심 기능

  1. 질문을 통해 트리 구조를 따라 내려감

  2. 잎(Leaf) 노드에 도달하면 동물을 추측

  3. 추측 실패 시 새로운 동물과 질문을 받아 트리 확장

  4. 다음 라운드부터는 더 똑똑해짐 (학습함)

4) 프로그램 구조: 순서도 이해하기

● 그림 10-2: 동물 퀴즈 트리 순서도 요약

그림을 보면 동물에 대한 지식으로 이뤄진 트리를 따라 내려가되, 질문을 던지면서 어느 경로로 내려갈지를 결정합니다.

  • 제대로 동물을 추측하면 기뻐한다.

  • 동물을 맞히지 못하면 사용자에게 정 답을 물어본 후 해당 동물을 마지막에 확인한 동물과 구분하는 데 필요한 질문을 요청해서, 그 정보를 트리에 추가하고 처음부터 다시 시작한다.

이 프로그램은 (순서도의 왼쪽에서) 지식 트리를 따라 내려가면서 질문을 던지고 답을 얻습니다.

오른쪽에서 경로의 끝에 도달하면 답을 맞혔는지 여부에 따라 잘난 체를 하거나 동물에 대한 지 식을 지식 기반knowledge base에 추가합니다.

이 순서도를 코드로 바꾸면 아래와 같은 흐름이 됩니다:

if (isLeaf(node)) {
  if (userAnswer === node.answer) {
    alert("맞췄습니다!");
  } else {
    learnNewAnimal();
  }
} else {
  if (userAnswer === "예") {
    node = node.yes;
  } else {
    node = node.no;
  }
}

5) 시스템 수준과 애플리케이션 수준의 차이

구분
애플리케이션 수준 (JS/웹)
시스템 수준 (C/콘솔)

사용자 인터페이스

HTML + CSS + DOM 조작

콘솔 출력, scanf/printf

데이터 저장

LocalStorage, 서버

파일 I/O, 메모리 직접 제어

입력 처리

prompt, click 이벤트

scanf, getchar()

확장성

빠르고 직관적

복잡하지만 강력함

웹 브라우저는 이미 많은 기능이 추상화되어 있지만, 시스템 프로그래밍은 메모리, 파일, 버퍼 등 세세한 부분까지 관리해야 합니다.

📌 부가설명: JSON → DB → 프론트까지 흐름도

앞서 요청하신 예시 흐름도도 아래와 같이 이어집니다.

● JSON 기반 데이터 흐름

graph LR
  A[프론트엔드: 입력폼] --> B[JSON 생성]
  B --> C[API로 서버 전송]
  C --> D[DB에 저장 (예: MongoDB)]
  D --> E[API로 클라이언트에 재전송]
  E --> F[프론트엔드 렌더링 (React, Vue)]

→ 이 흐름은 현대적인 SPA 프레임워크에서 CRUD 처리의 전형적인 구조입니다.

브라우저가 '컴퓨터 같은 프로그램'이라는 비유

고수준 프로그래머는 DOM API만 알면 되지만, 시스템 프로그래머는 '그 아래'까지 봐야 합니다.

2. 부가정리 : 동물 퀴즈 트리: JSON 구조 & C 구조체 변환

1) 동물 퀴즈 트리 구조란?

동물 맞히기 게임은 질문을 던지고 그에 따라 예/아니오로 가지를 뻗는 이진 트리 구조를 사용합니다.

예:

질문: 동물이 짖습니까?
 ├─ 예 → 질문: 머리가 3개입니까?
 │    ├─ 예 → 케르베로스
 │    └─ 아니요 → 개
 └─ 아니요 → 고양이

2) JSON 구조로 저장하기

{
  "question": "동물이 짖습니까?",
  "yes": {
    "question": "머리가 3개입니까?",
    "yes": { "animal": "케르베로스" },
    "no": { "animal": "개" }
  },
  "no": {
    "animal": "고양이"
  }
}

📌 JSON 저장 포인트

  • 질문 노드는 "question" 키를 가짐

  • 동물(잎 노드)은 "animal" 키만 있음

  • "yes", "no"로 자식 노드를 연결

3) C 구조체로 표현하기

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

typedef struct Node {
    int is_question;        // 1이면 질문, 0이면 동물
    char text[128];         // 질문 or 동물 이름
    struct Node* yes;       // 예 답변
    struct Node* no;        // 아니요 답변
} Node;

📌 사용법 예시

Node* create_animal(const char* name) {
    Node* node = malloc(sizeof(Node));
    node->is_question = 0;
    strcpy(node->text, name);
    node->yes = node->no = NULL;
    return node;
}

Node* create_question(const char* question, Node* yes_node, Node* no_node) {
    Node* node = malloc(sizeof(Node));
    node->is_question = 1;
    strcpy(node->text, question);
    node->yes = yes_node;
    node->no = no_node;
    return node;
}

5) JSON ↔ 구조체 변환 개념 요약

항목
JSON 저장
C 구조체 표현

노드 식별

"question" or "animal"

is_question (1/0)

질문 내용

text 필드에 저장

char text[128]

하위 노드

"yes", "no"

struct Node* yes, no

추가 확장성

구조적으로 유연함

메모리 직접 할당 필요

마무리 정리

JSON 구조는 웹과 서버에서 다루기 좋고, C 구조체는 시스템 수준에서 메모리를 제어할 수 있어 강력합니다.

3. 왜 우리는 브라우저를 떠나 C 코드로 가는가?

웹 브라우저는 마치 고급 리조트처럼 모든 것을 갖춘 고수준의 환경입니다. HTML, CSS, 자바스크립트만으로도 유저 인터페이스와 상호작용을 쉽게 구현할 수 있고, onclick, alert, appendChild() 같은 간단한 코드만으로 복잡한 동작이 가능합니다.

하지만…

이 "리조트"의 아래층에는 어떤 노동자들이 피땀 흘리며 시스템을 굴리고 있을까요?

우리는 이제 브라우저라는 가상 머신의 안락함을 잠시 떠나, C 언어로 구현한 터미널 기반의 동물 추측 게임을 통해, 이 고급 구조의 아래에서 실제로 어떤 일들이 벌어지고 있는지를 들여다보려 합니다.

1) 브라우저의 고수준 추상화 구조

[HTML/CSS/JS 코드]

[DOM 트리로 변환]

[이벤트 루프 & 자바스크립트 인터프리터]

[표현 계층 (렌더링 엔진)]

[OS I/O, GPU, 사운드 등 하부 인터페이스]
  • 브라우저는 우리가 직접 다루기 어려운 I/O, 장치 제어, 입력 처리 등 복잡한 시스템 수준의 요소들을 모두 감춰줍니다.

  • 이런 구조 덕분에 우리는 ‘동물이 짖습니까?’를 화면에 띄우고 버튼만 만들어도, 게임이 실행됩니다.

  • 하지만 이 편리함은 대가가 있습니다: 우리가 무엇을 통제하고 있는지, 어떻게 돌아가는지는 알 수 없습니다.

2) 그래서, C 버전으로 구현한다는 것은?

1. 직접 입력과 출력을 제어해야 합니다

  • 브라우저에선 alert() 하면 팝업이 뜨지만,

  • C에서는 printf()로 콘솔에 문자열을 출력하고, scanf() 또는 fgets()로 입력을 받아야 하죠.

  • 입력은 즉각 처리되지 않습니다. 사용자가 엔터를 눌러야 버퍼에 저장된 입력을 받을 수 있습니다.

→ 이것이 바로 Cooked Mode (표준 입력 모드) 입니다. Raw Mode로 바꾸면 엔터 없이 한 글자씩 읽는 것도 가능하지만, 복잡해집니다.

2. 시스템 콜로 OS에 요청해야 합니다

  • C에서 fgets() 같은 함수를 부르면 내부적으로는 read()라는 시스템 콜을 호출해 운영체제에게 "입력 줘!"라고 요청합니다.

  • 시스템 콜은 일반 함수 호출이 아닙니다. **문맥 전환(context switch)**이 일어나는 중요한 행위입니다.

📌 문맥 전환이란? 운영체제는 현재 프로그램이 요청을 마칠 때까지 기다리는 동안, 다른 프로그램이 실행될 수 있도록 CPU를 넘깁니다. 이를 위해 현재 프로그램의 상태(문맥)를 저장하고, 다른 프로그램의 상태로 레지스터, 스택 등을 복원합니다.

3. 드라이버가 키보드/디스플레이와 연결을 흉내 냅니다

  • 예전에는 컴퓨터와 키보드를 연결한 케이블이 있었죠. 지금은 이런 선이 소프트웨어적으로 에뮬레이션됩니다.

  • 터미널 입력을 처리하는 장치 드라이버는 입력을 입력 버퍼에 저장하고, 화면에는 에코(echo) 기능을 통해 사용자 입력을 보여줍니다.

  • 드라이버는 ENTER 키가 입력되어야 입력을 사용자 프로그램에게 전달합니다.

🔄 입출력 흐름: 전체 흐름도 설명

[사용자 키 입력]

[터미널 드라이버의 입력 버퍼에 저장]
    ↓ (ENTER 입력 시)
[운영체제가 사용자 프로그램 깨움]

[사용자 프로그램은 버퍼에서 문자열을 읽음]

[추론 로직 수행 → 다음 질문 출력]

[터미널 출력 버퍼 → 디스플레이]

이 흐름이 바로 표준 I/O (stdio) 라이브러리와 장치 드라이버의 협업 구조입니다.

🎯 왜 원형 버퍼가 필요할까?

일반 큐의 문제점

  • 일반 FIFO 큐는 데이터를 꺼낼 때마다 모든 요소를 한 칸씩 앞으로 당겨야 합니다.

  • 계산이 많고 비효율적이죠.

원형 버퍼(Circular Buffer)

  • 큐를 원 모양으로 생각합니다.

  • 앞에서 빼고, 뒤에서 넣고, 인덱스는 (현재 + 1) % 버퍼 길이로 계산.

  • 공간 재활용이 가능하고, 오버헤드가 적습니다.

g o
r
f
in → 버퍼 추가
out → 버퍼 읽기

이 구조는 장치 드라이버, 네트워크 버퍼, 키보드 입력 처리 등에서 아주 많이 사용됩니다.

📎 표준 입출력 라이브러리 (stdio)의 역할

역할
설명

입력 버퍼

한 번의 시스템 콜로 다량 입력을 받아 메모리에 저장

출력 버퍼

출력 요청이 있을 때까지 데이터 누적 → 등 신호에 맞춰 한 번에 출력

성능 향상

시스템 콜 호출 수를 줄여 문맥 전환 비용 절감

구조

내부적으로 링 버퍼 구조 활용 (FIFO지만 한쪽만 움직이는 큐 형태)


🧪 실제 동물 추측 프로그램 예시에서의 흐름 (C 기반)

  1. 프로그램 시작

  2. 사용자에게 질문 출력 (printf)

  3. scanf로 입력 대기 (ENTER 전까지는 슬립 상태)

  4. 대답에 따라 지식 트리 탐색

  5. 추측 실패 시, 새로운 동물/질문 입력 받아 지식 트리 확장

  6. 루프 반복

지식 트리는 C에서는 동적 구조체로 구현되며, 링크드 트리로 연결됩니다. 이는 자바스크립트에서 DOM 트리를 사용한 방식과 구조는 비슷하지만, 직접 메모리 할당/해제 (malloc/free)가 필요합니다.

브라우저에서 아주 쉽게 구현했던 "질문 → 예/아니요 → 다음 질문" 흐름은 C 기반으로 내려오면 한 단계 한 단계 모두 우리가 직접 짜야 할 구조가 됩니다.

  • HTML의 <div> 하나가 C에선 구조체 트리의 노드

  • onclick이벤트가 C에선 사용자 입력을 기다리는 시스템 콜

  • document.appendChild()가 C에선 malloc 후 포인터를 연결하는 트리 확장

📌 정리하자면…

비교 항목
브라우저 환경
C 터미널 환경

UI 구성

HTML 태그

printf 문자열 출력

트리 구조

DOM 트리

구조체 기반 링크드 트리

입력 처리

버튼 + 이벤트 핸들러

scanf + 시스템 콜 (read)

메모리 관리

자동

수동 (malloc/free)

버퍼링

자동, 거의 신경 안 씀

드라이버 버퍼 + stdio 버퍼 직접 고려

시스템 콜

추상화됨

직접 호출해야 함 (read, write)

성능

문맥 전환 신경 안 써도 됨

문맥 전환 고려 필요 (슬립, 스케줄링 등)

4. 동물 추측 게임: HTML/CSS/JS vs C 코드 비교 분석

1) 프로그램 구조 비교

항목
브라우저 기반 (HTML+JS)
시스템 기반 (C 터미널)

실행 환경

브라우저 (웹페이지)

터미널 (CLI)

사용자 인터페이스

HTML <button>, <input>

콘솔 출력 printf, 입력 scanf

지식 트리 구조

HTML <div string="질문/동물"> 트리

구조체 기반 이진 트리 (노드 구조체)

트리 저장 방식

DOM 노드에 숨겨서 저장

메모리 내 구조체 + 포인터

이벤트 처리

onclick, $(selector).click()

if/else, scanf() 입력 분기

상태 전환

DOM 탐색 및 자식 노드 이동

구조체 포인터 이동 (node = node->yes)

초기화

restart() 함수

루트 포인터 초기화

반복 구조

사용자 클릭 이벤트 중심

무한 루프 (while(1))

메모리 관리

자동 (DOM 트리 조작)

수동 (malloc / free)

실행 흐름

비동기적 인터랙션 (이벤트 루프)

동기적 흐름 (입력 → 처리 → 출력)

2) 지식 트리 구현 코드 비교

🧱 HTML/JS (브라우저)

<div id="root" class="invisible">
  <div string="동물이 짖습니까?">
    <div string="개"></div>
    <div string="고양이"></div>
  </div>
</div>
function question(new_node, html) {
  $('#dialog').append(html);
  if ($(new_node).length == 0) return true;
  node = new_node;
  $('#dialog').append($(node).attr('string') + '?');
  return false;
}
  • 데이터 구조: 실제 구조는 HTML DOM, 데이터는 <div> 요소의 string 속성

  • 노드 탐색: $(node).children(':first-child') 식으로 DOM 탐색

  • 확장 방식: wrap()prepend()로 새로운 질문을 삽입

⚙️ C 코드 (터미널)

typedef struct node {
  char *string;
  struct node *yes;
  struct node *no;
} Node;

Node *newNode(char *str) {
  Node *n = malloc(sizeof(Node));
  n->string = strdup(str);
  n->yes = n->no = NULL;
  return n;
}
void ask(Node *node) {
  if (node->yes == NULL && node->no == NULL) {
    printf("%s입니까? (y/n) ", node->string);
    char response;
    scanf(" %c", &response);
    if (response == 'y') printf("맞췄습니다!\n");
    else {
      // 새로운 질문/동물 추가...
    }
  } else {
    printf("%s (y/n) ", node->string);
    char response;
    scanf(" %c", &response);
    if (response == 'y') ask(node->yes);
    else ask(node->no);
  }
}
  • 데이터 구조: 구조체 기반 이진 트리, malloc으로 생성

  • 노드 탐색: 단순한 포인터 이동 (node->yes, node->no)

  • 확장 방식: strdup()으로 문자열 복사 후 새 노드 생성

3) 인터페이스 및 I/O 방식 비교

기능
HTML+JS
C (터미널)

입력 방식

<button>, <input> 클릭 → 이벤트 발생

scanf, getchar 등으로 동기 입력

출력 방식

<div>에 문자열 append

printf()로 터미널에 출력

인터랙션

비동기 이벤트 기반 (click)

순차적 흐름, 입력 없으면 대기

문맥 전환

브라우저가 처리, 사용자는 인식 못함

read() → 시스템 콜 → 문맥 전환 발생

시스템 콜 사용

브라우저 내부에서 추상화됨

read, write, malloc, free 등 명시적 호출

4) 메모리 구조 및 관리 차이

항목
브라우저 버전
C 버전

메모리 할당

브라우저가 DOM으로 관리

직접 malloc, free 호출

트리 저장

문서 구조로 자동 저장

힙에 노드 구조체로 저장

가비지 컬렉션

브라우저 GC 자동 처리

명시적 메모리 해제 필요

메모리 오류 위험

낮음

메모리 누수, dangling pointer 위험

5) 핵심 개념 비교 요약표

구분
브라우저 환경
C 환경

프로그래밍 모델

선언적 + 이벤트 기반

명령형 + 절차적

데이터 표현

DOM 엘리먼트 속성

구조체 포인터

유저 이벤트

클릭, 키 입력 → 이벤트 핸들러

문자 입력 → 조건 분기

렌더링

브라우저 엔진 자동 처리

텍스트 출력만

버퍼링

브라우저 내부 버퍼 자동

stdio, 드라이버, OS 레벨 버퍼

디버깅 방식

콘솔(log), 브라우저 툴

gdb, printf 디버깅

운영체제 추상화

거의 완전 추상화

운영체제 호출 직접 사용

코드 복잡도

짧고 간단

길고 메모리 관리 필요

✅ 어떤 상황에 어떤 방식이 좋을까?

목적
브라우저 방식이 유리
C 방식이 유리

사용자와 인터랙티브 UI

✅ 예

❌ 아니요

시스템 동작 원리 학습

❌ 한계 있음

✅ 아주 유리

빠른 프로토타입 제작

✅ 빠름

❌ 느림

메모리 최적화 / 고성능

❌ 브라우저 한계 있음

✅ 가능

장치 제어, 드라이버 구현

❌ 불가능

✅ 시스템 직접 접근 가능

유지보수

✅ DOM 트리 활용

❌ 포인터/메모리 관리 어려움

📌 결론

  • 브라우저 버전빠르게 UI를 구성하고 사용자와 상호작용하기에 최적화되어 있으며, 메모리나 I/O 처리를 브라우저가 도와주므로 학습이 쉬움

  • C 버전입출력, 버퍼링, 시스템 콜, 문맥 전환 등 시스템 프로그래밍의 핵심 개념을 모두 체험할 수 있으며, 내부 동작을 정확히 이해하는 데 적합

물론입니다! 아래는 **"추상화를 활용한 코드 개선"**이라는 주제를 바탕으로, 가독성 좋고 스토리텔링 구조로 정리한 콘텐츠입니다.


🧠 추상화를 활용한 코드 개선 – C와 자바스크립트의 접근 방식 차이

1) 상황 설정: 왜 매번 ‘고양이 vs 개’부터 시작할까?

동물 추측 게임을 해본 사람이라면 알겠지만, 프로그램을 새로 시작할 때마다 지식 트리가 초기화되며, "개와 고양이" 수준에서 다시 시작합니다.

"어제는 케르베로스까지 학습했는데..." 오늘 실행하니 또 개와 고양이만 아는 게임이라면 실망스럽겠죠.

이런 문제를 해결하려면 이전에 학습된 지식을 저장하고, 다음 실행 시 다시 불러오는 기능, 즉 상태의 지속성(persistence)이 필요합니다.

2) C 언어는 어떻게 이걸 해결했나?

C 언어는 이를 꽤 쉽게 해결할 수 있습니다. 그 이유는 운영체제 수준의 추상화가 C 언어에 잘 녹아 있기 때문입니다.

✅ 파일 시스템의 추상화 활용

운영체제는 파일과 장치를 ‘동일한 인터페이스’로 다루는 기능을 제공합니다.

즉, 파일에서 읽는 코드 == 키보드 입력을 받는 코드 => 둘 다 fgets(), fputs() 등으로 동일하게 구현 가능

이 덕분에 동물 추측 게임을 C로 만들면,

  • 학습 내용을 파일에 저장(-o 옵션)하거나

  • 저장된 내용을 다시 불러와서 이어하기(-i 옵션)가 매우 자연스럽게 구현됩니다.

🎯 예시 흐름

gta -o training.txt
  • 사용자가 새 동물과 질문을 입력하면 해당 내용이 training.txt에 저장됨

  • 다음 실행 시 gta -i training.txt로 이전 상태를 불러올 수 있음

이게 가능한 이유는? C 프로그램이 파일 시스템의 입력/출력 추상화를 직접 활용할 수 있기 때문입니다.

3) 브라우저(자바스크립트)는 왜 어려운가?

반면 브라우저 환경은 C처럼 직접 시스템 자원을 제어할 수 없습니다. 왜냐하면 브라우저는 사용자를 보호하기 위해 보안 모델과 추상화 레이어를 더 높게 설정해두기 때문이죠.

❌ 추상화가 ‘숨겨져 있는’ 구조

아래 그림처럼, 브라우저에서는 파일 시스템과 직접 연결되는 코드가 없습니다.

사용자 코드      →       브라우저 API      →     운영체제 I/O

       X                   O                   O

❗ 불편한 점

  • 파일 시스템에 접근하려면 File API, IndexedDB, LocalStorage 등 완전히 별개의 API를 써야 함

  • 이 API들은 일관된 입출력 인터페이스가 아닌 완전히 다른 설계철학을 가짐

  • 따라서 게임의 상태 저장 기능을 추가하려면 완전히 다른 방식의 코드를 새로 작성해야 함

4) 요약 비교

항목
C 프로그램
자바스크립트 (브라우저)

시스템 접근

파일/장치 모두 동일한 인터페이스

제한적 접근만 허용

상태 저장 방식

fopen, fgets, fputs

LocalStorage, IndexedDB 등 별도 API 필요

학습 지속성 구현 난이도

낮음 (파일만 다루면 됨)

높음 (추가 기술 학습 필요)

장점

단순한 코드로 다양한 입력/출력 구현 가능

사용자 보안에 강함, 크로스 플랫폼 환경

단점

시스템 자원 접근 제한 없음 → 보안 위험

동일 기능 구현 시 API 다양하고 복잡함

5) 🌐 프로그래밍 언어 설계에 대한 교훈

프로그래밍 언어와 플랫폼은 어떤 추상화를 어떻게 드러낼 것인가에 따라 생산성과 편의성이 달라집니다.

  • C는 "시스템에 가깝게 일하자"는 철학 아래, 입출력과 파일 시스템을 통합된 인터페이스로 제공합니다.

  • 브라우저는 보안과 유저 편의를 중시하며, 다양한 추상화를 개발자에게서 숨기고, 대신 안전한 고수준 API를 제공합니다.

이로 인해 동일한 기능이라도 완전히 다른 방식으로 구현되며, 추상화 수준에 따라 코드의 유연성과 재사용성도 달라집니다.

📦 런타임 라이브러리와 표준 입출력: 시스템 프로그래밍의 시작점

1) 프로그램이 실행되기까지 – 우리가 보지 못한 준비 작업들

C로 작성한 프로그램이 ./a.out이나 gta 같은 이름으로 실행되는 순간, 단순히 메인 함수부터 실행되는 것 같지만, 실제로는 그 이전에 아주 중요한 준비 작업이 일어납니다.

이걸 담당하는 게 바로 런타임 라이브러리(runtime library) 입니다.

🛠️ 런타임 라이브러리가 하는 일

  • 스택과 힙 메모리 공간 초기화

  • 표준 입출력 시스템을 위한 장치 파일 오픈

  • stdin, stdout, stderr 같은 기본 입출력 핸들 준비

이런 준비가 없으면 printf, scanf, fgets 같은 함수는 사용할 수 없습니다. C에서는 이 준비가 crt0 또는 crt1.o라는 스타트업 코드(startup object)에서 자동으로 수행됩니다.

2) stdio: C에서 가장 널리 쓰이는 라이브러리

📍 stdio의 핵심 개념: 버퍼링된 입출력

표준 입출력 라이브러리는 다음과 같은 세 가지 주요 포인터를 제공합니다:

이름
설명
디스크립터

stdin

표준 입력

0

stdout

표준 출력

1

stderr

표준 오류 출력

2

🤔 stdoutstderr의 차이는?

  • 둘 다 콘솔로 출력

  • 하지만 버퍼링 방식이 다름:

    • stdout: 라인 버퍼 → 줄 바꿈 전까지 저장되며, 프로그램이 중단되면 유실될 수도 있음

    • stderr: 비버퍼링 → 즉시 출력됨

따라서 오류 메시지는 항상 stderr로 출력하는 게 안전합니다.

📌 재밌는 역사적 배경

초기의 사진 조판 장치(C/A/T)는 조판된 문서를 출력하기까지 비용이 많이 들었기 때문에, 단순한 오류 메시지(예: “파일을 열 수 없습니다”)를 조판 프린터로 출력하지 않고 터미널로 따로 보내기 위해 stderr가 생겼습니다.

3) 시스템 프로그래밍의 어두운 그림자 – 버퍼 오버플로

💣 gets()의 위험한 함정

초기 C 라이브러리에는 gets(char *buffer)라는 아주 간단한 입력 함수가 있었습니다. 하지만 이 함수는 치명적인 단점을 안고 있었습니다.

char buffer[2];
gets(buffer);  // 👈 문제 발생
  • gets()는 입력받는 문자열의 길이를 제한하지 않음

  • 사용자 입력이 buffer의 크기를 초과하면? → 메모리 침범

🚨 실제 위협 사례

이런 취약점은 수많은 보안 사고로 이어졌고, C99 이후로는 gets()사용금지(deprecated) 되었으며, C11에서는 완전히 삭제되었습니다.

✅ 안전한 대안: fgets(buffer, sizeof(buffer), stdin)

버퍼 오버플로가 발생할 시 버퍼에 다 담지 못한 값들은 버퍼 이후의 공간에 들어차게 됩니다. 문제는 들어차는 방식이 밀어내기가 아닌 덮어쓰기라는 것입니다. 가령 8칸짜리 메모리가 있고, 그 안에 4칸짜리 버퍼가 있을 때

이와 같이 사용자가 버퍼를 초과하는 값을 입력하면 버퍼 이후의 값이 바뀌게 된다. 문제는 이걸 프로그램은 전혀 모르고 있는 상태라는 것이다. 심지어 버퍼 이후의 값이 바뀌어도 프로그램은 이를 전혀 사용자에게 통지하지 않는다!

⇒ 물론 이는 로우 레벨까지 프로그래머세세하게 코딩해야 했던 때의 이야기고 요즘은 디버깅시 컴파일러가 버퍼 앞뒤에 오버플로 방어용 1~4바이트짜리 값을 넣어(카나리라고 한다. 어원은 카나리아(조류) 문서 참조) 침범되었을 경우 예외를 때려버리거나 코딩 단계에서 '이 함수는 버퍼 오버플로의 위험이 있음' 이라고 경고를 해 준다. 물론 이 현상이 사라진 건 아니므로 프로그래머는 여전히 버퍼에 들어가는 값을 체크 해야 된다. 중요하지 않은 10줄~50줄짜리 소규모 예제 프로그램이라면 이는 딱히 신경쓰지 않아도 되지만 중형이나 대규모 프로젝트라면 이야기가 달라지는데 이 원리를 통해 해킹이 가능하기 때문이다. 간단한 예를 들자면 10자리 비밀번호를 치고 들어가는 시스템이 있다고 하자. 그리고 비밀번호를 저장하는 값 바로 뒤에 로그인 여부를 묻는 값이 있다고 하자.(0=실패, 이외의 값=성공) 이 상황에서 끝자리가 0이 아닌 11자리 비밀번호를 입력하면 틀리더라도 뚫을 수 있게 된다! 만약 버퍼를 초과하여 쓰이는 값이 프로그램의 RETN[4]값을 덮어쓰게 된다면 사용자가 프로그램의 진행 상황을 통제할 수 있게 된다. 만약 특정한 함수가 있고, 이 함수의 주솟값을 알고 어떤 입력이 버퍼 오버플로 공격이 가능하다면 그냥 '입력받는 문자열 + 4[5]/8[6]바이트(SFP 덮어쓰기용) + 해당 함수 주소'를 입력값에 쑤셔넣어 입력 함수가 끝나면 자신이 원하는 함수로 점프할 수 있게 된다. 가장 유명한 사례로는 하트블리드 사태와 스타크래프트 1의 EUD가 있다. - 나무 위키 -

4) 메모리와 포인터를 사용하는 C 구조체 – 노드 구조 선언

자바스크립트에서는 DOM <div> 구조를 활용해 트리를 만들었지만, C에서는 좀 더 직접적인 방식으로 메모리와 포인터를 활용합니다.

struct node {
  struct node *no;
  struct node *yes;
  char string[1];
};
  • no, yes왼쪽/오른쪽 자식 노드

  • string동물 이름이나 질문

💡 메모리 할당 방식

C에서는 문자열을 담기 위해 노드 구조체 크기 + 문자열 길이만큼 malloc을 사용해 직접 메모리를 계산하여 할당합니다.

malloc(sizeof(struct node) + strlen(string))

이런 저수준 메모리 제어는 위험하지만 강력하며, 시스템 프로그래밍에서 아주 중요한 능력입니다.

🧾 정리: 런타임, 입출력, 보안까지 이어지는 시스템 세계

항목
설명

런타임 라이브러리

실행 전에 메모리 구조와 파일 핸들을 초기화

stdio

입력/출력과 버퍼링을 다루는 표준 I/O 라이브러리

stdout vs stderr

출력 방식의 차이로 오류 메시지 관리

gets()fgets()

버퍼 오버플로와 그로 인한 보안 위협

구조체 + 포인터

자바스크립트보다 더 직접적인 메모리 조작 방식


🧠 주제: 고수준과 저수준 프로그램의 경계에서 – 자바스크립트 vs C로 구현한 ‘동물 추측 게임’ 비교

1) 프로그램 구조와 실행 환경 비교

구분
HTML/JavaScript
C 언어

실행 환경

브라우저 (V8 엔진 포함)

OS에서 직접 실행 (터미널 기반)

입출력 방식

브라우저 제공 Web API, DOM, 이벤트 핸들러

stdin, stdout, stderr 및 파일 입출력 함수

UI 처리

버튼, 텍스트 입력, HTML DOM으로 구현

텍스트 기반 y/n/q 입력 처리

지식 트리 구조

<div>를 트리 구조로 활용

struct node 기반 동적 메모리 구조

게임 상태 저장

브라우저 내부에서는 어려움

파일 입출력 (옵션으로 -i, -o)

프로그램 흐름

jQuery 및 JS 이벤트 기반 처리

루프, 조건문, 포인터 중심의 구조적 처리

2) 입출력 추상화의 차이

  • 브라우저(JavaScript):

    • 파일 접근 제한 (보안상 이유로 local file 접근 어려움)

    • Web API로 입출력 추상화

    • 브라우저가 많은 세부 사항을 감춤 (파일, 터미널 존재 X)

  • C 언어:

    • 파일/장치 구분 없음 → 동일한 인터페이스 제공

    • fopen, fgets, fputs 등의 표준 입출력 함수로 데이터 처리

    • stderr는 버퍼링을 하지 않기 때문에 중요한 메시지 출력에 사용

브라우저에서 새 기능을 추가하려면 “완전히 다른 API”를 써야 하지만, C에서는 장치와 파일이 같은 인터페이스를 공유하기 때문에 재사용이 용이합니다.

3) 메모리와 자료구조 구현 비교

항목
JavaScript
C 언어

노드 구조

<div string="질문">...</div>

struct node with string[]

메모리 할당

브라우저 내부 메커니즘 사용

malloc으로 수동 할당

문자열 처리

JS 내장 문자열 함수

strcpy, strlen 등 직접 처리

포인터 사용

없음 (참조 기반)

yes/no → 포인터 직접 연결

자바스크립트에서는 <div> DOM 요소가 곧 트리 노드이자 데이터를 품은 객체입니다. C에서는 이를 명시적으로 구조체와 포인터로 구현해야 하며, 메모리 할당도 직접 해줘야 합니다.

4) 버퍼링과 표준 입출력 구조

stdin / stdout / stderr 구조

  • stdin: 사용자의 입력을 받음

  • stdout: 일반 메시지 출력

  • stderr: 오류 메시지 출력 (버퍼링 없음)

char buffer[2];
gets(buffer); // 위험: 버퍼 오버플로 가능

gets는 더 이상 사용되지 않으며, 버퍼 크기를 지정할 수 있는 fgets()로 대체됩니다. 버퍼 오버플로 취약점은 보안 측면에서 큰 이슈입니다.

🎯 버퍼 오버플로 예시

char buffer[2];
gets(buffer);  // yyy 입력 → launch_missiles 변수에 영향 가능

→ 대응법:

fgets(buffer, sizeof(buffer), stdin);

5) 훈련 기능 구현 차이

  • JavaScript:

    • 브라우저에 훈련 데이터 저장 어려움

    • 로컬스토리지 또는 백엔드 서버 연동 필요

  • C 프로그램:

    • -i, -o 옵션으로 파일 입출력 기능 구현

    • fopen, fputs, fgets 등으로 데이터 저장

훈련 데이터를 파일에 저장한 뒤 다음 게임에서 이어서 불러오는 기능은 CLI에서 훨씬 자연스럽게 구현됩니다.

✨ 마무리: 왜 두 코드는 같은 게임인데 이렇게 다를까?

  • 브라우저 기반 앱은 사용자 인터페이스(UI)를 자동으로 제공하지만, 시스템 자원 접근에는 제한이 많습니다.

  • 반면 C로 구현된 CLI 버전은 모든 것을 직접 구현해야 하지만, 파일과 장치를 같은 방식으로 다룰 수 있어 확장성이 좋습니다.

  • 추상화의 차이는 곧 개발 방식과 성능, 유지보수에 큰 영향을 미칩니다.

Last updated