👉 링커(Linker)란?
- 링커(Linker)는 소프트웨어 개발 과정에서 여러 개의 프로그램 조각(코드나 데이터)을 하나로 묶어 실행 가능한 프로그램으로 만드는 도구
- 프로그래머가 작성한 소스 코드는 컴파일러에 의해 개별적인 기계어 파일(객체 파일, Object File)로 변환됨
- 이 파일들에는 함수나 변수 등의 코드 조각들이 들어 있지만, 서로 연결되어 있지 않아서 단독으로 실행할 수 없음
- 링커는 여러 객체 파일과 라이브러리를 가져와 하나의 실행 파일로 합침
- 이 과정에서 링커는 각 코드 조각이 참조하는 다른 조각(예 : 함수 호출, 변수 접근)을 연결함
- 링커가 완료되면 실행 가능한 파일(예 : Windows의
.exe, Linux의 실행 파일)이 생성됨
- 이 파일은 독립적으로 실행할 준비가 된 상태가 됨
- 블록 놀이로 비유할 수 있음
- 각각의 블록은 프로그램의 작은 조각(예 : 함수나 변수)
- 블록들을 조립해서 하나의 완성된 구조물을 만드는 것이 바로 링커의 역할
👉 링커의 역할
- 심벌 해석
- 심벌(Symbol ) : 프로그램에서 사용되는 함수나 변수의 이름을 의미
printf, main, myFunction 같은 이름이 심벌
- 컴파일러가 코드를 컴파일할 때, 심벌을 사용하지만 해당 심벌의 정확한 위치를 모름
- 여기서 링커는 참조된 심벌 찾고, 심벌을 연결
- 참조된 심벌 찾기
- 링커는 각 객체 파일에서 정의된 심벌과 참조된 심벌을 분석
- 정의된 심벌 : 코드에서 실제로 구현된 함수나 변수
- 참조된 심벌 : 다른 객체 파일이나 라이브러리에 있는 함수나 변수를 사용하는 경우
- 심벌 연결
- 참조된 심벌이 어디에 정의되어 있는지 찾아서 서로 연결
- 예를 들어,
main.o에서 printf를 호출하면 링커는 이를 printf가 정의된 라이브러리 파일로 연결
- 프로그램을 빌드할 때 사용하는 라이브러리를 처리
- 정적 라이브러리(Static Library)
- 정적 라이브러리는 .a(Linux)나 .lib(Windows) 파일 형식으로 제공
- 링커가 정적 라이브러리를 사용하면, 라이브러리의 필요한 코드가 프로그램에 복사되어 포함됨
- 예를 들어,
libmath.a에서 sqrt 함수를 사용하면, sqrt 함수의 기계어 코드가 실행 파일에 포함
- 특징
- 실행 파일이 커질 수 있음(라이브러리 코드가 복사되기 때문)
- 실행 파일이 독립적으로 동작(외부 라이브러리 파일이 필요 없음)
- 실행 시 추가적인 라이브러리 로드 작업이 없음
- 동적 라이브러리(Dynamic Library)
- 동적 라이브러리는
.so(Linux)나 .dll(Windows) 파일 형식으로 제공
- 링커는 실행 파일에 라이브러리를 직접 포함하지 않고, 라이브러리의 주소를 참조만 함
- 실제 라이브러리 코드는 실행 시에 프로그램이 메모리에 로드될 때 연결됨
- 특징
- 실행 파일 크기가 작아짐(코드가 복사되지 않음)
- 여러 프로그램이 동일한 동적 라이브러리를 공유할 수 있어 메모리를 효율적으로 사용
- 하지만, 실행 파일이 라이브러리에 의존하기 때문에, 실행 환경에서 해당 라이브러리가 없으면 실행되지 않을 수 있음
- 프로그램의 심벌(함수와 변수)의 실제 메모리 주소를 결정
- 정적 링킹(Static Linking)
- 정적 링킹은 컴파일 시간에 심벌의 실제 메모리 주소를 결정
- 링커는 각 심벌의 고정된 주소를 할당하고, 이를 실행 파일에 포함
- 실행 파일을 다른 시스템에서 실행해도 심벌 주소는 변하지 않음
- 예 : 함수 호출 시,
printf 함수의 위치(주소)가 실행 파일에 고정
- 동적 링킹(Dynamic Linking)
- 동적 링킹은 프로그램이 실행될 때, 심벌의 주소를 동적으로 결정
- 프로그램이 실행되면 운영 체제가 동적 라이브러리를 메모리에 로드하고, 링커는 이를 기반으로 심벌의 실제 주소를 연결
- 예 : 실행 시
printf가 정의된 라이브러리가 로드되고, 링커가 해당 라이브러리의 주소를 연결
- 링커가 심벌 주소를 결정하는 과정
- Relocation (재배치)
- 컴파일 단계에서는 심벌이 정확히 어느 메모리 주소에 위치할지 알 수 없음
- 링커는 다음과 같은 과정을 거침
- 절대 주소 확인 : 각 심벌이 메모리의 어느 위치에 위치할지 계산
- 재배치 정보 처리 : 컴파일된 코드에 포함된 상대 주소를 절대 주소로 변환
- 테이블 작성 : 함수와 변수의 심벌 테이블을 만들어 실행 중에 참조할 수 있도록 준비
👉 링커의 작동 흐름
👉 예시
#include <stdio.h>
void greet();
int main() {
greet();
return 0;
}
#include <stdio.h>
void greet() {
printf("Hello, World!\n");
}
- 컴파일 단계
main.c와 greet.c를 각각 컴파일 → main.o와 greet.o 생성
main.o는 greet()의 심벌만 참조하고, 실제 구현 위치를 모름.
greet.o는 printf()를 참조하지만, 실제 구현은 표준 라이브러리(libc.so)에 있음
- 링킹 과정
- 링커가
main.o, greet.o, 표준 라이브러리를 결합
greet()가 greet.o에 있다는 것을 확인하고 연결
printf()가 표준 라이브러리(libc.so)에 있다는 것을 확인하고 동적 라이브러리를 참조