개발 · 자동화기초 · 18분 읽기

소스 코드에서 실행까지: 컴파일·인터프리터·로더

소스 코드가 실행되기까지의 변환 단계와 오류 발생 시점을 구분하고, 컴파일러·인터프리터·로더와 Python 바이트코드 실행을 정리합니다.

개념부터 찾아보기

정의·역할·동작 원리를 독립 페이지에서 확인하세요. 본문의 밑줄 용어에도 설명이 있습니다.

이 노트에서 설명할 수 있게 되는 것

  1. 전처리·컴파일·어셈블·링크·적재가 바꾸는 대상을 구분한다.
  2. Python도 실행 전에 파싱과 컴파일을 수행한다는 점을 오류 예제로 설명한다.
  3. 절차·함수·객체 지향을 자원 관리나 세대 서열과 혼동하지 않는다.

01소스 파일·프로그램·프로세스

  • 소스 파일은 사람이 작성한 명령과 선언을 언어 문법에 맞춰 저장한 텍스트다.
    • 실행 파일은 대상 환경에서 적재·실행할 수 있도록 준비된 코드와 메타데이터다.
    • 는 그 프로그램이 실행되면서 주소 공간·열린 파일·권한·실행 상태를 가진 운영체제의 관리 단위다.
    • 같은 실행 파일을 두 번 실행하면 보통 서로 다른 가 생긴다.
  • ·어셈블·링크·적재·실행은 각각 역할과 성공 조건이 다르다.
    • 문법 오류, 링크 오류, 실행 중 예외와 권한 오류를 발생 단계에 맞춰 구분한다.
    • C/GCC는 대표적인 도구 체인 예이며 모든 언어가 같은 중간 파일을 만드는 것은 아니다.

02C 프로그램이 실행되는 단계

  1. 헤더 포함, 매크로와 조건부 컴파일을 처리한다.
  2. 구문·타입 등을 분석하고 대상 기계에 맞는 코드 표현을 만든다. GCC의 -S는 어셈블리 출력을 남긴다.
  3. 어셈블리 텍스트를 기계어·심볼·재배치 정보가 있는 로 바꾼다.
  4. 과 라이브러리의 정의·참조를 연결하고 실행 형식을 만든다. 해결되지 않은 심볼은 링크 오류가 될 수 있다.
  5. 운영체제가 주소 공간을 준비하고, 필요한 경우 동적 가 공유 라이브러리와 심볼을 연결한다.
  6. 런타임 초기화 뒤 프로그램의 동작을 수행한다. 실행 중 오류와 자원 부족은 앞선 구문·링크 오류와 구분한다.

03C 도구 체인: 전처리부터 목적 파일까지

  • 전처리기는 #include에 따라 헤더 내용을 포함하고 매크로와 조건부 컴파일 지시문을 처리한다.
    • 이어 가 구문·타입 등을 분석하고 대상 기계의 코드로 내릴 표현을 만든다.
    • GCC에서 -S는 어셈블리 출력을 남기고, 어셈블러는 이를 기계어와 심볼·재배치 정보가 들어 있는 로 만든다.
  • .s는 사람이 읽을 수 있는 어셈블리 텍스트이고, .o/.obj는 보통 바이너리 이다.
    • 에는 아직 외부 함수의 최종 주소를 해결하지 못한 참조가 있을 수 있다.
    • 확장자만 보지 말고 도구·파일 형식·대상 플랫폼까지 확인한다.
단계주로 바뀌는 것실패 예시
전처리헤더·매크로가 펼쳐진 소스include 파일 없음
컴파일·어셈블언어 표현 → 대상 명령어와 문법·타입·어셈블리 오류
링크의 심볼 참조와 배치함수 정의가 없어 undefined reference
적재·실행주소 공간 구성 후 제어 이전라이브러리 없음, 권한·런타임 오류
C / GCC
gcc -E hello.c -o hello.i
gcc -S hello.i -o hello.s
gcc -c hello.s -o hello.o
gcc hello.o -o hello

각 명령은 전처리, 어셈블리 생성, 생성, 링크를 구분해 보여 주는 예시다. 이 웹사이트를 만드는 과정에서 실행한 명령은 아니다.

04링커와 로더가 맡는 서로 다른 일

  • 는 여러 과 라이브러리의 정의·참조를 맞추고 코드와 데이터를 실행 형식에 배치한다.
    • 정적 링크에서는 필요한 라이브러리 코드를 결과물에 포함할 수 있고, 동적 링크에서는 공유 라이브러리에 대한 참조를 남겨 에서 연결한다.
    • “컴파일이 됐다”와 “링크가 됐다”는 서로 다른 성공 조건이다.
  • 실행을 시작할 때 운영체제는 실행 파일을 확인하고 코드·데이터 등의 주소 공간 매핑을 준비한다.
    • 동적 실행 파일에서는 동적 가 공유 라이브러리를 찾아 매핑하고 필요한 심볼 처리를 수행한다.
    • 언어 런타임 초기화 뒤 사용자 코드에 도달한다.
    • 전체 파일을 RAM에 한꺼번에 복사해야만 실행되는 것은 아니며, 실제 페이지 적재는 운영체제의 정책과 연결된다.
  • 소스는 같아도 CPU 명령어 집합, ABI, 라이브러리 버전, 실행 파일 형식이 다르면 그대로 실행되지 않을 수 있다.
    • 반대로 공유 라이브러리 하나가 누락되면 프로그램 시작 전에 실패할 수 있다.
    • 이 경우 소스 문법을 고치는 대신 과 의존성을 조사해야 한다.
원리 심화

05ELF의 Section·Segment·Symbol·Relocation

  • ELF 헤더는 파일 종류·대상 ·바이트 순서·진입점 등 해석 정보를 제공한다. ELF라는 형식이 같아도 CPU 명령어와 ABI가 다르면 그대로 실행할 수 없다.
  • 링킹에 필요한 코드·데이터· 정보를 분리한다. .text, .data, .bss 같은 이름은 흔한 구성 예이고 파일마다 존재하는 절이 같지는 않다.
  • 적재 시에는 program header의 정보로 파일 내용을 주소 공간에 매핑한다. 하나의 가 여러 을 포함할 수 있다.
  • 0으로 초기화할 영역은 파일에 같은 양의 0을 모두 저장하지 않아도 된다. 따라서 적재 의 메모리 크기가 파일 내 데이터 크기보다 클 수 있다.
  • 다른 파일에 정의된 함수·데이터를 참조하면 에는 심벌과 위치 보정 정보가 남을 수 있다. 링크 시 결합하거나 동적 적재 과정에서 필요한 주소를 연결한다.
  • 공유 라이브러리 이름·검색 경로·심벌 버전·ABI가 실제 환경과 맞아야 한다. 파일이 설치되어 있다는 사실과 필요한 심벌을 제공한다는 사실은 다르다.
예제로 추적

06두 C 파일에서 컴파일 성공과 링크 실패를 분리하기

  1. 가상 예제 main.c는 int add(int,int)를 선언하고 add(2,3)==5이면 종료값 0을 반환한다. 선언은 호출 규격이며 함수 본문을 제공하지 않는다.
  2. gcc -c main.c는 main.o를 만든다. 아직 add의 정의가 없어도 외부 심벌 참조를 포함한 을 만들 수 있다.
  3. main.o만 실행 파일로 링크하면 add의 정의를 해결하지 못하는 링크 오류가 난다. 이 경우 main 함수가 실행되다 실패한 것이 아니다.
  4. add.c에 int add(int a,int b){return a+b;}를 두고 add.o를 만든 뒤 main.o와 함께 링크한다. 같은 심벌 이름뿐 아니라 선언과 구현의 타입 계약도 맞아야 한다.
  5. 실행 형식과 필요한 라이브러리가 맞으면 와 런타임 초기화 뒤 main이 실행된다. 예제의 입력 2·3에 대한 조건이 참이면 종료값은 0이다.
  6. 소스, , 링크 명령, 과 종료값을 연결한다. 링크 성공만으로 모든 입력의 정답이나 실행 중 예외가 없음을 증명하지는 않는다.
관찰 지점입력 또는 검사 예얻는 정보
컴파일gcc -c main.c소스의 해당 번역 단위가 로 변환됨
readelf -s main.o정의·미정의 심벌의 구분
링크gcc main.o add.o -o demo필요한 ·라이브러리의 결합
적재 구조readelf -l demoprogram header와 구성
실행예제 입력에 대한 종료값 확인그 실행 경로의 관찰 결과

07인터프리터에도 파싱과 컴파일이 있다

  • 인터프리터 방식이라는 말은 구현이 프로그램을 실행하는 방식의 큰 분류다.
    • 원본 파일을 반드시 한 줄씩 번역 없이 실행한다는 뜻이 아니다.
    • 일반적인 CPython은 소스를 토큰화·파싱하고 코드 객체와 바이트코드를 만들어 인터프리터가 실행한다.
    • import된 모듈의 바이트코드를 .pyc 캐시에 저장하기도 하지만 이는 보통 독립적인 네이티브 실행 파일이 아니다.
  • Java의 대표 구현도 소스를 JVM 바이트코드로 컴파일한 뒤 에서 실행하고, 실행 중 일부 코드를 JIT 컴파일할 수 있다.
    • 따라서 “C는 컴파일, 은 인터프리터, Java는 컴파일이므로 모두 같은 실행 경로”처럼 세 칸으로만 외우면 실제 처리를 놓친다.
    • 언어와 특정 구현·실행 옵션을 분리해서 설명하는 편이 정확하다.
import dis

def add(a, b):
    return a + b

dis.dis(add)

함수의 CPython 바이트코드를 읽는 예제다. 명령 이름과 배치는 버전별로 달라지며, CPU 기계어를 출력하는 도구가 아니다.

08문법 오류와 실행 중 예외의 발생 시점

  • 하나의 소스 파일 전체를 실행하는 경우, 뒤쪽 줄에 구문 오류가 있어 파싱·컴파일에 실패하면 앞쪽 print도 실행되지 않는다.
    • 반면 문법이 올바르고 실행 중 0으로 나누는 예외가 발생하면 그 지점 앞의 부작용은 이미 일어났을 수 있다.
  • 대화형 셸에서는 입력 단위를 각각 컴파일·실행한다.
    • 이전 입력에서 실행한 결과가 다음 입력의 문법 오류로 취소되지 않는 것은 이 때문이다.
    • Notebook의 셀도 상태가 남을 수 있어, 보이는 줄 순서와 실제 실행 순서가 다를 수 있다.
    • 재현하려면 새 에서 순서대로 실행했는지 확인한다.
# 사례 A: 이 세 줄을 하나의 파일로 실행하면 '시작'도 출력되지 않는다.
print('시작')
if True
    print('완료')

# 사례 B: 별도 파일로 실행하면 '시작' 출력 후 ZeroDivisionError가 발생한다.
print('시작')
value = 1 / 0
print('완료')

두 사례는 각각 별도 파일이라고 가정한다. 두 사례를 한 파일에 함께 붙이면 사례 A의 문법 오류 때문에 전체가 실행되지 않는다.

LEARN THROUGH PRACTICE

객체 식별자와 실행 중 예외를 실제 출력으로 구분하기

실행 결과가 무엇을 증명하는지 한정하고, 값의 동일성·객체 정체성·예외 발생 지점을 따로 읽는다.

실습 구성과 전제

  • 정수 대입 스크립트와 문자열 검색 셀은 별도 실험이다. 같은 커널의 연속 실행으로 가정하지 않는다.
  • 구현과 실행 방식에 따라 상수 재사용은 달라질 수 있다. 화면의 id 숫자를 다른 컴퓨터에서 재현할 필요는 없다.
  1. 같은 값과 같은 객체를 나누어 읽는다

    • ==는 값 비교이고 is는 객체 정체성 비교다. id는 객체가 살아 있는 동안 유지되는 식별자다.
    • 캡처의 각 스크립트 실행에서는 같은 정수 리터럴이 동일 객체로 재사용된 것으로 읽을 수 있다. 이것을 모든 큰 정수에 보장되는 언어 규칙으로 일반화하지 않는다.
    파이썬 (개인) · 명령·상태 출력 2원본 크기로 보기 ↗
    • 257을 두 이름에 대입한 실행에서 두 id 출력이 같다.
    • 1000으로 바꾼 별도 실행에서도 두 id는 같지만 앞 실행의 숫자와는 다르다.
    확인 기준

    구현·버전·실행 파일 또는 셀 경계를 기록하고, 실제 값 비교에는 ==를 사용한다.

  2. 문법을 통과한 뒤 실패한 호출을 찾는다

    • 문자열 index는 지정 범위에서 처음 일치한 위치를 반환하고 없으면 ValueError를 발생시킨다. end는 포함되지 않는다.
    • 사진에는 앞의 세 호출 출력이 있으므로 해당 셀은 실행을 시작했다. 네 번째 호출의 런타임 예외를 구문 분석 실패와 구별한다.
    python · 코드와 실행 결과 28원본 크기로 보기 ↗
    • 출력 0, 8, 5 다음에 ValueError: substring not found가 나타난다.
    • 화살표는 index('s', 3, 5) 호출을 가리킨다. 위치 5는 이 검색 범위에 포함되지 않는다.
    확인 기준

    문자열 위치를 0부터 세고, start≤위치<end 조건을 직접 대입한다.

  3. 같은 조건을 작은 예제로 재확인한다

    • index의 예외를 예상하고 처리하면 뒤의 검증도 계속할 수 있다. 예외 처리 없이 실행하면 실패한 호출 이후의 문장은 실행되지 않는다.
    • 노트북에서는 셀을 실행한 순서에 따라 변수 상태가 달라진다. 검증할 값의 대입부터 함께 실행해 이전 셀 상태의 영향을 줄인다.
    s = 'sabcdsefs'
    print(s.index('s'), s.index('s', 6), s.index('s', 3, 7))
    try:
        print(s.index('s', 3, 5))
    except ValueError:
        print('검색 범위에 없음')

    관찰한 세 위치와 예외 조건을 한 번에 재구성한 예제다.

    확인 기준

    0 8 5와 범위 없음이 나오는지 확인한다. 객체 id의 특정 숫자는 성공 기준에 넣지 않는다.

실습에서 확인한 동작 원리

  • 실행 출력은 그 실행 조건의 증거이며 구현별 최적화까지 언어의 보편 규칙으로 만들지 않는다.
  • 앞선 출력과 traceback 위치를 함께 보면 파싱·실행·예외 처리 중 어느 단계에서 멈췄는지 설명할 수 있다.

설정·출력 추가로 확인하기 9장

아래 화면을 펼쳐 위 실습의 설정값, 명령과 확인 기준을 비교해 보세요. 각 묶음은 별도 실습 기록입니다.

실습 화면 묶음 01 1장
CLI 개발 도구 설치와 활용 · LLM OS에 적용원본 크기로 보기 ↗
  • 학습 원본 · CLI 개발 도구 설치와 활용의 LLM OS에 적용에 수록된 화면·도식이다.
실습 화면 묶음 02 1장
CLI 개발 도구 설치와 활용 · LLM OS에 적용원본 크기로 보기 ↗
  • 학습 원본 · CLI 개발 도구 설치와 활용의 LLM OS에 적용에 수록된 화면·도식이다.
실습 화면 묶음 03 1장
CLI 개발 도구 설치와 활용 · LLM OS에 적용원본 크기로 보기 ↗
  • 학습 원본 · CLI 개발 도구 설치와 활용의 LLM OS에 적용에 수록된 화면·도식이다.
실습 화면 묶음 04 1장
CLI 개발 도구 설치와 활용 · LLM OS에 적용원본 크기로 보기 ↗
  • 학습 원본 · CLI 개발 도구 설치와 활용의 LLM OS에 적용에 수록된 화면·도식이다.
실습 화면 묶음 05 1장
Jupyter 서버 접속 설정원본 크기로 보기 ↗
  • 학습 원본 · Jupyter 서버 접속 설정의 해당 절에 수록된 화면·도식이다.
실습 화면 묶음 06 1장
Jupyter 서버 접속 설정원본 크기로 보기 ↗
  • 학습 원본 · Jupyter 서버 접속 설정의 해당 절에 수록된 화면·도식이다.
실습 화면 묶음 07 2장
pymysql · 실습·개념 참고 화면 4원본 크기로 보기 ↗
  • 「jupyter server 에 로그남기기 (파일만 생성)」 문맥에 삽입된 원본 실습·개념 참고 화면이다. 소스 코드에서 실행까지 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
pymysql · 실습·개념 참고 화면 5원본 크기로 보기 ↗
  • 「jupyter server 에 로그남기기 (파일만 생성)」 문맥에 삽입된 원본 실습·개념 참고 화면이다. 소스 코드에서 실행까지 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
실습 화면 묶음 08 1장
suricata · 실습·개념 참고 화면 4원본 크기로 보기 ↗
  • 문서 문맥에 삽입된 원본 실습·개념 참고 화면이다. 소스 코드에서 실행까지 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
동작·명령 참고 문서

09Jupyter의 문서 순서와 커널 실행 상태는 다르다

  • 브라우저는 셀 편집 화면이고 서버는 문서 저장과 통신을 중개한다. 코드를 실제로 실행하는 커널은 별도 이며 변수와 객체를 메모리에 유지한다.
  • 는 코드·설명·출력·메타데이터를 담는 JSON 문서다. 저장된 출력이 보인다는 사실만으로 현재 커널에 그 변수가 존재하거나 같은 환경이 준비됐다고 판단하지 않는다.
  • Notebook 서버를 시작한 과 선택한 커널의 이 다를 수 있다. 셀의 sys.executable과 터미널 출력을 비교하여 패키지와 실행기를 연결한다.
  1. 빈 커널에서 x = 1 셀을 실행하면 이름 x가 정수 1을 참조한다.
  2. x += 1 셀을 실행하면 현재 x를 읽어 계산한 정수 2로 다시 연결한다. 이 셀만 한 번 더 실행하면 3이 된다.
  3. 재시작하면 앞선 실행 상태가 사라진다. 문서에 이전 출력이 남아 있어도 x += 1부터 실행하면 x가 정의되지 않아 NameError가 날 수 있다.
  4. 새 커널에서 정의 셀부터 위에서 아래로 실행해 의도한 결과가 나오는지 확인한다. 문서 배치와 실제 실행 이력의 차이를 줄이는 검증이다.

10함수 호출·지역 변수·객체 수명

  • 함수를 호출하면 인수와 지역 이름, 실행 위치 등 호출을 이어 가기 위한 상태가 필요하다.
    • 이 과 함수가 만든 객체의 수명은 같지 않다.
    • 함수가 반환한 리스트를 호출자가 참조하면 함수 호출이 끝나도 리스트를 계속 사용할 수 있다.
    • “함수 종료 시 모든 heap 데이터가 사라진다”는 식으로 설명하면 이 동작을 설명할 수 없다.
  • 의 대입은 이름을 객체에 연결한다.
    • b = a는 a라는 이름 자체를 b로 복사하거나 언제나 데이터를 깊은 복사하는 연산이 아니다.
    • a와 b가 같은 리스트 객체를 가리키면 한쪽 이름으로 수정한 결과가 다른 쪽에서도 보인다.
    • b = 10으로 재대입하면 b의 연결이 바뀌며 a의 연결까지 바뀌지는 않는다.
  • C의 정수 변수 대입과 의 객체 이름 연결을 비교하는 것은 도움이 되지만, 이를 “절차 지향은 값 복사, 객체 지향은 참조 복사”라는 보편 법칙으로 만들면 틀린다.
    • 값·포인터·참조·가변성은 각 언어의 자료 모델과 자료형에 따라 판단한다.
def make_items():
    items = [1, 2]
    return items

a = make_items()
b = a
b.append(3)
print(a)  # [1, 2, 3]
b = [9]
print(a, b)  # [1, 2, 3] [9]

함수 반환 후 객체가 살아 있는 경우와, 같은 객체의 변경·이름의 재연결을 구분한다.

11절차·함수·객체·관점 지향의 학습 위치

  • 절차적 구성은 작업 순서와 제어 흐름을 중심으로 프로그램을 나눈다.
    • 함수를 쓰면 공통 동작에 이름을 붙이고 입력·출력으로 연결할 수 있다.
    • 그러나 함수를 쓴다는 사실만으로 함수형 프로그래밍이 되지는 않는다.
    • 함수형 관점은 값의 변환·함수 합성·부작용 관리 등을 중요하게 다루며, 순수 함수는 같은 입력에 같은 결과를 내고 관찰 가능한 외부 상태를 바꾸지 않는 식으로 설명한다.
  • 객체 지향에서는 관련 상태와 동작을 객체로 묶고 책임·인터페이스·객체 사이의 협력을 설계한다.
    • 클래스는 인스턴스를 만드는 정의이며, 메서드도 함수적 동작이다.
    • “객체 지향에는 함수가 없다”는 표현은 맞지 않는다.
    • 한 언어에서 절차적 코드, 함수, 객체를 함께 사용할 수 있다.
  • 관점 지향은 로깅··접근 검사처럼 여러 기능에 걸치는 관심사를 분리하는 방법으로 이해한다.
    • 모든 프로그램이 절차 → 함수 → 객체 → 관점이라는 연대기 단계로 발전해야 하는 것은 아니다.

12성능과 자원 관리는 실행 측정으로 확인하기

  • 컴파일 언어이면 항상 빠르고 자원을 자동으로 잘 관리하며, 인터프리터이면 자원을 관리하지 않는다는 구분은 성립하지 않는다.
    • 알고리즘, 자료구조, I/O 대기, 외부 라이브러리, 런타임 최적화가 실제 병목을 바꾼다.
    • 호출 한 번이 네이티브 라이브러리의 대규모 계산으로 이어질 수도 있다.
  • 운영 관점에서는 프로그램이 실패한 단계를 먼저 구분한다.
    • 실행 파일을 못 찾는지, 로딩 중 라이브러리가 없는지, 초기 설정을 못 읽는지, 요청 처리 중 예외인지, CPU·메모리·I/O가 부족한지 확인한다.
    • 오류 메시지·종료 코드·표준 오류·서비스 로그를 실행 단계와 연결해야 수정할 대상을 좁힐 수 있다.
검증·예외

13실행 실패를 단계·환경·관측값으로 좁히기

  • 파일·행·진단 메시지와 실제 컴파일 옵션을 연결한다. 헤더 경로 문제, 구문·타입 오류를 문제와 섞지 않는다.
  • 미정의 심벌이면 필요한 객체·라이브러리 포함과 링크 순서를 본다. 중복 정의이면 같은 구현을 두 번 넣었는지 확인한다.
  • 실행권한·파일 형식·CPU/ABI·지정 인터프리터와 라이브러리 경로를 구분한다. 프로그램 파일의 존재만으로 운영체제가 적재할 수 있다고 단정하지 않는다.
  • 표준 오류·종료 상태·신호·traceback을 보존한다. 호출 경로의 마지막 줄과 원인 지점이 항상 같은 위치인 것은 아니다.
  • 컴파일 최적화·디버그 정보·런타임 버전에 따라 코드 배치와 추적 출력이 달라질 수 있다. 재현 기록에는 빌드 옵션과 바이너리 식별값을 남긴다.
  • 같은 바이너리의 환경 차이인지, 다른 빌드의 코드 차이인지 먼저 분리한다. 동시에 라이브러리·옵션·입력을 모두 바꾸면 성공의 원인을 좁히기 어렵다.
READ · OBSERVE · CONNECT

함께 보는 학습 자료

아래 공식 자료를 본문의 개념·예제와 함께 읽어 보세요. 자료별로 확인할 위치와 관찰할 내용을 적었습니다.

도해

Jupyter 브라우저·서버·커널 구조도

The IPython Kernel과 The Interface의 두 그림을 이어 읽는다. 브라우저의 셀 실행·파일 저장이 각각 커널과 서버의 어느 책임으로 연결되는지 비교하는 자료다.

읽으면서 확인할 것
  • 코드를 실행하는 별도
  • 파일을 저장하는 서버의 역할
  • 화면의 문서 순서와 커널에 남아 있는 실행 상태의 차이
학습 확인

이해했는지 확인하기

  • .s와 .o/.obj를 같은 파일이라고 설명하지 않을 수 있는가?
  • Python의 SyntaxError와 실행 중 예외가 앞선 출력에 미치는 차이를 설명할 수 있는가?
  • 반환된 리스트가 함수 종료 후에도 살아 있는 이유를 이름과 객체로 설명할 수 있는가?
  • 프로그램 시작 실패와 요청 처리 실패에서 확인할 근거를 다르게 고를 수 있는가?

함께 확인할 공식 자료