1. 유닉스(UNIX)와 C언어

1970년대 초, 벨 연구소(Bell Labs)의 전설적인 엔지니어 데니스 리치(Dennis Ritchie) 켄 톰슨(Ken Thompson)은 컴퓨터 역사에 남을 깊은 고민에 빠져 있었습니다.

 

당시 그들은 유닉스(UNIX)라는 운영체제를 개발 중이었는데, 처음에는 기계어와 1:1로 대응되는 어셈블리어로 작성했습니다.

문제는 연구소에 새로운 컴퓨터가 들어올 때마다, 달라진 하드웨어 특성에 맞춰 유닉스를 처음부터 다시 구현해야 하는 고통을 겪은 것입니다.

 

“컴퓨터(하드웨어)가 바뀌어도, 코드는 수정 없이 그대로 쓸 수 없을까?”

 

이 질문 끝에 그들은 "사람이 이해하기 쉬운 언어로 코드를 짜고, 기계어 번역은 프로그램(컴파일러)에게 맡기자"는 아이디어를 실현합니다.

 

1973년, 그들은 어셈블리어로 되어 있던 유닉스 커널(핵심) 전체를 C언어로 과감하게 재작성합니다.

결과는 혁명적이었습니다. C언어로 작성된 유닉스는 소스 코드를 거의 수정하지 않고도, 각 기계에 맞는 컴파일러(Compiler)만 통과시키면 어떤 컴퓨터에서든 실행할 수 있게 되었습니다.

 

💡 왜 이 개념이 중요한가요?

  1. 소프트웨어 이식성(Portability)의 시작
    과거에는 하드웨어가 바뀌면 소프트웨어도 버려야 했지만, 이제는 코드 하나로 슈퍼컴퓨터부터 PC까지 모두 실행할 수 있게 되었습니다.
  2. 현대 컴퓨터 표준의 확립
    C언어와 유닉스가 전 세계 표준이 되면서, 자연스럽게 하드웨어들도 C언어가 정의한 메모리 구조(1바이트=8비트)를 따르게 되었습니다. 오늘날 Windows, macOS, Linux, Android 모두 이 유산 위에 서 있습니다.

 

 

2. 컴파일러(Compiler) 

C언어(고급 언어)는 어떻게 하드웨어의 구조를 모르는 채로 작동할까요? 비밀은 컴파일러에 있습니다.

컴파일러는 C언어를 바로 기계어로 바꾸는 것이 아니라, 각 하드웨어 전용 컴파일러가 중간 단계인 '어셈블리어'를 먼저 생성하고, 이를 기계어로 변환합니다.

4단계: C언어
(Source Code)
3단계: 컴파일러
(Translation)
2단계: 어셈블리어
(Assembly)
1단계: 기계어
(Machine Code)
x = x + 1;
공통 소스 코드
Intel용 컴파일러
ADD EAX, 1
1011 0000...
ARM용 컴파일러
ADD R1, #1
1110 0010...
💡 핵심: 소스 코드는 하나지만, 각 하드웨어에 맞는 컴파일러를 사용하면 서로 다른 어셈블리어와 기계어가 생성됩니다. 이것이 이식성(Portability)의 원리입니다.

build ← 자세히보기

 

 

3. 현대의 프로그래밍 

이제 전체 그림을 그려봅시다.

개발자의 머릿속에 있는 '생각'이 실제 컴퓨터의 '전기 신호'가 되기까지는 다음과 같은 과정을 거칩니다.

👨‍💻 개발자
(Developer)
4단계: C언어
(Source Code)
3단계: 컴파일러
(Translation)
2단계: 어셈블리어
(Assembly)
1단계: 기계어
(Machine Code)
🤖 컴퓨터
(Execution)
"숫자 1을 더해!"
자연어 / 의도
(기계어 직접 작성
중간 단계 없음)
1011 0101...
⚡ ON/OFF
물리적 전기 신호
제어 및 연산
(어셈블리어 직접 작성
중간 단계 없음)
ADD EAX, 1
(Mnemonic)
⟿ 어셈블러 번역
1011 0000...
x = x + 1;
공통 코드
Intel 컴파일러
ADD EAX, 1
1011 0000...
ARM 컴파일러
ADD R1, #1
1110 0010...
💡 통합 해석: 개발자의 의도("1을 더해!")는 과거 기계어나 어셈블리어로 하드웨어에 맞춰 직접 작성되었으나, 현대에는 C언어 같은 고급 언어로 한 번만 작성(공통 코드)하면 컴파일러가 각 컴퓨터 환경(Intel, ARM 등)에 맞는 어셈블리어와 기계어로 자동 번역해 줍니다.