컴파일러와 인터프리터의 차이: 작성한 코드는 어떻게 실행될까?
컴파일러와 인터프리터의 차이: 작성한 코드는 어떻게 실행될까?#
우리가 작성하는 소스 코드는 사람이 읽고 수정하기 쉬운 형태입니다. 하지만 CPU가 print("Hello, World!") 같은 문장을 그대로 이해하고 실행하는 것은 아닙니다. 프로그램이 동작하려면 소스 코드를 분석하고, 실행 환경이 처리할 수 있는 형태로 바꾸는 과정이 필요합니다.
이때 자주 만나는 두 용어가 컴파일러와 인터프리터입니다.
- 컴파일러: 코드를 분석해 다른 형태의 코드나 실행 파일을 만듭니다.
- 인터프리터: 코드를 실행하는 프로그램을 가리킬 때 사용하며, 구현에 따라 소스 코드나 중간 형태의 코드를 해석해 실행합니다.
입문할 때는 “컴파일러는 미리 번역하고, 인터프리터는 실행하면서 처리한다”라고 이해해도 출발점으로는 좋습니다. 다만 실제 언어 구현은 이 두 방식이 섞여 있습니다. Python을 단순히 “소스 파일을 한 줄씩 기계어로 번역하는 언어”라고 설명하거나, Java를 “컴파일하니 곧바로 .exe가 나오는 언어”라고 설명하면 실제 실행 과정과 달라집니다.
이 글에서는 먼저 공통 개념을 익히고, Python·C·Java의 실행 과정을 비교해 보겠습니다.
가장 먼저 알아둘 용어#
| 용어 | 뜻 | 예시 |
|---|---|---|
| 소스 코드 | 개발자가 프로그래밍 언어로 작성한 코드 | hello.py, hello.c |
| 컴파일 | 소스 코드 등을 다른 형태의 코드로 변환하는 과정 | C 소스 코드 → 기계어를 포함한 실행 파일 |
| 기계어 | CPU가 실행할 수 있는 명령의 표현 | 특정 CPU 구조에 맞는 명령 |
| 바이트코드 | 가상 머신이나 실행 환경이 처리하도록 만든 중간 형태의 코드 | Java 클래스 파일의 바이트코드 |
| 런타임 | 프로그램 실행에 필요한 환경 또는 실행 시점 | Python 실행기, JVM 등 |
| 표준 출력 | 프로그램이 결과 텍스트를 내보내는 통로 | 터미널에 표시되는 Hello, World! |
모든 언어가 이 표의 단계를 같은 순서와 방식으로 거치는 것은 아닙니다. 각 언어의 구현과 실행 환경에 따라 달라집니다.
코드를 작성하면 왜 변환 과정이 필요할까?#
다음 Python 코드를 보겠습니다.
print("Hello, World!")사람은 이것을 “문장을 출력하라”는 뜻으로 읽을 수 있습니다. 하지만 CPU가 이해하는 명령은 특정 하드웨어에 맞춘 기계어입니다. 또한 print()가 무엇을 뜻하는지, 따옴표 안의 문자를 어디로 출력할지 같은 규칙은 프로그래밍 언어와 실행 환경이 정합니다.
따라서 프로그램을 실행하려면 적어도 다음과 같은 일이 필요합니다.
- 소스 코드의 문법을 읽고 올바른 코드인지 확인합니다.
- 각 표현식과 명령이 무엇을 뜻하는지 해석합니다.
- 실행 가능한 형태의 명령으로 바꾸거나 실행 환경에서 처리합니다.
- 파일 읽기나 출력 같은 작업이 필요하면 운영체제가 제공하는 기능을 사용합니다.
컴파일러와 인터프리터는 이 과정의 일부를 맡습니다. “사람의 말을 컴퓨터 언어로 번역하는 번역가”라는 비유는 처음 이해할 때 도움이 되지만, 실제로는 문법 분석, 코드 변환, 실행 환경의 처리가 단계별로 일어납니다.
컴파일러란 무엇인가?#
컴파일러는 입력 코드를 분석해 다른 형태의 코드로 변환하는 프로그램입니다. 흔히 C 소스 코드를 컴파일해 실행 파일을 만드는 사례로 설명합니다.
C 프로그램은 어떻게 실행할까?#
hello.c 파일에 다음 코드를 작성했다고 가정해 보겠습니다.
#include <stdio.h>
int main(void) {
printf("Hello, World!\n");
return 0;
}C 컴파일러인 GCC를 사용하는 환경이라면 다음처럼 컴파일할 수 있습니다.
gcc hello.c -o hello명령이 성공하면 hello라는 실행 파일이 만들어집니다. Linux나 macOS의 터미널에서는 다음처럼 실행할 수 있습니다.
./hello출력 결과:
Hello, World!Windows에서는 생성할 파일 이름을 hello.exe로 지정하고 그 파일을 실행할 수 있습니다.
gcc hello.c -o hello.exe
.\hello.exe이때 중요한 점은 hello.c라는 소스 파일을 실행하는 것과, 컴파일 결과인 실행 파일을 실행하는 것은 다른 단계라는 사실입니다.
컴파일할 때 무엇을 확인할까?#
컴파일러는 소스 코드를 분석하며 문법에 맞지 않는 부분 등을 발견할 수 있습니다.
#include <stdio.h>
int main(void) {
printf("Hello, World!\n")
return 0;
}위 코드는 printf(...) 뒤에 세미콜론이 없습니다. 컴파일러는 이 문제를 보고하고 실행 파일 생성을 완료하지 못할 수 있습니다.
하지만 컴파일에 성공했다고 프로그램이 올바르게 동작한다는 뜻은 아닙니다. 다음처럼 실행 도중에만 알 수 있는 문제도 있습니다.
/* 개념 설명: 입력값에 따라 실행 중 문제가 발생할 수 있다. */
int result = total / count;count가 어떤 값이 될지 실행 전에는 알 수 없는 경우가 있습니다. 또 문법이 맞아도 계산식 자체가 잘못되어 엉뚱한 결과가 나올 수 있습니다.
컴파일하면 반드시 .exe가 만들어질까?#
아닙니다. 무엇으로 컴파일하는지는 언어와 도구에 따라 다릅니다.
C 프로그램은 대상 운영체제와 CPU에 맞는 실행 파일을 만드는 경우가 많습니다. Java 컴파일러는 일반적으로 JVM이 실행할 바이트코드를 담은 .class 파일을 만듭니다. 따라서 “컴파일러는 무조건 기계어 .exe 파일을 만든다”는 설명은 정확하지 않습니다.
인터프리터란 무엇인가?#
인터프리터는 프로그램을 실행하며 코드를 처리하는 실행기를 설명할 때 쓰는 말입니다. 입문서에서는 “코드를 읽으면서 바로 실행한다”라고 비유합니다.
이 설명은 컴파일 결과 파일을 먼저 만드는 전형적인 C 사용 방식과 대비하기에 유용합니다. 그러나 인터프리터가 반드시 소스 파일의 첫 줄을 읽고 기계어로 번역해 실행한 뒤, 다음 줄로 넘어가는 방식으로 동작하는 것은 아닙니다.
Python의 대표적인 구현인 CPython을 예로 보겠습니다.
Python 코드를 실행할 때 일어나는 일#
hello.py 파일을 작성합니다.
print("Hello, World!")터미널에서 실행합니다.
python hello.py환경에 따라 Python 실행 명령이 python3일 수도 있습니다.
python3 hello.pyCPython은 코드를 분석해 바이트코드로 컴파일한 뒤, Python 가상 머신에서 그 바이트코드를 실행합니다. 일상적으로는 “Python 인터프리터로 실행한다”라고 부르지만, 내부에 컴파일 단계가 없다는 뜻은 아닙니다.
전체 흐름을 간단히 적으면 다음과 같습니다.
Python 소스 코드
→ 문법 분석과 바이트코드 생성
→ Python 실행 환경에서 바이트코드 실행
→ 출력Python에서 간혹 보이는 .pyc 파일은 컴파일된 바이트코드를 저장한 파일입니다. 다만 .pyc가 항상 생성되어야만 Python 코드가 실행되는 것은 아닙니다.
Python은 문법 오류를 반드시 그 줄에 도달한 후 발견할까?#
아닙니다. 다음처럼 소스 파일 뒤쪽에 문법 오류가 있다면, 앞부분의 코드가 먼저 실행된 다음 그 줄에서 문법 오류가 발견된다고 일반화할 수 없습니다.
print("첫 번째 작업")
if True
print("두 번째 작업")이 파일은 문법 분석 단계에서 SyntaxError가 발생하므로 첫 번째 print()도 실행되지 않습니다.
반면 문법은 올바르지만 실행 중에 예외가 발생하는 코드는 그 지점에 도달했을 때 문제가 드러날 수 있습니다.
print("첫 번째 작업")
number = 0
print(10 / number)
print("마지막 작업")여기서는 "첫 번째 작업"이 출력된 뒤 ZeroDivisionError가 발생하며, 마지막 작업은 실행되지 않습니다.
즉, 문법 오류와 실행 중 예외는 발견되는 과정이 다릅니다. “인터프리터 언어에서는 오류를 한 줄씩 실행하다 발견한다”는 설명만으로 두 상황을 모두 설명할 수 없습니다.
Python·C·Java의 실행 과정을 비교하면?#
세 언어로 같은 문장을 출력할 수 있지만, 실행 과정에는 차이가 있습니다.
| 언어와 대표적인 실행 방식 | 개발자가 작성하는 파일 | 중간 단계 | 실행할 때 사용하는 것 |
|---|---|---|---|
| Python의 CPython 구현 | .py |
바이트코드 생성 | Python 실행 환경 |
| C의 일반적인 사용 방식 | .c |
컴파일 및 링크로 실행 파일 생성 | 대상 환경에 맞는 실행 파일 |
| Java의 일반적인 사용 방식 | .java |
컴파일해 .class 바이트코드 생성 |
JVM |
이 비교에서 언어의 이름과 실행 방식이 항상 일대일로 연결되는 것은 아니다라는 점을 기억하세요. 같은 언어에도 여러 구현이 있을 수 있고, 실행 환경이 인터프리트와 기계어 변환을 함께 사용할 수 있습니다.
Java는 왜 컴파일도 하고 JVM도 사용할까?#
다음은 Hello.java 예제입니다.
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}소스 코드를 컴파일합니다.
javac Hello.java그러면 일반적으로 Hello.class 파일이 생성됩니다. 이 파일에는 JVM이 처리할 바이트코드가 들어 있습니다. 실행할 때는 다음처럼 JVM을 사용합니다.
java Hello실행 결과:
Hello, World!따라서 원문에 있던 “javac MyProgram.java를 실행하면 MyProgram.exe가 만들어진다”는 설명은 수정해야 합니다. 일반적인 Java 개발 흐름에서 javac의 결과물은 .class 파일입니다.
JVM은 바이트코드를 실행하며, 실행 환경에 따라 자주 사용하는 부분을 기계어로 변환하는 JIT 컴파일을 활용할 수도 있습니다. Java를 “컴파일러 아니면 인터프리터 중 하나”로만 분류하기 어려운 이유입니다.
컴파일과 인터프리트의 장단점은 어떻게 봐야 할까?#
입문서에서는 흔히 다음처럼 정리합니다.
- 컴파일 방식은 실행이 빠르다.
- 인터프리터 방식은 수정 후 바로 실행하기 쉽다.
- 컴파일 방식은 문법 오류를 미리 발견한다.
- 인터프리터 방식은 실행하다가 문법 오류를 발견한다.
이 설명에는 이해를 돕는 부분이 있지만, 실제 언어 전체에 적용하기에는 지나치게 단순합니다.
실행 속도는 번역 방식 하나로 결정되지 않는다#
미리 생성한 기계어를 실행하는 방식은 실행 중 코드 해석 비용을 줄이는 데 도움이 될 수 있습니다. 그러나 프로그램의 속도는 다음에도 영향을 받습니다.
- 사용한 알고리즘과 자료구조
- 언어의 실행 환경
- JIT 컴파일 등 최적화 방식
- 디스크·네트워크 대기 시간
- 작업에 사용하는 라이브러리
예를 들어 네트워크 응답을 기다리는 프로그램에서는 코드 실행 방식보다 외부 서비스의 응답 시간이 훨씬 큰 영향을 줄 수 있습니다. “어느 언어가 무조건 빠르다”는 결론보다 실제 작업을 측정하는 것이 정확합니다.
수정 후 다시 실행할 수 있는 속도도 프로젝트에 따라 다르다#
C처럼 실행 파일을 만드는 방식에서는 소스 변경 후 다시 빌드해야 합니다. 반면 Python 스크립트는 소스 파일을 수정하고 곧바로 실행하는 작업 흐름이 일반적입니다.
하지만 큰 프로젝트에서는 테스트, 패키징, 의존성 설치, 서버 재시작에도 시간이 듭니다. 코드에 명시적인 컴파일 명령이 없다고 모든 개발과 배포가 항상 빠른 것은 아닙니다.
문법 오류의 발견 시점을 단순히 둘로 나눌 수 없다#
C 컴파일러는 실행 파일을 만들기 전에 소스 코드의 문법 오류를 알려줍니다. Python 역시 소스 파일을 실행하기 전에 문법 분석을 하므로, 실행되지 않은 뒤쪽 코드의 문법 오류 때문에 파일 전체를 시작하지 못할 수 있습니다.
반면 입력값에 따라 발생하는 0으로 나누기 같은 문제는 어느 쪽에서도 실제 실행 중 드러날 수 있습니다.
미리 발견할 수 있는 오류와 실제 값이 들어와야 알 수 있는 오류를 구분하는 편이 이해에 도움이 됩니다.
플랫폼에서 실행되는 방식도 다르다#
특정 환경을 대상으로 만든 C 실행 파일은 다른 운영체제나 CPU에서 그대로 실행되지 않을 수 있습니다. Python 소스 코드는 해당 환경에 적절한 Python 실행기와 필요한 라이브러리가 있다면 여러 운영체제에서 실행할 수 있습니다. Java 바이트코드는 호환되는 JVM이 있는 환경에서 실행할 수 있습니다.
하지만 Python이나 Java 프로그램도 운영체제 전용 라이브러리와 기능을 사용하면 플랫폼 차이를 고려해야 합니다. 실행 환경이 있다는 사실과 모든 프로그램이 수정 없이 이식된다는 주장은 다릅니다.
코드를 작성한 뒤 화면에 결과가 나오기까지#
Hello, World!를 출력하는 간단한 프로그램을 기준으로 전체 흐름을 다시 정리해 보겠습니다.
1단계: 무엇을 만들지 정한다#
목표를 “문장을 한 번 출력한다”라고 정합니다. 작은 기능이라도 기대하는 결과가 있어야 코드를 확인할 수 있습니다.
기대 결과: Hello, World!2단계: 소스 코드를 작성한다#
Python 예제라면 hello.py에 다음 내용을 저장합니다.
print("Hello, World!")이 파일은 소스 코드입니다. 작성할 때 메모장 같은 텍스트 편집기나 코딩용 편집기를 사용할 수 있습니다. 어떤 편집기를 쓰든 실행될 프로그램의 핵심은 파일에 저장된 코드입니다.
3단계: 실행 환경을 통해 실행한다#
터미널에서 Python 실행기와 파일 이름을 지정합니다.
python hello.pyPython 실행기는 소스 코드를 분석하고 실행합니다. C라면 컴파일 및 링크로 실행 파일을 만든 뒤 그 파일을 실행하고, Java라면 바이트코드를 만든 뒤 JVM에서 실행합니다.
4단계: 프로그램이 출력한다#
Python의 print()는 텍스트를 표준 출력으로 보냅니다. 터미널에서 실행했다면 터미널이 그 출력을 받아 화면에 표시합니다.
Hello, World!원문에는 CPU가 각 문자를 가져와 그래픽 카드에 직접 “이 글자를 픽셀로 그려라”라고 지시한다는 설명이 있습니다. 이는 실제 출력 경로를 지나치게 단순화한 비유입니다. 터미널에서의 일반적인 흐름은 프로그램 → 표준 출력 → 터미널 프로그램 → 운영체제의 화면 표시 기능으로 이해하는 편이 정확합니다.
같은 프로그램이라도 출력을 파일로 보내면 화면 대신 파일에 기록할 수 있습니다.
python hello.py > output.txtprint()가 본질적으로 “모니터 픽셀을 직접 그리는 명령”은 아니라는 점을 보여주는 예입니다.
5단계: 결과를 확인한다#
기대했던 문자열이 출력되었는지 확인합니다. 결과가 다르다면 다음을 구분해 살펴봅니다.
- 파일에 작성한 코드가 다른가?
- 실행한 파일 경로가 다른가?
- 문법 오류가 있는가?
- 실행 중 예외가 발생했는가?
- 프로그램은 실행됐지만 출력 내용이 틀렸는가?
이 질문들이 이후 디버깅의 출발점이 됩니다.
처음 배우는 사람이 자주 혼동하는 부분#
“코드는 결국 1과 0으로 바뀐다”는 말은 틀렸을까?#
CPU가 실행하는 명령과 컴퓨터 안의 데이터는 물리적으로 비트로 표현됩니다. 하지만 우리가 작성한 소스 코드가 언제나 곧바로 하나의 기계어 파일로 번역되는 것은 아닙니다.
소스 코드가 바이트코드 같은 중간 형태로 바뀌고, 실행 환경이 이를 처리할 수도 있습니다. “최종적으로 컴퓨터가 처리할 수 있는 형태가 필요하다”는 핵심은 맞지만 그 경로는 여러 가지입니다.
“인터프리터는 무조건 한 줄씩 실행한다”는 말은 맞을까?#
한 줄씩 읽는 비유는 실행하며 코드를 처리한다는 느낌을 설명하는 데 도움이 됩니다. 하지만 Python 소스 파일은 문법 분석과 바이트코드 생성 과정을 거칩니다. 따라서 줄 단위 비유를 내부 구현 그대로 받아들이면 문법 오류의 발견 시점을 잘못 이해하게 됩니다.
“Java는 컴파일 언어이고 Python은 인터프리터 언어”라고 외우면 될까?#
입문용 분류로 들을 수는 있지만, 둘 모두 내부에 여러 단계가 있습니다. Python의 대표적인 구현은 바이트코드를 만들고, Java는 바이트코드로 컴파일한 뒤 JVM에서 실행합니다.
언어를 둘 중 하나의 상자에 넣기보다, 소스 코드가 어떤 중간 형태를 거쳐 어디에서 실행되는지 살펴보는 편이 정확합니다.
“처음 배우는 언어는 실행 방식으로 정해야 할까?”#
처음에는 문법과 개발 환경이 이해하기 쉽고, 원하는 작은 프로그램을 직접 만들어 볼 수 있는 언어를 고르면 됩니다. 한 언어로 변수·조건문·반복문·함수·자료구조와 오류를 다루는 법을 익히면 다른 언어를 배울 때도 도움이 됩니다.
다만 언어를 바꾸면 문법만 달라지는 것은 아닙니다. 자료형의 동작, 메모리 관리, 실행 환경, 개발 도구도 다릅니다. 공통 개념은 옮겨 갈 수 있지만 새로운 언어의 고유한 규칙도 다시 배워야 합니다.
직접 비교해 보기#
다음 세 프로그램은 모두 같은 문자열을 출력합니다.
Python#
hello.py:
print("Hello, World!")실행:
python hello.pyC#
hello.c:
#include <stdio.h>
int main(void) {
printf("Hello, World!\n");
return 0;
}컴파일 및 실행:
gcc hello.c -o hello
./helloJava#
Hello.java:
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}컴파일 및 실행:
javac Hello.java
java Hello세 코드의 출력은 같습니다. 하지만 개발자가 작성한 파일과 실행 단계는 다릅니다. 이 차이를 한 번 직접 확인하면 소스 코드, 컴파일 결과물, 실행 환경이 서로 다른 것이라는 점이 분명해집니다.
정리#
컴파일러와 인터프리터는 소스 코드가 실행되기까지의 과정을 이해하는 데 필요한 개념입니다.
- 컴파일러는 코드를 분석해 기계어 실행 파일이나 바이트코드 등 다른 형태의 코드를 만듭니다.
- 인터프리터는 실행 환경에서 코드를 처리하며 프로그램을 실행하는 방식을 설명할 때 사용합니다.
- Python의 대표적인 구현은 소스 코드를 바이트코드로 컴파일한 뒤 실행합니다.
- C는 일반적으로 대상 환경의 실행 파일을 만드는 과정을 거칩니다.
- Java는 바이트코드로 컴파일한 뒤 JVM에서 실행합니다.
처음에는 도구의 이름을 외우는 것보다 **“내가 작성한 파일은 무엇이고, 무엇이 그것을 분석하며, 실제로 실행하는 것은 무엇인가?”**를 따라가 보세요. 이 흐름을 이해하면 새로운 언어를 만나도 실행 명령, 오류 메시지, 배포 파일이 각각 어떤 역할을 하는지 훨씬 쉽게 파악할 수 있습니다.