C 공식 문서: 개념, 예제, 실습
예제 중심의 간결한 C 레퍼런스. 개념을 읽고 코드를 본 뒤 Coddy 여정에서 직접 연습하세요.
시작하기
- C 언어란C는 1972년에 태어난 작고 빠른 컴파일 언어로, 지금도 운영체제와 데이터베이스, 그리고 거의 모든 임베디드 장치를 돌리고 있습니다. C가 무엇인지, 어디서 돌아가는지, 2026년에도 배울 가치가 있는 이유를 정리했습니다.
- C 설치 (GCC)어떤 운영체제에서든 동작하는 C 컴파일러 설치하기: 윈도우의 MinGW-w64, macOS의 애플 clang, 리눅스의 gcc - 그리고 설치가 제대로 되었는지 확인하고 첫 파일을 컴파일하는 방법.
- 컴파일과 실행.c 파일을 실행 가능한 프로그램으로 바꾸는 법: gcc 명령, 전처리·컴파일·링크가 실제로 하는 일, 첫날부터 써야 할 옵션들, 그리고 실패했을 때 오류를 읽는 법.
- C 문법모든 C 프로그램은 같은 뼈대를 가집니다. include, main 함수, 세미콜론으로 끝나는 문장, 그리고 중괄호로 묶인 블록입니다. 각 부분이 하는 일과 컴파일러가 실제로 강제하는 규칙을 살펴봅니다.
- 주석C에는 한 줄짜리 //와 여러 줄짜리 /* */ 두 가지 주석 스타일이 있고, 서로 다른 내력과 하나의 중첩 함정을 갖고 있습니다. 둘을 쓰는 방법과 함께, 무엇을 주석으로 남길 가치가 있고 무엇은 그렇지 않은지 살펴봅니다.
변수와 타입
- 변수C에서 변수를 선언하는 방법, 선언과 초기화의 차이, 이름 규칙, 한 줄에 여러 개 선언하기, 그리고 초기화하지 않은 변수가 왜 초보자가 가장 많이 만드는 버그인지.
- 자료형C의 모든 변수에는 크기와 담을 수 있는 값을 정하는 타입이 있습니다. 기본 타입 전체와 실제 크기, signed와 unsigned의 차이, sizeof, limits.h, 그리고 오버플로가 일어날 때 벌어지는 일을 정리했습니다.
- 상수C에서 절대 바뀌지 않는 값에 이름을 붙이는 세 가지 방법 - const 키워드, #define 매크로, enum 상수 - 각각이 실제로 무엇이고 언제 무엇을 골라야 하는지.
- 연산자알아둘 가치가 있는 모든 C 연산자 - 산술, 대입, 비교, 논리, 증감 - 과 정수 나눗셈의 함정, 음수에서 나머지 연산이 하는 일, 그리고 믿고 쓸 수 있는 우선순위 표.
- 불리언C에는 C99 전까지 불리언 타입이 없었습니다. 0은 거짓, 그 외 모든 값은 참이고, 비교 연산은 int를 내놓습니다. 그 위에 stdbool.h가 bool, true, false를 얹어 줍니다. 이 설계가 만들어 낸 = 대 == 버그까지 함께 봅니다.
- 형변환C는 끊임없이 타입을 변환합니다. 캐스트로 직접 요청한 경우도 있지만, 대부분은 알아서 일어납니다. 승격 규칙, 명시적 캐스트 문법, 그리고 조용히 데이터를 잃는 변환들을 정리했습니다.
제어 흐름
- if elseC가 판단하는 방법: if 문, else, else if 연쇄, 중첩, 그리고 삼항 연산자 - 여기에 참·거짓 규칙(0은 거짓, 나머지는 모두 참)과 조건을 조용히 망가뜨리는 `=` 대 `==` 버그까지.
- switchC의 switch 문이 상수 목록에서 분기 하나를 고르는 방법 - case 레이블, break가 중요한 이유, 의도한 폴스루와 실수로 생긴 폴스루, default, char와 enum으로 분기하기, 그리고 switch가 else-if 사슬보다 나은 순간.
- for 반복문C의 for 반복문으로 코드를 반복하는 방법 - 세 부분으로 된 헤더, 올려 세기와 내려 세기, sizeof 요령으로 배열 순회하기, 중첩 반복문, 무한 반복문, 그리고 누구나 한 번쯤 걸리는 off-by-one 및 부호 없는 타입 버그.
- while 반복문C의 while 반복문이 조건이 바뀔 때까지 반복하는 방법 - 조건 먼저 규칙, 센티널 반복문, EOF까지 입력 읽기, while(1), 그리고 for와 while 사이의 변환.
- do-while 반복문본문을 먼저 실행하는 C의 반복문: do-while이 검사 전에 한 번의 실행을 보장하는 방식, 왜 세미콜론으로 끝나는지, 이를 위해 만들어진 메뉴와 재시도 패턴, 그리고 평범한 while 반복문과의 차이.
- break와 continuebreak는 반복문이나 switch를 빠져나가고, continue는 다음 반복으로 건너뜁니다. for, while, do-while에서 각각 어떻게 동작하는지, 플래그나 goto로 중첩 반복문을 탈출하는 방법, 그리고 반복문을 따라가기 어렵게 만드는 오용 사례를 다룹니다.
함수
- 함수C에서 함수를 정의하고 호출하는 방법 - 반환 타입, 매개변수, 반환값, void 함수, 분해(decomposition)가 중요한 이유, 그리고 작은 이름 있는 조각들로 만든 완성된 예제.
- 함수 매개변수C는 모든 것을 값으로 전달합니다 - 함수는 원본이 아니라 사본을 받습니다. 그것이 실제로 무엇을 뜻하는지, 포인터로 참조 전달을 흉내 내는 법, 배열이 왜 다르게 동작하는지, 그리고 결과를 여러 개 돌려주는 법.
- 함수 프로토타입C가 호출 전에 함수의 모양을 봐야 하는 이유 - 프로토타입 작성하기, implicit declaration 오류 고치기, 헤더 파일에 프로토타입 두기, 그리고 f(void)와 f()의 진짜 차이.
- 재귀C 함수가 자기 자신을 부르는 방법 - 그것을 멈추는 기저 사례, 팩토리얼과 피보나치 따라가기, 순진한 피보나치가 재앙적으로 느린 이유, 스택 오버플로가 진짜 무엇인지, 그리고 반복문이 더 나은 답일 때.
- 스코프C 변수가 어디서 보이고 얼마나 오래 사는지 - 블록 스코프, 함수 매개변수, 파일 스코프 전역 변수, 호출 사이에 살아남는 static 지역 변수, static 함수, 섀도잉, 그리고 전역 변수가 문제를 일으키는 이유.
포인터
- 포인터포인터는 메모리 주소를 담는 변수입니다. 이 페이지는 & 연산자와 * 연산자, 포인터 선언과 역참조, 포인터 타입이 중요한 이유, 그리고 포인터가 존재하는 이유를 보여주는 swap() 함수까지 바닥부터 차근차근 쌓아 올립니다.
- 포인터 산술포인터에 1을 더하는 것은 1바이트를 더하는 것이 아니라 그 타입의 다음 객체로 이동하는 것입니다. ptr+1, 증감, 포인터 차이, 비교, 포인터로 배열 훑기, 그리고 끝 다음 한 칸 규칙을 다룹니다.
- 포인터와 배열C에서 배열 이름은 쓰이는 거의 모든 자리에서 첫 원소를 가리키는 포인터로 바뀝니다. 이 페이지는 그 붕괴, arr[i]가 글자 그대로 *(arr+i)인 이유, 배열 크기가 함수에 따로 전달되어야 하는 이유, 그리고 배열 포인터와 포인터 배열의 차이를 설명합니다.
- 함수 포인터함수에도 주소가 있고, 함수 포인터는 그 주소를 담습니다. 이 페이지는 선언 문법을 해독하고, typedef로 읽기 좋게 만드는 법을 보여 주며, 콜백과 사용자 정의 비교 함수를 쓰는 qsort, 디스패치 테이블까지 쌓아 올립니다.
- NULL 포인터NULL은 포인터가 아무것도 가리키지 않을 때 담는 주소입니다. NULL이 진짜 무엇인지, 역참조하면 왜 죽는지, 0이나 초기화되지 않은 포인터와 어떻게 다른지, 그리고 널 버그를 코드에서 몰아내는 방어 패턴을 다룹니다.
배열과 문자열
- 배열C에서 배열을 선언하고 초기화하는 방법, 0부터 시작하는 인덱싱, sizeof로 길이 구하기, 요소 순회, 그리고 끝을 넘어 읽는 것이 왜 오류 메시지가 아니라 미정의 동작인지 알아봅니다.
- 다차원 배열C에서 2차원 배열을 선언하고 초기화하고 반복하는 법, 행 우선 배치가 메모리에서 실제로 무엇을 뜻하는지, 2차원 배열을 함수에 넘길 때 열 개수가 필요한 이유, 그리고 행렬 예제.
- 문자열C에는 문자열 타입이 없습니다. 문자열은 '\0' 바이트로 끝나는 char 배열입니다. 이 페이지는 그 모델을 세운 뒤 리터럴, 출력, 순회, 그리고 =로 문자열을 복사할 수 없는 이유를 다룹니다.
- 문자열 함수<string.h>의 실전 안내: strlen으로 길이 재기, strcpy와 strncpy로 복사하기, strcat으로 잇기, strcmp로 비교하기, strchr과 strstr로 찾기 - 그리고 어떤 것도 넘치지 않도록 버퍼 크기를 정하는 법.
- 문자열 변환C에서 텍스트와 숫자 사이를 오가는 법: atoi가 오류를 알릴 수 없는 이유, endptr과 errno로 strtol과 strtod를 제대로 쓰는 법, snprintf로 숫자를 문자열로 만들기, 그리고 '0'을 이용한 한 자리 요령.
동적 메모리
- 동적 메모리힙이 존재하는 이유, malloc이 실행 시점에 크기가 정해지는 메모리를 건네주는 방식, p = malloc(n * sizeof *p) 관용구, NULL 검사, 정확히 한 번만 해제하기, 그리고 처음부터 끝까지 따라가는 동적 배열 예제.
- calloc과 realloccalloc은 0으로 채운 메모리를 주고 크기 계산을 안전하게 해 줍니다. realloc은 이미 채워 둔 블록을 키워 줍니다. 이 페이지에서는 둘 다, 그리고 realloc 실패가 메모리 누수로 이어지지 않게 막는 임시 포인터 관용구와, calloc이 malloc + memset보다 나은 경우를 다룹니다.
- 메모리 누수누수가 정말 무엇인지, C 프로그램이 그것을 만드는 세 가지 방식, 그것을 막는 소유권 규율, 그리고 나머지를 valgrind와 -fsanitize=address로 찾는 법 - 새는 프로그램을 단계별로 고치며 마무리합니다.
- 스택과 힙데이터가 실제로 사는 곳: 스택의 자동 저장 공간, 힙의 동적 저장 공간, 그리고 세 번째 영역인 정적 저장 공간 - 지역 변수를 반환해 생기는 고전적인 댕글링 포인터 버그와 선택 기준까지.
구조체, 공용체, 열거형
- 구조체C 구조체로 관련된 값들을 하나의 타입으로 묶는 법 - 선언하기, 점 연산자로 멤버 접근하기, 지정 초기화자, 구조체 배열, 중첩, 그리고 모두를 놀라게 하는 전달 시 복사 규칙.
- 구조체와 포인터C에서 구조체를 가리키는 법 - 화살표 연산자, (*p).x에 괄호가 필요한 이유, 변경하거나 복사를 피하려고 포인터로 구조체 넘기기, malloc으로 구조체 할당하기, 그리고 연결 리스트 노드 만들기.
- typedeftypedef가 기존 C 타입에 새 이름을 주는 방법 - 태그가 있는 그리고 없는 typedef struct 관용구, 함수 포인터와 배열의 typedef, 그리고 typedef가 해가 되는 한 가지 경우인 포인터 감추기.
- 공용체C의 공용체가 여러 타입을 같은 바이트에 저장하는 방법 - 크기가 가장 큰 멤버인 이유, 마지막으로 쓴 멤버를 읽어야 하는 이유, 그리고 태그 달린 공용체(enum + union)가 그것을 안전하게 만드는 방법.
- 열거형(enum)C에서 enum을 정의하는 방법 - 자동 번호 매기기, 명시적 값, switch에서 enum을 써서 컴파일러가 빠진 case를 잡게 하기, enum을 문자열로 변환하기, 그리고 읽기 좋게 유지하는 명명 규칙.
입력과 출력
- printfC에서 printf가 실제로 동작하는 방식 - 형식 문자열 모델, 매일 쓰는 서식 지정자, 정렬 출력을 위한 폭과 정밀도, 실수를 보기 좋게 출력하는 법, 반환값, 그리고 printf(user_input)이 보안 구멍인 이유.
- scanfC에서 scanf로 입력을 읽는 법 - &가 필요한 이유, 공백 처리 방식, %s의 버퍼 오버플로와 폭으로 막는 법, 반환값 확인하기, 남은 줄바꿈 버그, 그리고 견고한 대안인 fgets.
- 형식 지정자C의 모든 형식 지정자를 한곳에 - %d, %s, %c, %f, %p와 나머지가 뜻하는 것, 길이 수식자 l·ll·h·z, 너비·정밀도·플래그, 그리고 printf와 scanf에서 다르게 동작하는 것들.
- 파일 입출력C에서 파일을 읽고 쓰는 방법 - fopen 모드 표, NULL 검사, fprintf와 fputc로 쓰기, fgets·fscanf·fgetc로 읽기, 정석적인 한 줄씩 읽기 반복문, 그리고 바이너리 모드를 쓸 때.
- 명령줄 인자C 프로그램이 명령줄을 읽는 방법을 살펴봅니다. argc와 argv의 구조, 인자 순회, argv[0]에 담긴 값, atoi 대신 strtol로 숫자 변환하기, 그리고 이 모두를 모아 만든 작은 계산기까지.
전처리기
- 전처리기전처리기는 컴파일러가 보기도 전에 소스 텍스트를 편집합니다. 헤더를 붙여 넣고, 매크로를 치환하고, 코드를 넣거나 빼죠. 지시문 가족 전체와 그 출력을 들여다보는 법을 다룹니다.
- 매크로#define으로 만드는 객체형·함수형 매크로 - 모든 인자와 본문 전체에 괄호가 필요한 이유, 다중 평가의 함정, do-while(0)을 쓰는 여러 줄 매크로, 그리고 함수나 const가 더 나은 도구인 경우.
- 헤더 파일C 프로그램을 여러 파일로 나누는 방법: .h에 들어갈 것과 .c에 들어갈 것, 중복 포함을 막는 인클루드 가드, 여러 파일을 함께 컴파일하기, 그리고 extern으로 전역 변수 공유하기.
- 조건부 컴파일#ifdef, #ifndef, #if, #elif, #else로 빌드마다 다른 코드를 컴파일하는 법 - 디버그 스위치, 플랫폼 분기, -D로 명령줄에서 매크로 정의하기, 그리고 #if 0으로 코드를 비활성화하기.
표준 라이브러리
- 수학 함수math.h 둘러보기 - sqrt, pow, fabs, floor와 ceil, round, fmod, 삼각함수와 로그 계열, INFINITY와 NAN - 그리고 고전적인 undefined reference 오류를 일으키는 -lm 링커 플래그.
- 난수C에서 rand()와 RAND_MAX로 난수를 만드는 법, srand(time(NULL))로 딱 한 번 씨앗을 뿌리는 이유, %로 범위에 대응시키기와 거기서 생기는 작은 편향, 실수 난수, 그리고 재현 가능한 수열.
- 표준 라이브러리표준 헤더가 각각 무엇을 제공하는지 - stdio.h, stdlib.h, string.h, math.h, time.h, ctype.h, limits.h, stdbool.h, stdint.h, assert.h - 짧은 동작 예제와 함께 살펴봅니다.
오류와 디버깅
- 세그멘테이션 오류세그폴트는 프로그램이 소유하지 않은 메모리를 건드렸다는 뜻입니다. 거의 모든 세그폴트를 설명하는 다섯 가지 원인을 최소 예제와 해법과 함께 정리하고, gdb와 AddressSanitizer로 정확한 줄을 찾는 법도 다룹니다.
- 흔한 오류모든 C 프로그래머가 최소 한 번은 겪는 실수들 - 세미콜론 누락, == 대신 =, 암시적 선언, &를 빠뜨린 scanf, ==로 문자열 비교, 정수 나눗셈, 초기화하지 않은 변수, off-by-one - 각각을 증상, 원인, 해결로 정리했습니다.
- 정의되지 않은 동작미정의 동작은 C 표준이 아무 요구도 하지 않는 코드입니다. 그래서 최적화기가 여러분의 검사를 지워 버리는 것을 포함해 무슨 일이든 일어날 수 있습니다. 무엇이 그것을 일으키는지, "제 컴퓨터에서는 되는데요"가 아무것도 증명하지 못하는 이유, 그리고 그것을 잡는 플래그를 다룹니다.