Computer Science 기본 지식/컴퓨터 구조

[컴퓨터 구조] C++ 링커 동작 방식 확인

로파이 2021. 4. 17. 23:15

링커의 역할

 

Symbol Resolution

컴파일된 여러 모듈을 모아 각 목적 파일에서 해결되지 않은 심벌의 주소를 결정한다. 내부 목적 파일에서 참조되지 않는다면 외부 목적 파일에서 찾게 된다.

 

헤더 파일에서 정의된 변수는 컴파일 타임에 결정된다.

- 주소가 결정되기 때문에 링커의 심벌 해결이 필요가 없다.

// foo.h
int a = 10;
#include "foo.h"
int main()
{
	printf("%d\n", a); // foo.h의 a
	return 0;
}

 

목적 파일의 외부 링크가 사용되는 경우

1. 클래스의 static 멤버

- can.obj

// can.h
class Can
{
public:
	static int var;
};
#include "can.h"
// can.cpp
int Can::var = 10;

- man.obj

// man.h
void poo();
#include <cstdio>
#include "can.h"
#include "man.h"
// man.cpp
void poo()
{
	printf("%d\n", Can::var);
}

- main 프로그램

#include "man.h"
#include <iostream>

int main()
{
	poo();
	return 0;
}

poo 호출은 Can::var 전역 멤버 변수에 의존적이다. 해당 변수는 man.obj 파일에 미해결 된 심벌로 남아 있고 링커는 can.h 헤더 파일에 해당 변수의 선언을 찾고 목적 파일의 정의에서 정의된 변수가 있다면 해당 변수의 주소로 링크한다.

static 멤버 변수를 실행 파일에 정의하지 않으면 "확인할 수 없는 외부 기호" 링크 에러가 나는 이유이다.

 

2. 외부 함수

koo() 함수는 can.h에 정의된 doo 함수에 의존적이다. 해당 함수를 호출할 때 분기하는 주소가 링커에 의해 can.obj에서 참조되어 해결된다.  

#include <cstdio>
#include "can.h"
#include "man.h"
// man.cpp
void poo()
{
	printf("%d\n", Can::var);
}
void koo()
{
	doo(); // can.h에 정의된 함수
}

 

3. 외부 기호로 명시된 전역 변수 extern modifier

- can.cpp에 정의된 g_far

#include <cstdio>
#include "can.h"

float g_far = 1.0f;
int Can::var = 10;

void doo()
{
}

 

- man.cpp에서 g_far를 extern 외부 전역 변수로 선언

g_far은 can.obj로부터 참조된다.

#include <cstdio>
#include "can.h"
#include "man.h"

extern float g_far;

void poo()
{
	printf("%d\n", Can::var);
}

void koo()
{
	doo();
}

void woo()
{
	printf("%f\n", g_far);
}

 

링커의 외부 기호 충돌 (두 번 이상 선언)

- can.cpp

#include <cstdio>
#include "can.h"

int g_var = 10;

- man.cpp

#include <cstdio>
#include "can.h"
#include "man.h"

int g_var = 10;

can.obj가 먼저 어셈블 되었다면 man.obj를 생성할 때 이미 정의된 기호로 링크 에러가 발생한다.

 

파일 내로 좁히는 static modifier 

#include <cstdio>
#include "can.h"

static int g_var = 10;

can.obj에 data 영역에 정의된 g_var은 이제 외부 링크로 참조되지 않는다. (내부 참조로만 사용된다.)

- extern으로 외부 기호를 찾으려고 하면 링크 에러가 발생한다.

#include <cstdio>
#include "can.h"
#include "man.h"

extern int g_var; // 정의되지 않은 기호 (링크 에러)

- 함수에도 적용 가능하다.

주로 한 헤더 파일에서 함수를 선언 및 정의하고 다른 외부 파일에서 해당 함수를 참조할 때 주로 사용한다.

- math.h

#pragma once
#include <cmath>

static int add(int a, int b)
{
	return a + b;
}

static int sub(int a, int b)
{
	return a - b;
}

- man.cpp

#include <cstdio>
#include "can.h"
#include "man.h"
#include "math.h"

void cal_man()
{
	int c = add(10, 5);
}

- can.cpp

#include <cstdio>
#include "can.h"
#include "math.h"

void cal_can()
{
	int c = add(1, 2);
}

math.h 헤더에 해당 함수가 static으로 정의하지 않는다면 다른 파일에서 두 번 이상 함수가 정의되기 때문에 두 번 이상의 정의된 링크 에러가 발생한다. 만약 static을 쓰고 싶지 않다면, cpp에 해당 함수를 정의한다.

 

extern 유의 사항

extern 외부 변수를 사용할 때는 초기화하여 사용하면 안 된다.

초기화할 시 해당 파일에서 새로 정의하는 변수로 취급한다.

can.cpp

#include <cstdio>
#include "can.h"

static int g_var = 0;
float g_far = 1.0f;

man.cpp

#include <cstdio>
#include "can.h"
#include "man.h"

extern int g_var = 5; // g_var는 man.cpp에 새로 정의한 변수
extern float g_far = 10.f; // g_far은 man.cpp에 새로 정의한 변수
                          // can.obj에 이미 정의되어 있으므로 링크 에러