프로그램을 실행하면, 우리는 흔히 하나의 실행 파일이 모든 일을 처리한다고 생각한다.
하지만 실제로는 실행 파일 하나만으로 동작하는 프로그램은 거의 없다.
대부분의 프로그램은 실행 시점에 외부 코드의 도움을 받아 완성된다.
이 글에서는 동적 라이브러리, 동적 연결, 그리고 플러그인 실행이
어떤 하나의 흐름으로 이어지는지, “프로그램은 언제 완성되는가”라는 질문을 중심으로 정리해본다.
정적 링크와 동적 링크는 무엇이 다를까
먼저 대비되는 개념부터 짚고 가야 한다.
정적 링크는 필요한 코드가 모두 실행 파일 안에 포함된 방식이다.
컴파일과 링크가 끝나는 순간, 프로그램은 이미 완성되어 있다.
반면 동적 링크는 다르다.
실행 파일은 “이 함수가 필요하다”는 정보만 알고 있을 뿐,
실제 구현 코드는 파일 바깥에 남겨둔다.
그리고 그 코드는 프로그램이 실행되는 순간에야 연결된다.
이 차이 하나가 이후 모든 이야기를 결정한다.
정적 링크는 단순하지만 무겁고,
동적 링크는 가볍지만 실행 중에 많은 일이 벌어진다.
동적 라이브러리는 왜 실행 파일 밖에 있을까
동적 라이브러리는 말 그대로 실행 중에 불러오는 코드 덩어리다.
운영체제는 이 라이브러리를 메모리에 올리고,
실행 파일이 그 안의 함수들을 사용할 수 있게 연결해준다.
이 방식을 쓰는 이유는 명확하다.
여러 프로그램이 같은 라이브러리를 공유할 수 있기 때문이다.
예를 들어 문자열 처리, 그래픽, 네트워크 같은 공통 기능은
각 실행 파일마다 복사해 넣을 필요가 없다.
그 결과, 디스크 사용량은 줄어들고
메모리에서도 동일한 코드 영역을 여러 프로세스가 함께 쓸 수 있다.
운영체제 입장에서는 매우 효율적인 구조다.
동적 연결은 정확히 언제 일어날까
여기서 중요한 질문이 하나 생긴다.
“동적 연결은 실행의 어느 시점에 일어날까.”
답은 두 단계로 나뉜다.
첫 번째는 프로그램 시작 시점이다.
실행 파일이 로드될 때, 운영체제의 로더는
이 프로그램이 필요로 하는 동적 라이브러리 목록을 확인한다.
두 번째는 실제 함수 호출 시점이다.
일부 함수는 처음 호출되는 순간에야 주소가 확정된다.
이 방식을 지연 바인딩이라고 부른다.
즉, 동적 연결은 단 한 번에 끝나는 작업이 아니다.
프로그램이 실행되는 동안,
필요에 따라 계속해서 “연결”이라는 작업이 개입한다.
실행 파일은 사실 설계도에 가깝다
이쯤 되면 실행 파일의 정체가 조금 달라 보이기 시작한다.
실행 파일은 완성품이라기보다 설계도에 가깝다.
여기에는 전체 로직의 뼈대와,
외부에서 가져와야 할 기능의 목록만 담겨 있다.
실제 동작하는 프로그램은
실행 파일 + 여러 개의 동적 라이브러리가 결합된 결과물이다.
그리고 이 결합은 실행 중에 이루어진다.
이 관점은 플러그인 구조를 이해하는 데 중요한 발판이 된다.
플러그인은 동적 연결의 극단적인 형태다
플러그인은 동적 라이브러리 개념을 한 단계 더 밀어붙인 구조다.
동적 라이브러리는 “항상 필요할 수도 있는 코드”라면,
플러그인은 “있을 수도, 없을 수도 있는 코드”다.
프로그램은 미리 정해진 인터페이스만 알고 있다.
그리고 실행 중에 특정 디렉토리를 탐색하거나,
설정 파일을 읽어 플러그인을 동적으로 로드한다.
중요한 점은,
플러그인의 존재 여부가 프로그램 실행 시점에 결정된다는 것이다.
컴파일 시점에는 어떤 기능이 추가될지 알 수 없다.
이로 인해 프로그램은 훨씬 유연해진다.
기능 추가를 위해 전체 프로그램을 다시 빌드할 필요가 없다.
단지 새로운 플러그인 파일을 배포하면 된다.
플러그인 실행의 핵심은 계약이다
플러그인 구조의 본질은 기술이 아니라 약속이다.
프로그램과 플러그인은
“이 함수는 이렇게 생겼다”는 계약을 공유한다.
이 계약은 보통 인터페이스나 함수 시그니처 형태로 정의된다.
프로그램은 이 계약만 믿고 플러그인을 호출한다.
실제 구현이 무엇인지는 전혀 신경 쓰지 않는다.
그래서 플러그인 시스템에서는
제어 흐름이 한 단계 뒤집힌다.
프로그램이 모든 로직을 직접 소유하지 않고,
외부 코드에 실행을 위임하는 구조가 된다.
예시 상황: 이미지 편집 프로그램과 필터 플러그인
이미지 편집 프로그램이 하나 있다고 가정해보자.
이 프로그램은 사진에 필터를 적용하는 기능을 제공하고 싶다.
하지만 문제는 이거다.
필터 종류가 계속 늘어난다.
흑백, 세피아, 블러, 샤픈, AI 스타일 필터까지 끝이 없다.
모든 필터를 프로그램 안에 직접 넣으면 어떻게 될까.
- 필터 추가할 때마다 프로그램 수정
- 다시 빌드
- 다시 배포
그래서 플러그인 구조를 선택한다.
1. “계약”이 먼저 만들어진다
프로그램은 이렇게 생각한다.
“필터가 뭔지는 모르겠지만
이렇게만 동작해주면 나는 쓸 수 있다.”
그래서 딱 하나의 계약을 정의한다.
조금 더 구체적으로 말하면,
- 이 함수 이름을 가져야 하고
- 이 타입의 이미지를 받아야 하며
- 처리된 이미지를 반환해야 한다
이게 바로 계약이다.
중요한 점은
👉 어떤 필터인지, 내부에서 뭘 하는지는 전혀 관심 없다는 것이다.
2. 프로그램이 “믿고” 호출한다는 뜻
이제 프로그램 코드는 이렇게 생긴다.
여기서 프로그램은 이런 생각을 하지 않는다.
- 흑백인가?
- 세피아인가?
- GPU를 쓰는가?
- AI 모델을 돌리는가?
오직 하나만 믿는다.
“apply라는 함수가 있고
이미지를 넣으면 이미지가 나온다.”
이게
**“프로그램은 이 계약만 믿고 플러그인을 호출한다”**라는 말의 정확한 의미다.
3. 구현을 신경 쓰지 않는다는 말의 진짜 뜻
플러그인 개발자는 완전히 자유롭다.
- 어떤 알고리즘을 쓰든
- 내부에 어떤 라이브러리를 쓰든
- 성능이 느리든 빠르든
상관없다.
왜냐하면 프로그램은 구현을 전혀 들여다보지 않기 때문이다.
오직 계약만 맞으면 된다.
그래서 다음이 가능해진다.
- 오늘은 흑백 필터
- 내일은 AI 스타일 필터
- 모레는 외부 회사가 만든 필터
프로그램 코드는 단 한 줄도 바뀌지 않는다.
동적 구조가 가져오는 대가
물론 동적 라이브러리와 플러그인은 장점만 있는 것은 아니다.
실행 시점에 문제가 드러난다는 치명적인 단점이 있다.
라이브러리가 없거나,
버전이 맞지 않거나,
계약이 깨지면 프로그램은 실행 중에 실패한다.
정적 링크에서는 컴파일 단계에서 잡혔을 오류가
런타임 오류로 밀려난다.
그래서 동적 구조를 쓸수록
명확한 인터페이스 정의와 버전 관리가 중요해진다.
유연함은 책임을 함께 요구한다.
결론: 프로그램은 실행 중에 완성된다
동적 라이브러리, 동적 연결, 플러그인 실행 원리는
서로 다른 개념처럼 보이지만 하나의 문장으로 정리할 수 있다.
프로그램은 실행 파일 하나로 완성되지 않는다.
실행 중에 필요한 코드들이 결합되며 점진적으로 완성된다.
이 관점을 이해하면
운영체제의 로더, 런타임 에러, 플러그인 아키텍처가
하나의 흐름으로 연결된다.
그리고 복잡해 보이던 시스템 설계가
“언제, 무엇을, 어떻게 연결할 것인가”라는 질문으로 단순해진다.
실제 기술 면접에서는 CS 지식을 말로 설명할 수 있어야 합니다.
저만의 노하우를 담아 CS 지식을 면접에서 말로 풀어낼 수 있도록 돕는 강의를 제작했습니다.
무료 강의와 함께 부담없이 시작해보세요~
👉 [무료 강의 보러 가기](https://inf.run/SgjyS)
댓글