C는 1972년 벨 연구소의 데니스 리치가 유닉스 운영체제를 작성하기 위해 만든, 컴파일 방식의 범용 프로그래밍 언어입니다. 같은 시대에 나온 거의 모든 것보다 오래 살아남았고, 이유는 단순합니다. C는 기계 바로 한 겹 위에 놓여 있습니다. 메모리와 CPU의 실제 능력에 직접 접근하게 해 주면서도, 여전히 사람이 쓴 글처럼 읽힙니다.
반세기가 지난 지금도 C는 다른 언어들이 그 위에 세워지는 언어로 남아 있습니다.
완결된 C 프로그램
여섯 줄에 이 언어의 모양이 다 담겨 있습니다. 실행을 눌러 보세요.
#include <stdio.h>는 표준 입출력 선언을 가져와 printf를 알게 해 줍니다. main은 모든 C 프로그램이 시작하는 곳입니다. printf는 텍스트를 출력하고, \n은 줄바꿈이며, return 0은 프로그램이 성공적으로 끝났음을 운영체제에 알립니다. 이것이 컴파일 가능한 완결된 프로그램입니다. 감싸야 할 클래스도, 밑에 깔린 프레임워크도 없습니다.
C를 다르게 만드는 것
대부분의 현대 언어는 기계를 여러분에게서 감춥니다. C는 의도적으로 그러지 않습니다.
- 네이티브 코드로 컴파일됩니다. C 프로그램은 특정 CPU를 위한 기계 명령어가 되어 직접 실행되며, 중간에 인터프리터도 가상 머신도 없습니다.
- 메모리를 직접 관리합니다. 가비지 컬렉터가 없습니다. 메모리를 요청하고 직접 돌려줍니다. 일이 더 많은 대신, C 프로그램이 메모리를 아주 적게 쓰고 타이밍이 예측 가능한 이유이기도 합니다.
- 작습니다. C의 키워드는 약 32개입니다. 언어 전체를 머릿속에 담을 수 있는데, C++나 자바, 파이썬은 그렇지 않습니다.
- 런타임이 거의 없습니다. C 프로그램은 RAM이 몇 킬로바이트뿐이고 운영체제조차 없는 마이크로컨트롤러에서도 돌아갈 수 있습니다.
그 대가는 안전함 대신 정직함입니다. C는 배열 끝을 넘어 읽거나 이미 해제한 메모리를 쓰는 것을 막지 않습니다. 그런 실수들에는 이름이 있습니다 - 세그멘테이션 폴트, 메모리 누수, 미정의 동작 - 그리고 C를 배운다는 것은 대체로 그것들을 피하는 법을 배우는 일입니다.
C가 실제로 돌아가는 곳
C는 역사 속 유물이 아닙니다. 지금 이 글을 읽고 있는 기기 안에서도 여러 겹으로 돌아가고 있습니다.
- 운영체제. 리눅스 커널은 대략 3천만 줄의 C입니다. 윈도우와 애플 커널의 상당 부분도 C입니다.
- 임베디드 시스템. 자동차, 온도 조절기, 의료 기기, 라우터, 세탁기 속 마이크로컨트롤러는 압도적으로 C로 프로그래밍됩니다. 칩 전체의 메모리가 32KB일 때 C는 종종 유일하게 현실적인 선택지입니다.
- 데이터베이스. SQLite, PostgreSQL, MySQL, Redis는 C 프로그램입니다.
- 언어 런타임. 파이썬의 참조 구현인 CPython은 C로 작성되었습니다. 루비의 MRI, PHP의 Zend 엔진, Lua의 핵심도 마찬가지입니다. 파이썬이 "너무 느릴" 때의 해법은 보통 C 확장입니다.
- 도구와 라이브러리. Git, curl, FFmpeg, OpenSSL, zlib, 그리고 또 SQLite - 컴퓨팅의 기반 계층은 C입니다.
C의 애플리케이션 바이너리 인터페이스가 중요한 이유이기도 합니다. 거의 모든 언어가 C 함수를 호출할 수 있습니다. 파이썬, 러스트, 고, 자바, 자바스크립트 모두 C 라이브러리에 닿는 방법을 가지고 있습니다. C는 언어들 사이의 공용어입니다.
C를 배우는 이유
정직한 답이 두 가지 있고, 각각 다른 사람을 가리킵니다.
시스템 쪽 일 - 커널, 드라이버, 임베디드 펌웨어, 데이터베이스 - 을 하고 싶다면, C는 그 분야의 실무 언어이고 그 영역의 채용 공고 대부분이 (흔히 C++나 Rust와 나란히) C를 직접 언급합니다. C를 편하게 읽지 못하면 리눅스나 PostgreSQL, 마이크로컨트롤러 코드베이스에 기여하기가 어렵습니다.
이미 쓰는 언어를 더 잘하고 싶다면, C는 그 밑에 무엇이 있는지 가르쳐 줍니다. C를 하고 나면 배열 인덱스가 왜 0부터 시작하는지, 문자열이 진짜로 무엇인지, 큰 객체를 값으로 넘기는 것이 왜 비싼지, 스택과 힙이 무엇인지, 널 포인터가 실제로 무엇을 가리키는지 알게 됩니다. 이 개념들은 모든 언어에 등장합니다. C는 그것들이 감춰지지 않고 눈에 보이는 곳입니다.
이 프로그램은 세 줄짜리 C이면서, 동시에 변수가 무엇인지에 대한 완전한 시연입니다. 어떤 주소에 있는 이름 붙은 상자 말입니다. 대부분의 언어에서는 이 질문 자체를 던질 수조차 없습니다.
C와 C++
C++는 1979년에 "클래스가 있는 C"로 시작해 현존하는 가장 큰 언어 중 하나로 자랐습니다. 클래스, 템플릿, 예외, 연산자 오버로딩, STL, 스마트 포인터, 그리고 C의 명세 전체를 왜소해 보이게 만드는 표준 라이브러리가 있습니다.
실무에서의 관계는 이렇습니다.
- C++는 C의 문법을 유지했으므로
if,for,while, 연산자, 포인터, 배열은 둘 다에서 똑같아 보입니다. - 잘 쓰인 C 대부분은 C++로도 컴파일됩니다. 몇 가지 예외가 있습니다(예를 들어 C++는
malloc의 결과를 캐스팅해야 합니다). - C++ 코드는 거의 절대 올바른 C가 아닙니다.
둘 다 배울 생각이라면 C를 먼저 배우는 것이 실질적인 이점입니다. C++는 C의 메모리 모델 위에 그대로 세워져 있고, 사람들이 가장 헷갈려 하는 C++의 부분들 - 포인터, 참조, 복사 의미론, 소멸자 - 은 모두 C가 훤히 보여 주는 문제들에 대한 답이기 때문입니다.
다만 C는 공식적인 의미에서 C++의 부분집합이 아닙니다. 둘은 각자 진화하는 별개의 표준이고, 현대 C에는 C++에 없는 기능도 있습니다(지정 초기화자는 C99에서, restrict 포인터, 가변 길이 배열도 있습니다).
어떤 C인가: 표준 버전들
C는 표준화되어 있고, 오래된 코드를 읽을 때는 버전이 중요합니다.
| 표준 | 연도 | 알아둘 점 |
|---|---|---|
| C89 / C90 | 1989 | 아주 오래된 기준선. 변수는 블록 맨 위에서만 선언. |
| C99 | 1999 | // 주석, 어디서나 변수 선언, stdbool.h의 bool, long long |
| C11 | 2011 | 스레드, 원자 연산, 더 안전한 라이브러리 함수 |
| C17 | 2018 | C11의 버그 수정판 - 오늘날 흔한 기본값 |
| C23 | 2024 | 키워드가 된 true/false/bool, nullptr, 이진 리터럴 |
이 문서들은 C17을 기준으로 하며, 중요한 곳에서는 C99 이상의 기능임을 밝힙니다. 컴파일러는 기본적으로 현대 표준을 쓰지만, 명시할 수도 있습니다.
gcc -std=c17 -Wall -Wextra hello.c -o hello
C에 없는 것
C를 이해하는 일의 절반은 무엇이 없는지 아는 것입니다. 거의 모든 "이건 어떻게 하죠?" 질문의 답이 현대 언어와 다르기 때문입니다.
- 문자열이 없습니다. C의 문자열은 0 바이트로 끝나는
char배열입니다. 둘을 이어 붙이려면 함수를 호출하고 목적지가 충분히 큰지 걱정해야 합니다. - 컬렉션이 없습니다. 리스트도, 맵도, 셋도 없습니다. 고정 크기 배열과 포인터가 전부이고, 해시 테이블은 직접 쓰거나 라이브러리를 링크해야 하는 것입니다.
- 예외가 없습니다. 함수는 특별한 값 -
-1,NULL, 상태 코드 - 을 반환해 실패를 알리고, 호출자가 확인할 것으로 기대됩니다. - 제네릭도 오버로딩도 없습니다. 함수 이름 하나는 함수 하나를 뜻합니다. 표준 라이브러리에 세 가지 타입을 위한
abs,labs,fabs가 따로 있는 이유입니다. - 네임스페이스도 모듈도 없습니다.
static이 아닌 모든 함수가 하나의 전역 이름 공간을 공유하므로, C 라이브러리들은 모든 것에 접두사를 붙입니다(sqlite3_open,png_read_info). - 가비지 컬렉터가 없습니다. 모든
malloc에는 짝이 되는free가 필요하고, 잊으면 메모리 누수입니다.
이 중 어느 것도 실수로 빠진 게 아닙니다. 일부는 C가 떠안기를 거부한 런타임 기계장치(가비지 컬렉터, 예외 되감기, 늘어나는 문자열)이고, RAM이 2KB인 칩에 들어가는 이유의 큰 부분이 거기에 있습니다. 네임스페이스나 제네릭 같은 다른 것들은 그저 C의 설계보다 나중에 나온 언어 기능이고, 언어를 작게 유지하려고 들이지 않았을 뿐입니다.
버퍼가 32바이트인 것은 여러분이 그만하면 충분하다고 결정했기 때문입니다. C는 확인해 주지 않으며, 그 결정 하나에 이 언어의 성격 전체가 담겨 있습니다.
C는 어렵나요?
C는 작지만 봐주는 법이 없습니다. 외울 문법은 많지 않습니다. 일주일이면 언어 전체를 읽을 수 있습니다. 시간이 걸리는 것은 규율입니다. 어느 메모리 조각을 누가 소유하는지 추적하고, 문자열이 0으로 끝나는 바이트 뭉치일 뿐임을 기억하고, 포인터를 따라가기 전에 널이 아닌지 확인하는 일 말입니다.
C의 어려움은 대부분 개념이 아니라 안전장치가 없다는 데 있습니다. 컴파일러는 범위를 벗어난 쓰기나 허공을 가리키는 포인터로부터 구해 주지 않으니, 그래서 정확해지는 법을 배우게 됩니다. (포인터 자체는 처음에 정말로 품이 듭니다. 그게 정상이고, 포인터 장은 천천히 다룹니다.) 그 정확함은 이후에 쓰게 될 모든 언어로 옮겨 가는 기술입니다.
자주 묻는 질문
C 프로그래밍 언어란 무엇인가요?
C는 1972년 벨 연구소의 데니스 리치가 만든 컴파일 방식의 범용 프로그래밍 언어입니다. 메모리를 직접 제어할 수 있고 CPU가 실제로 하는 일에 가깝게 대응되기 때문에, 운영체제와 디바이스 드라이버, 데이터베이스가 여전히 C로 작성됩니다.
오늘날 C는 어디에 쓰이나요?
운영체제 커널(리눅스, 윈도우와 macOS의 일부), 자동차와 가전, 마이크로컨트롤러의 임베디드 펌웨어, SQLite와 PostgreSQL 같은 데이터베이스, CPython과 Redis 같은 언어 런타임, 그리고 다른 언어들이 호출해 쓰는 성능이 중요한 라이브러리에 쓰입니다.
2026년에도 C를 배울 가치가 있나요?
두 가지 이유로 그렇습니다. C는 여전히 시스템과 임베디드 분야의 언어이고, 그 분야는 크고 보수도 좋습니다. 또한 메모리와 포인터, 스택이 실제로 어떻게 동작하는지 가르쳐 줍니다. 대부분의 다른 언어가 C로 구현되어 있으므로, 이 지식은 다른 모든 언어를 다루는 실력을 끌어올립니다.
C와 C++의 차이는 무엇인가요?
C++는 "클래스가 있는 C"로 시작해 C의 문법 대부분을 유지하면서 객체지향, 템플릿, 표준 라이브러리 등을 덧붙였습니다. C는 훨씬 작습니다. 클래스도, 예외도, 제네릭도 없습니다. 올바른 C 대부분은 C++로도 컴파일되지만, 그 반대는 성립하지 않습니다.