1. C란?
C는 1972년 Bell 연구소의 Dennis Ritchie가 만든 절차적 시스템 프로그래밍 언어입니다. 50년이 넘은 지금도 운영체제 커널과 고성능 소프트웨어의 핵심 언어로 사용됩니다.
2. 컴파일러 설치 및 개발 환경 구축
C 언어 코드를 컴파일하고 실행하기 위해서는 운영체제에 맞는 컴파일러(GCC, Clang, MSVC 등)와 코드를 작성할 텍스트 에디터(VS Code 등)가 필요합니다. 각 운영체제별로 가장 널리 사용되는 환경을 구축해보겠습니다.
2.1 운영체제별 컴파일러 설치
🪟 Windows (MSYS2 & MinGW-w64 설치)
Windows 환경에서는 GNU 컴파일러 모음인 GCC의 Windows 이식 버전인 MinGW-w64를 설치하는 것이 가장 범용적이고 표준적입니다.
- MSYS2 다운로드: MSYS2 공식 웹사이트에 접속하여 인스톨러(
.exe)를 다운로드하고 기본 설정으로 설치합니다. (기본 설치 경로:C:\msys64) - 패키지 매니저로 GCC 설치: 설치 완료 후 실행되는 터미널(MSYS2 UCRT64) 창에서 아래 명령어를 입력하여 컴파일러 툴체인을 설치합니다.
# GCC 컴파일러 및 관련 개발 도구 모음(Toolchain) 설치 pacman -S mingw-w64-ucrt-x86_64-toolchainbash(설치 시 선택 항목을 물어보면
Enter를 눌러 전체 설치를 진행합니다.) - 환경 변수(Path) 등록: Windows 시스템이 어디서든
gcc명령어를 인식할 수 있도록 시스템 경로에 등록해야 합니다.- 키보드의
Win + S를 누르고 "시스템 환경 변수 편집"을 검색하여 실행합니다. - 우측 하단의 [환경 변수] 버튼을 클릭합니다.
- '시스템 변수' 목록에서
Path변수를 찾아 선택하고 [편집]을 누릅니다. - [새로 만들기]를 클릭한 뒤 컴파일러 실행 파일이 위치한 경로인
C:\msys64\ucrt64\bin를 입력하고 확인을 누릅니다.
- 키보드의
- 설치 확인: 새로운 명령 프롬프트(cmd) 또는 PowerShell을 열고 아래 명령어를 입력하여 설치가 잘 되었는지 확인합니다.
gcc --versionbash
🍎 macOS (컴파일러 설치 및 세부 설정)
macOS에서는 시스템 기본 컴파일러인 Apple Clang(Xcode Command Line Tools)을 설치하거나, 필요에 따라 Homebrew를 통해 표준 GNU GCC를 직접 설치할 수 있습니다.
방법 A: Xcode Command Line Tools 설치 (가장 추천 & 초간단)
macOS가 공식 제공하는 컴파일 툴체인(Clang 기반)을 설치하는 표준 방법입니다.
- 터미널 앱(Terminal.app)을 켭니다. (Cmd + Space 누른 후 '터미널' 검색)
- 아래 명령어를 입력하고 실행합니다.
xcode-select --installbash - 화면에 "명령줄 개발 도구 설치가 필요합니다"라는 안내 팝업창이 나타나면 [설치] 버튼을 누르고 사용권 계약에 동의합니다.
- 설치가 자동으로 진행되며 완료되면 아래 명령어로 설치 상태를 최종 확인합니다.
clang --versionbash
방법 B: Homebrew를 통한 순수 GNU GCC 설치 (고급 사용자용)
macOS에서 기본 gcc 명령어는 사실 Apple Clang에 링크되어 있습니다. 만약 표준 C 라이브러리와 완전한 GNU GCC 컴파일러를 그대로 사용하고 싶다면 패키지 관리자인 Homebrew를 이용해야 합니다.
- 터미널 앱에서 Homebrew 설치 스크립트를 실행합니다. (Homebrew가 이미 설치되어 있다면 생략 가능)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"bash - 아래 명령어를 입력하여 순수 GNU GCC 최신 버전을 설치합니다.
brew install gccbash - 설치가 끝나면
gcc-13또는gcc-14(설치된 버전에 맞춰 명령어 생성) 명령어를 터미널에 쳐서 버전 번호가 올바르게 나오는지 확인합니다.gcc-14 --versionbash
🐧 Linux (Ubuntu / Debian 설치)
Linux 환경은 패키지 매니저를 통해 컴파일에 필요한 필수 도구 세트(build-essential)를 아주 쉽게 설치할 수 있습니다.
sudo apt update
sudo apt install build-essentialbash
(설치가 끝나면 gcc --version과 make --version으로 컴파일러 작동 여부를 확인합니다.)
2.2 VS Code에서 C 개발 환경 및 실행 활용하기
가장 강력하고 가벼운 텍스트 에디터인 Visual Studio Code (VS Code)에서 C 코드를 작성하고 빌드/디버깅하는 방법입니다.
- VS Code 다운로드 및 설치: VS Code 공식 홈페이지에서 운영체제에 맞는 버전을 설치합니다.
- 필수 확장(Extension) 설치: VS Code 실행 후 왼쪽 사이드바의 확장 아이콘(
Ctrl + Shift + X)을 클릭하고 다음 확장을 설치합니다.- C/C++ (Microsoft): 코드 자동 완성, 디버깅, 구문 강조 등 필수 기능 제공
- Code Runner (Jun Han): 단축키 하나로 C 코드를 즉시 컴파일하고 실행할 수 있는 편리한 도구
- 프로젝트 폴더 열기: 원하는 빈 폴더를 생성한 후 VS Code에서
File > Open Folder로 해당 폴더를 엽니다. - C 소스코드 작성: 새 파일 만들기 아이콘을 클릭하고
hello.c파일을 생성한 뒤 아래의 기본 코드를 입력합니다.#includecint main(void) { printf("Visual Studio Code에서 C 실행 완료!\n"); return 0; } - 코드 빌드 및 실행하기 (2가지 방법)
- 방법 A (Code Runner 이용 - 가장 간편함):
코드 편집 창 우측 상단의 ▶ (Run Code) 버튼을 클릭하거나, 단축키
Ctrl + Alt + N(macOS는Ctrl + Option + N)을 입력하면 하단 출력 창에 즉시 결과가 나타납니다.입력값(scanf)을 처리하고 싶다면: VS Code 설정(Ctrl + ,)에서code-runner.runInTerminal을 검색한 뒤 "Run In Terminal" 옵션을 체크해주세요. 기본 출력 창 대신 실제 터미널을 사용하여 키보드 입력을 처리할 수 있게 됩니다. - 방법 B (통합 터미널 직접 빌드 - 표준 방법):
VS Code 하단에 터미널(
Ctrl + `)을 열고 직접 컴파일러 명령어를 입력하여 빌드 및 실행합니다.# 1. gcc 컴파일러로 hello.c를 빌드하여 hello.exe(Linux/macOS는 hello) 파일 생성 gcc hello.c -o hello # 2. 실행 파일 실행하기 # Windows: .\hello.exe # macOS & Linux: ./hellobash
- 방법 A (Code Runner 이용 - 가장 간편함):
3. Hello World
#include <stdio.h>
int main(void) {
printf("Hello, World!\n");
return 0;
}
/* 컴파일 & 실행
gcc -Wall -Wextra -o hello hello.c
./hello
*/c
4. 변수와 자료형 (Variables & Data Types)
C는 정적 타입 언어로 변수 선언 시 반드시 자료형을 명시해야 합니다.
#include <stdio.h>
#include <stdint.h> // 고정 크기 정수 타입
#include <stdbool.h> // bool 타입 (C99)
int main(void) {
/* 정수형 */
char c = 'A'; // 1 byte (-128 ~ 127)
short s = 1000; // 2 bytes
int n = 42; // 4 bytes
long l = 100000L; // 4 or 8 bytes
long long ll = 9e18; // 8 bytes
/* 고정 크기 정수 (이식성 보장) */
int8_t i8 = 127;
int32_t i32 = 2147483647;
uint64_t u64 = 18446744073709551615ULL;
/* 실수형 */
float f = 3.14f; // 4 bytes, ~7자리 정밀도
double d = 3.14159265; // 8 bytes, ~15자리 정밀도
/* bool (C99) */
bool flag = true;
/* 상수 */
const int MAX = 100;
/* 형변환 */
int a = 7, b = 2;
double result = (double)a / b; // 3.5 (정수 나눗셈 방지)
printf("char='%c' int=%d double=%.2f bool=%d\n", c, n, result, flag);
return 0;
}c
sizeof(int)로 확인하거나 <stdint.h>의 고정 크기 타입(int32_t, uint64_t 등)을 사용하면 이식성을 보장합니다.
5. 연산자와 제어문 (Operators & Control Flow)
#include <stdio.h>
int main(void) {
/* 산술 연산자 */
int a = 10, b = 3;
printf("%d %d %d %d %d\n", a+b, a-b, a*b, a/b, a%b); // 13 7 30 3 1
/* 비트 연산자 */
unsigned int flags = 0b1010;
flags |= 0b0001; // 비트 SET → 1011
flags &= ~0b0010; // 비트 CLEAR → 1001
flags ^= 0b0100; // 비트 FLIP → 1101
/* if-else */
int score = 85;
if (score >= 90) printf("A\n");
else if (score >= 80) printf("B\n");
else printf("C\n");
/* switch */
int day = 3;
switch (day) {
case 1: printf("월요일\n"); break;
case 2: printf("화요일\n"); break;
case 3: printf("수요일\n"); break;
default: printf("기타\n");
}
/* 반복문 */
for (int i = 0; i < 5; i++) printf("%d ", i); // 0 1 2 3 4
int j = 0;
while (j < 3) { printf("w%d ", j); j++; }
/* 삼항 연산자 */
int max = (a > b) ? a : b; // 10
return 0;
}c
6. 함수 (Functions)
C에서 함수는 코드를 재사용 가능한 단위로 분리하는 핵심 도구입니다. 선언(prototype)과 정의(definition)를 분리할 수 있습니다.
#include <stdio.h>
/* 함수 선언 (prototype) — 헤더 파일에 위치 */
int add(int a, int b);
double power(double base, int exp);
void swap(int *a, int *b); // 포인터로 출력 파라미터 전달
/* 함수 정의 */
int add(int a, int b) { return a + b; }
double power(double base, int exp) {
double result = 1.0;
for (int i = 0; i < exp; i++) result *= base;
return result;
}
/* 값이 아닌 주소로 전달 (call-by-reference 시뮬레이션) */
void swap(int *a, int *b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
/* 재귀 함수 */
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
/* 가변 인자 */
#include <stdarg.h>
int sum(int count, ...) {
va_list ap;
va_start(ap, count);
int total = 0;
for (int i = 0; i < count; i++) total += va_arg(ap, int);
va_end(ap);
return total;
}
int main(void) {
printf("add: %d\n", add(3, 4)); // 7
printf("pow: %.0f\n", power(2.0, 10)); // 1024
printf("5!: %d\n", factorial(5)); // 120
printf("sum: %d\n", sum(4, 1, 2, 3, 4)); // 10
int x = 10, y = 20;
swap(&x, &y);
printf("swap: %d %d\n", x, y); // 20 10
return 0;
}c
7. 배열과 문자열 (Arrays & Strings)
#include <stdio.h>
#include <string.h>
int main(void) {
/* 1차원 배열 */
int nums[5] = {10, 20, 30, 40, 50};
for (int i = 0; i < 5; i++) printf("%d ", nums[i]);
/* 2차원 배열 */
int matrix[3][3] = {{1,2,3},{4,5,6},{7,8,9}};
printf("\n%d\n", matrix[1][2]); // 6
/* 문자열 = char 배열 + NULL 종단('\0') */
char name[20] = "TestForge";
char greeting[50];
printf("길이: %zu\n", strlen(name)); // 9
strcpy(greeting, "안녕하세요, ");
strcat(greeting, name);
printf("%s\n", greeting);
/* 문자열 비교 */
if (strcmp("abc", "abc") == 0) printf("같음\n");
/* 문자열 탐색 */
char *pos = strstr(name, "Forge"); // "Forge" 시작 포인터
if (pos) printf("위치: %ld\n", pos - name); // 4
/* snprintf: 안전한 문자열 포맷 */
char buf[64];
snprintf(buf, sizeof(buf), "score=%d%%", 95);
printf("%s\n", buf); // score=95%
return 0;
}c
gets()는 버퍼 크기를 검사하지 않아 보안 취약점의 원인이 됩니다. fgets(buf, sizeof(buf), stdin)을 사용하세요. 마찬가지로 strcpy 대신 strncpy, sprintf 대신 snprintf를 사용합니다.
8. 포인터 (Pointers)
포인터는 변수의 메모리 주소를 저장하는 변수입니다. C 언어의 핵심 기능으로, 동적 메모리 할당·배열·함수 포인터 모두 포인터 기반입니다.
#include <stdio.h>
int main(void) {
int x = 42;
int *ptr = &x; // &: 주소 연산자
printf("값: %d\n", *ptr); // 역참조: 42
printf("주소: %p\n", (void*)ptr);
/* 포인터로 값 수정 */
*ptr = 100;
printf("x = %d\n", x); // 100
/* 포인터 산술 */
int arr[5] = {1,2,3,4,5};
int *p = arr; // 배열 이름 = 첫 요소 주소
printf("%d %d\n", *p, *(p+2)); // 1 3
for (int *q = arr; q < arr + 5; q++)
printf("%d ", *q); // 1 2 3 4 5
/* 이중 포인터 */
int val = 7;
int *p1 = &val;
int **p2 = &p1; // 포인터의 포인터
printf("이중: %d\n", **p2); // 7
/* const 포인터 */
const int *cp = &x; // 가리키는 값 변경 불가, 포인터 자체는 가능
int * const pc = &x; // 포인터 자체 변경 불가, 가리키는 값은 가능
return 0;
}c
함수 포인터
/* 함수 포인터: 콜백 패턴 */
#include <stdlib.h>
int compare_asc(const void *a, const void *b) {
return (*(int*)a - *(int*)b);
}
int compare_desc(const void *a, const void *b) {
return (*(int*)b - *(int*)a);
}
typedef int (*Comparator)(const void*, const void*);
void sort_array(int *arr, size_t n, Comparator cmp) {
qsort(arr, n, sizeof(int), cmp);
}
int main(void) {
int nums[] = {5, 2, 8, 1, 9, 3};
sort_array(nums, 6, compare_asc);
for (int i = 0; i < 6; i++) printf("%d ", nums[i]); // 1 2 3 5 8 9
return 0;
}c
9. 구조체 (struct)
구조체는 서로 다른 자료형의 변수를 하나로 묶는 사용자 정의 타입입니다.
#include <stdio.h>
#include <string.h>
/* 구조체 정의 */
typedef struct {
int id;
char name[50];
float score;
} Student;
/* 구조체 배열과 포인터 */
void print_student(const Student *s) {
printf("[%d] %s: %.1f\n", s->id, s->name, s->score);
}
Student create_student(int id, const char *name, float score) {
Student s;
s.id = id;
strncpy(s.name, name, sizeof(s.name) - 1);
s.score = score;
return s;
}
int main(void) {
Student a = create_student(1, "김철수", 92.5f);
Student b = {2, "이영희", 88.0f};
Student class[2] = {a, b};
for (int i = 0; i < 2; i++)
print_student(&class[i]);
/* 비트 필드 — 메모리 절약 */
typedef struct {
unsigned int is_admin : 1; // 1비트
unsigned int level : 4; // 4비트 (0~15)
unsigned int status : 3; // 3비트 (0~7)
} UserFlags;
UserFlags uf = {1, 5, 2};
printf("admin=%u level=%u\n", uf.is_admin, uf.level);
return 0;
}c
연결 리스트 (Linked List)
#include <stdlib.h>
typedef struct Node {
int data;
struct Node *next;
} Node;
Node *create_node(int data) {
Node *n = malloc(sizeof(Node));
if (!n) return NULL;
n->data = data;
n->next = NULL;
return n;
}
void push_front(Node **head, int data) {
Node *n = create_node(data);
n->next = *head;
*head = n;
}
void free_list(Node *head) {
while (head) {
Node *tmp = head;
head = head->next;
free(tmp);
}
}
int main(void) {
Node *list = NULL;
push_front(&list, 3);
push_front(&list, 2);
push_front(&list, 1);
for (Node *p = list; p; p = p->next)
printf("%d ", p->data); // 1 2 3
free_list(list);
return 0;
}c
10. 동적 메모리 (Dynamic Memory)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
/* malloc: 미초기화 메모리 할당 */
int n = 10;
int *arr = malloc(n * sizeof(int));
if (!arr) { perror("malloc"); return 1; }
for (int i = 0; i < n; i++) arr[i] = i * i;
/* realloc: 크기 변경 */
int *bigger = realloc(arr, 20 * sizeof(int));
if (!bigger) { free(arr); return 1; }
arr = bigger;
for (int i = n; i < 20; i++) arr[i] = i;
/* calloc: 0으로 초기화된 메모리 */
double *matrix = calloc(4 * 4, sizeof(double));
if (!matrix) { free(arr); return 1; }
matrix[0 * 4 + 0] = 1.0; // [0][0]
matrix[1 * 4 + 1] = 1.0; // [1][1] — 단위 행렬
free(arr);
free(matrix);
return 0;
}c
• 메모리 누수(leak): malloc 후 free 누락
• 이중 해제(double-free): free 두 번 호출 → UB(Undefined Behavior)
• 댕글링 포인터: free 후 포인터를 NULL로 초기화하지 않고 사용
→
valgrind ./program 또는 -fsanitize=address 컴파일 옵션으로 감지하세요.
11. 파일 입출력 (File I/O)
#include <stdio.h>
#include <stdlib.h>
int main(void) {
/* 쓰기 */
FILE *fp = fopen("data.txt", "w");
if (!fp) { perror("fopen"); return 1; }
fprintf(fp, "이름,점수\n철수,92\n영희,88\n");
fclose(fp);
/* 읽기 — 한 줄씩 */
fp = fopen("data.txt", "r");
if (!fp) { perror("fopen"); return 1; }
char line[256];
while (fgets(line, sizeof(line), fp))
printf("> %s", line);
fclose(fp);
/* 이진 파일 읽기/쓰기 */
typedef struct { int id; double score; } Record;
Record rec = {1, 95.5};
FILE *bin = fopen("data.bin", "wb");
fwrite(&rec, sizeof(rec), 1, bin);
fclose(bin);
Record loaded = {0};
bin = fopen("data.bin", "rb");
fread(&loaded, sizeof(loaded), 1, bin);
fclose(bin);
printf("id=%d score=%.1f\n", loaded.id, loaded.score);
return 0;
}c
12. 전처리기 (Preprocessor)
/* ── 매크로 ──────────────────────────────── */
#define PI 3.14159265
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define ARRAY_LEN(a) (sizeof(a) / sizeof((a)[0]))
/* 사용 */
double area = PI * 5 * 5; // 78.539...
int bigger = MAX(10, 20); // 20
int len = ARRAY_LEN((int[]){1,2,3,4}); // 4
/* ── 조건부 컴파일 ────────────────────────── */
#ifdef DEBUG
#define LOG(fmt, ...) fprintf(stderr, "[DEBUG] " fmt "\n", ##__VA_ARGS__)
#else
#define LOG(fmt, ...) /* 릴리즈에선 아무것도 하지 않음 */
#endif
/* ── 헤더 가드 (include guard) ─────────────── */
/* mylib.h */
#ifndef MYLIB_H
#define MYLIB_H
typedef struct { int x, y; } Point;
Point point_add(Point a, Point b);
#endif /* MYLIB_H */
/* ── 컴파일 시점 검사 ──────────────────────── */
#include <assert.h>
_Static_assert(sizeof(int) == 4, "int must be 4 bytes");
/* 런타임 assert */
int divide(int a, int b) {
assert(b != 0 && "division by zero");
return a / b;
}c
13. 다음 단계 (Next Steps)
핵심 심화 주제:
• 멀티스레딩: POSIX Threads (
pthread) — 뮤텍스, 조건 변수, 경쟁 조건• 소켓 프로그래밍: BSD 소켓 API로 TCP/UDP 서버 구현
• 시스템 콜:
fork/exec/wait, 파이프, 시그널• 빌드 시스템: Makefile → CMake → Meson
추천 연계 학습:
• Linux 서버 관리 가이드
• Go 언어 — C 다음으로 배우기 좋은 시스템 언어
• Docker — C 기반 컨테이너 런타임 이해