언어/C&C++ 기본

[C++] Linker

차가운오미자 2021. 6. 14. 18:34

링커의 역할

: 심볼들과 함수들이 어디에 있는지 찾고 이들을 이어준다. (find where the symbols and functions are and link them together)

 

In visual studio,

Ctrl + F7 : compile only

Ctrl + F5 : compile and link (Build)

 

- compiling error example: syntax error

- linking error example: missing entry point

 

* 에러 메시지가 c로 시작하면 컴파일 에러이고 LNK로 시작하면 링킹 에러임.

 

Linking Error 예시

예를 들어서, a.cpp 파일에 Log()란 함수를 declare 하고 definition은 b.cpp에 했다고 하자.

// a.cpp
void Log(const char* mes);
// end of a.cpp

//b.cpp 
void Log(const char *mes){
    std::cout << mes << std::endl;
} // 이 함수의 이름을 Logr로 바꾸면 linking error가 뜬다.

int main(){
    Log("Hello"); // 주석 처리해도 에러 발생
}

 

만약에 b.cpp파일에 Log()함수의 이름을 Logr()로 바꾸면 F7(compile olny)를 했을 땐 문제가 되지 않는다.

a.cpp파일에 Log()란 함수가 어딘가에 있다고 해줬으니, 컴파일을 하는 과정에서는 이 함수가 실제로 어디에 정의되어 있는지는 신경쓰지 않는다. 하지만 F5를 눌러서 빌드를 해보면, 링킹 에러가 뜬다 (unresolved symbol).

 

이 상태에서 그럼 main()에서 Log()함수를 call하지 않도록 해보아도, 즉 과정에서 문제가 되는 함수를 사용하지 않더라도 링킹 에러가 뜬다. 나는 안쓸거니까 그거까지 링커가 신경쓰지 않게 하고 싶다? 그럼 static을 사용해보자.

 

함수에 static을 붙이면 이 translation unit에서만 사용하기 위해 정의된 것이라고 선언하는 것이다.

 

그러면 문제가 생기지 않는다.

 

링커가 정의된 함수의 body를 찾아서 이어주려면,

1) 함수명이 같아야 하고,

2) 리턴 타입도 같아야 하고,

3) parameter 들도 동일해야 하고

4) 중복 정의되지 않아야 한다.

 

CASE 1

main.h 파일:

#include <iostream>
#include "log.h" // 중복되는 부분

void Log(const char *message){
    cout << message << endl;
}

void Add(int a, int b){
    Log("processing Add")
    return a+b;
}

int main(){
    Add(5, 10);
}

log.h 파일:

#include <iostream>

void Log(const char* message){
    std::cout << message << std::endl;
}

 

이렇게 두 개의 파일이 있다고 했을 떄, 만약 main.cpp 파일에 log.h 을 include하게 되면, Log()함수가 두 개나 있어서 링커가 헷갈려하게 된다.

 

이걸 해결하기 위해서 log.h 파일에 Log()함수를 static으로 바꿔주면, 이 Log()함수는 internally 사용된다고 생각하기 떄문에 링커가 에러로 감지하지 않게 된다. 오직 이 obj파일에서만 인식 가능하다? 이런 식으로 이해

 

#include <iostream>

void static Log(const char* message){ // 여기!!!!
    std::cout << message << std::endl;
}

또 다른 방법은 inline을 선언하는 것이다.

#include <iostream>

void inline Log(const char* message){ //여기!!!!
    std::cout << message << std::endl;
}

 

CASE 2.

 

log.h

#include <iostream>
#pragma once

void Log(const char* message) {
	std::cout << message << std::endl;
}

Log.cpp

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

void initLog() {
	Log("log init!!");
}

Main.cpp

#include <iostream>
#include "log.h" // 중복되는 부분

using namespace std;

void Log(const char* message);

int static Add(int a, int b) {
    Log("processing Add");
    return (a + b);
}

int main() {
    Add(5, 10);
}

이렇게 해놓고 컴파일만 하면 에러가 뜨지 않는다. 하지만 빌드를 해보면 링킹 에러가 뜨는 것을 확인할 수 있다.

 

 

Log 함수가 중복정의되었다고 에러가 뜨는데, 이는 두 개의 translation unit에 Log 함수가 정의되었기 떄문이다. 즉, Log.cpp 파일에 log.h를 한 번 include했고, Main.cpp에도 log.h 를 include 했기 떄문이다.

 

이를 해결하기 위해서는 두 가지 방법이 있다.

1. Log() 를 세 번째 translation unit으로 만들어버린다. (ex. log.h 에 있는 Log()함수 정의에 inline을 추가)

2. Log()를 두 개의 translation unit 중 하나에만 들어가도록 한다. -> 좋은 방법!!!

 

두 번째 방법을 이용한다는 것은, log.h 에는 선언만 해주고서, log.cpp 나 main.cpp 파일에 body를 정의해준다는 것이다. 이렇게 하면 하나의 translation unit에만 정의되니까 에러가 없어진다!

 

깔끔하게 코드를 보자면:

log.h

#include <iostream>
#pragma once

void Log(const char* mes);

Log.cpp

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

void initLog() {
	Log("log init!!");
}

void Log(const char* message) {
	std::cout << message << std::endl;
}

Main.cpp

#include <iostream>
#include "log.h" // 중복되는 부분

using namespace std;

void Log(const char* message);

int static Add(int a, int b) {
    Log("processing Add");
    return (a + b);
}

int main() {
    Add(5, 10);
}

이렇게 하면 문제 없이 빌드에 성공할 수 있다.

'언어 > C&C++ 기본' 카테고리의 다른 글

[C++] Class  (0) 2021.06.25
[C++] Header Files  (0) 2021.06.14
[C++] Variables, Functions, Pointers, References  (0) 2021.06.14
[C++] Compiler  (0) 2021.06.14
[C++] How C++ Works  (0) 2021.06.14