사전 정적 링크
개념

정적 링크

gabury1고친 사람 github-actions[bot]

정적 링크는 프로그램이 쓸 라이브러리 코드를 빌드할 때 실행 파일 안으로 복사해 넣습니다. 그래서 완성된 파일 하나만 옮기면 다른 컴퓨터에서도 돕니다. 대신 파일이 커집니다. 라이브러리를 고치면 그 코드를 복사해 간 프로그램을 다시 빌드해야 합니다.

쉽고 빠른 이해

정적 링크는 프로그램이 부를 라이브러리 코드를 빌드할 때 미리 실행 파일 안에 넣어 둡니다. 압축을 푸는 라이브러리를 쓴다면 그 코드가 내 프로그램 파일 안에 같이 들어갑니다.

이게 없으면 프로그램을 옮길 때마다 그것이 쓰는 라이브러리를 받는 컴퓨터에 먼저 깔아 둬야 합니다. 깔린 라이브러리의 판이 안 맞으면 프로그램은 켜지지도 않습니다.

어떻게 도나:

  1. 컴파일러가 소스 파일마다 코드 조각을 만듭니다. 남의 함수를 부르는 대목은 비워 둡니다
  2. 빌드 마지막에 링커라는 도구가 라이브러리에서 필요한 조각을 꺼내 그 조각들과 한 파일에 담습니다
  3. 비워 둔 곳을 그 파일 안에서 정해진 주소로 채워 실행 파일을 완성합니다

대가는 둘입니다. 파일이 커집니다. 라이브러리를 고칠 때마다 그 코드를 복사해 간 프로그램을 전부 다시 빌드해야 합니다.

그래서 실행 파일 하나만 옮겨 돌려야 할 때 고릅니다. 여러 프로그램이 같은 라이브러리를 나눠 쓰거나 라이브러리만 따로 고쳐야 할 때는 안 고릅니다.

상세

두꺼운 요리책에서 오늘 만들 두 가지 조리법만 종이 한 장에 옮겨 적습니다. 나머지 수십 가지는 그 종이에 없습니다. 종이 한 장만 들고 부엌에 서면 책을 다시 펼칠 일이 없습니다.

이 절은 그 복사가 언제 일어나고 무엇을 복사하는지를 따라갑니다. 목적 파일 두 개와 압축 라이브러리 하나를 이어 붙여 프로그램 하나를 만드는 경우를 예로 듭니다.

먼저 빌드의 마지막 단계에서 무슨 일이 벌어지는지 봅니다. 이어서 라이브러리에서 무엇만 골라 담는지 봅니다. 그렇게 만든 파일이 주는 이득과 대가를 짚습니다. 끝으로 어느 쪽을 고를지 가르는 기준을 봅니다.

코드를 복사해 넣는 빌드 마지막 단계

빌드의 마지막 단계를 맡는 도구가 링커입니다. 컴파일러가 소스 파일마다 따로 만들어 둔 코드 조각을 모아 프로그램 하나로 잇습니다. 이 도구가 없으면 여러 파일로 나눠 짠 프로그램은 서로의 함수를 못 찾습니다.

컴파일러가 소스 파일 하나로 만들어 내는 조각을 목적 파일이라고 합니다. 이 조각은 남의 함수를 부르는 대목을 비워 둡니다. 조각 하나만 봐서는 그 함수가 완성된 프로그램의 어느 주소에 놓일지 알 수 없습니다.

정적 링크는 이때 라이브러리 코드까지 실행 파일 안으로 복사해 넣고 비어 있던 주소를 채웁니다. 복사가 끝나면 원래의 라이브러리 파일은 더 필요 없습니다. 프로그램이 부를 코드가 실행 파일 한 덩이 안에 다 들어갔습니다.

flowchart TD
    subgraph 빌드에 들어가는 것
        O1["목적 파일 · 내가 짠 코드"]
        O2["목적 파일 · 내가 짠 코드"]
        L["압축 라이브러리 파일"]
    end
    O1 --> K["링커"]
    O2 --> K
    L -->|"필요한 조각만"| K
    K -->|"빈 주소를 채워"| E["실행 파일 · 부를 코드가 다 들어 있다"]

그림의 화살표가 모두 파일 하나로 모입니다. 이렇게 만든 실행 파일은 켤 때 바깥에서 가져올 것이 없습니다.

파일 안쪽을 보면 비워 둔 곳이 무엇으로 메워졌는지 드러납니다.

block-beta
columns 3
  A["목적 파일 · 내가 짠 코드"]:2 B["비운 자리"]
  C["실행 파일 · 내가 짠 코드"] D["채운 주소"] E["라이브러리에서 온 코드"]

위는 컴파일러가 만든 목적 파일입니다. 아래는 링크를 마친 실행 파일입니다. 비워 둔 곳에는 주소가 들어갔습니다. 라이브러리에서 온 코드는 그 뒤에 붙었습니다.

라이브러리에서 조각을 고르는 잣대

그림에서 링커로 들어간 라이브러리 파일을 정적 라이브러리라고 부릅니다. 목적 파일 여러 개를 꾸러미 하나로 묶어 둔 파일입니다. 링커는 그 꾸러미를 다 쓰지 않고 안에서 조각을 꺼내 씁니다.

꺼낼 조각을 고르는 잣대는 심볼입니다. 함수나 변수에 붙은 이름입니다.

링커는 이 이름으로 「어느 이름이 불리기만 하고 아직 실제 코드가 없나」를 살핍니다. 못 채운 이름이 남아 있으면 그 이름의 실제 코드를 가진 조각을 정적 라이브러리에서 꺼내 옵니다.

그래서 라이브러리가 아무리 커도 실행 파일에는 부르는 것만 들어갑니다. 압축 라이브러리에 함수가 백 개 있어도 두 개만 부른다면 나머지 아흔여덟 개는 복사되지 않습니다.

flowchart TD
    subgraph P["정적 라이브러리 꾸러미"]
        F1["조각 · 압축하는 함수"]
        F2["조각 · 푸는 함수"]
        R["나머지 조각 98개"]
    end
    F1 --> E["실행 파일"]
    F2 --> E

화살표가 없는 조각은 아무도 부르지 않은 조각입니다. 실행 파일로 넘어가지 않습니다.

끝까지 못 채운 이름이 남으면 링크는 실패합니다. 미정의 참조라고 부르는 오류입니다. 빌드는 거기서 멈춥니다. 어느 라이브러리를 빠뜨렸는지를 빌드할 때 알게 된다는 뜻입니다.

실행 파일 하나로 끝나는 배포

이렇게 만든 프로그램은 옮기기 쉽습니다. 파일 하나만 복사하면 됩니다. 받는 컴퓨터에 어떤 라이브러리가 깔려 있는지 미리 알지 않아도 됩니다.

코드를 미리 안 담으면 이 일이 배포할 때마다 숙제로 남습니다. 프로그램을 옮길 때마다 그것이 부르는 라이브러리를 받는 컴퓨터에 먼저 깔아야 합니다. 깔린 판이 안 맞으면 프로그램은 켜지지도 않습니다.

켤 때 할 일도 적습니다. 운영체제가 파일을 메모리에 올리면 프로그램의 첫 줄부터 돕니다. 라이브러리 파일을 찾아 붙이는 준비 단계가 없습니다.

동적 링크가 반대쪽 선택입니다. 라이브러리 코드를 복사하지 않습니다. 이름만 적어 두었다가 프로그램을 켤 때 바깥의 파일을 찾아 붙입니다. 이때 여러 프로그램에게 코드를 내주는 파일을 공유 라이브러리라고 부릅니다.

파일 크기와 다시 빌드라는 대가

복사한 만큼 파일이 커집니다. 같은 라이브러리를 쓰는 프로그램이 열 개면 같은 코드가 열 벌 디스크에 놓입니다. 그 프로그램들이 한꺼번에 떠 있으면 메모리에도 각각 올라갑니다.

flowchart TD
    subgraph 디스크
        P1["프로그램 A · 내 코드 + 라이브러리 사본"]
        P2["프로그램 B · 내 코드 + 라이브러리 사본"]
        P3["프로그램 C · 내 코드 + 라이브러리 사본"]
        R["나머지 프로그램 7개 · 같은 사본"]
    end

라이브러리를 한 벌만 두고 나눠 쓰는 길이 없습니다.

고치는 일도 번거로워집니다. 라이브러리에서 보안 구멍이 발견되면 그 코드를 복사해 간 프로그램을 전부 다시 빌드해 다시 배포해야 합니다. 어느 프로그램이 복사해 갔는지 찾아내는 일부터 해야 합니다.

정적과 동적을 가르는 두 질문

고르는 기준은 둘입니다. 받는 쪽 컴퓨터를 믿을 수 있나, 라이브러리를 나눠 쓸 일이 있나입니다.

이런 때 고르는 쪽
실행 파일 하나만 옮겨서 돌려야 한다 정적 링크
켜는 컴퓨터에 무엇이 깔렸는지 모른다 정적 링크
여러 프로그램이 같은 라이브러리를 나눠 쓴다 동적 링크
라이브러리를 프로그램과 따로 고쳐야 한다 동적 링크

표의 위 두 줄은 받는 쪽 컴퓨터를 못 믿는 경우입니다. 아래 두 줄은 코드를 나눠 쓰는 이득이 큰 경우입니다. 컨테이너 이미지처럼 프로그램과 필요한 파일을 한 덩이로 묶어 나르는 방식이 퍼졌습니다. 그러면서 정적 링크를 고르는 프로그램이 늘었습니다.

관련 항목

정적 링크가 다루는 파일

정적 라이브러리 · 목적 파일 · 실행 파일 · 공유 라이브러리 · 아카이브 파일 · ELF

정적 링크를 수행하는 도구

정적 링커 · 링커 · 컴파일러 · 어셈블러 · 빌드 시스템 · ar

링크할 때 읽고 고치는 구성 요소

심볼 · 심볼 테이블 · 재배치 · 섹션 · 진입점 · 스트립

링크 시점으로 갈리는 이웃 방식

동적 링크 · 지연 바인딩 · 링크 타임 최적화 · 동적 로딩 · 프리링킹

정적 링크가 실패할 때 나는 오류

미정의 참조 · 심볼 충돌 · 중복 정의

링크 방식을 고를 때 부딪히는 문제

ABI · 의존성 지옥 · 코드 비대화

정적 링크가 어울리는 배포 환경

컨테이너 · 임베디드 시스템 · 크로스 컴파일 · 단일 바이너리 · 부트로더

정적 링크를 기본으로 쓰는 언어

Go · Rust · C · C++ · Zig

다른 이름: static linking · static link · 정적 연결 · 빌드 타임 링크