본문 바로가기
컴퓨터 활용/윈도우 활용

[스크랩] 링크 에러에 대한 고찰

by 호군 2010. 12. 29.
반응형
링크 : http://minjang.egloos.com/2146607



요즘 한가하게 블로그에 글을 쓸 시간이 없음에도 불구, 머리 좀 식힐 겸 생각했던 이야기 하나.

옛날 옛적에

C언어는 대학교 입학해서 처음으로 배웠다. 나는 컴퓨터 공학과나 전산학과로 입학한 사람이 아니어서 그냥 혼자서 야매로 공부를 했다. Borland C/C++ 를 좀 만지기도 했으나 주로 Visual C++ 위에서 열심히 코딩 했다. 컴파일 에러는 그럭저럭 C 책 뒤져 가며 잡을 수 있었다. 그런데 나를 정말로 좌절케 했던 것은 바로 LNK2001 에러. VC++ 쓰는 사람이라면 이 에러로 고생 안 해 본 사람은 없을 것이다.

요즘은 고교 과정에서 수준 높은 영어 단어를 가르치는 지는 모르겠으나, 대학교 막 입학한 나에게 ‘unresolved’라는 단어는 매우 생소했다. 사전을 찾아보니 ‘미해결된’ 이라는 뜻이었다. External, 이건 외부라는 뜻이고, symbol 은 상징/기호라는 뜻이니까 그렇다면 “미해결된 외부 기호”. 도대체 이게 무슨 뜻?? 게다가 이 에러가 어디에서 일어났는지도 알 수가 없었다. 내가 작성하고 있던 파일은 main.c 였는데 갑자기 .obj라는 파일은 뭐야? 도대체 “Linking…”은 또 무슨 소릴까? 어렸을 때 MS-DOS 디렉토리에서 LINK.EXE라는 프로그램은 본 것 같았는데.

그런데 LNK2001 에러와 쌍벽을 이루는 에러가 또 있나니 그 이름은 LNK2005.

LNK2001과 LNK2005의 콤보 어택은 수 많은 초보 C/C++ 프로그래머들을 당혹하게 만든 주범이다. 태어나서 GW-BASIC, QBasic만 만져 본 나로서는 이해하기 참 힘든 에러였다.

 

초보자를 위한 링크 에러 설명

초보자를 위해 링크 에러에 대해 최대한 간략히 설명하면: C/C++ 컴파일러는 소스 파일 하나를 독립적으로 ‘컴파일’한다. 그런데 컴파일할 때는 함수 원형 선언만 있고 구현이 없어도 문제 없이 컴파일을 할 수 있다. 단순히 함수 원형의 리턴 및 파라미터 타입만 맞으면 아무런 문제 없이 컴파일은 진행된다. 이렇게 개별 파일의 컴파일 작업이 끝나면 최종 실행 파일을 만들기 위해 모으는 작업, 즉 ‘링킹’을 한다. 이 때 하는 일이 ‘심볼’들을 찾아 연결하는 작업이다. 심볼이라는 단어는 컴파일러의 관점에서 나온 단어다. 컴파일러는 함수 및 변수 같은 것을 ‘심볼 테이블’로 관리하기 때문에 나온 말이다.

링크 과정에서는 각각의 소스 파일이 사용한 ‘심볼’을 해결해야 한다. 쉽게 말하면 사용하고 있는 함수의 실제 몸통을 어디선가 찾아야만 한다. 만약 이 몸통을 못 찾거나 두 개 이상 찾으면 링킹은 실패하고 각각 VC++에서 LNK2001, LNK2005에러가 발생한다. printf 같은 경우는 기본 라이브러리에 이미 그 구현이 있기 때문에 그냥 알아서 되는 것 뿐이다.

위의 경우에는 foo() 라는 함수에 대한 구현이 어떠한 소스 파일에서도 찾을 수가 없으면 LNK2001 에러가 나고, 두 개 이상 파일에서 foo() 함수 구현을 찾으면 LNK2005 에러가 된다.

 

링크 에러의 HCI 적인 고찰

왜 이 링크 에러를 어렵게 느낄까? 한번 사용자 경험 측면에서 이야기 해보자. 일반적인 컴파일러 에러의 형태는 컴파일러 종류에 상관없이 매우 비슷하다.

너무나 당연히 “어느 소스 파일의 어느 라인에서” 이라는 정보가 들어가 있다. 그래서 main.c 파일의 6번째 줄로 가면 이 에러를 잡을 수 있다라는 것을 알려 준다. 그런데 링크 에러는? 맨 위 그림을 보면 “어디에서”에 해당하는 정보가 없다. VC++ 컴파일러가 멍청해서 그런 것일까? 아니다. 대부분이 다 그러하다.

gcc 메세지는 더 무시무시하다. 희한한 이름의 .o 파일에서 문제가 있다고 한다. test.c 라는 파일은 보이는데 “.text+0xa”라는 또 알 수 없는 말이 있다. 링크 에러가 컴파일 에러에 비해 매우 어렵게 느껴지는 것은 이렇게 에러가 어디서 났는지를 쉽게 알려 주지 않기 때문이다. 만약에 컴파일러가

어떤 소스 파일의 몇 번째 줄에서 부른 이 함수의 구현 내용을 찾지 못했습니다.

라고 이야기 해주면 어떨까? 단언컨데 훨씬 링크에러를 쉽게 이해할 수 있을 것이다.

test.c:6: link error: cannot find the definition of the function ‘foo’. Where is the body?

test.c:6: 링크 에러: 함수 ‘foo’의 정의가 없어. 어디다 뒀냐? 라이브러리 링크는 제대로 했냐?

대충 이 정도만 되어도 훨씬 쉬울 것이다. VC++나 기타 에디터의 도움이 있다면 바로 링크 에러를 만든 소스 파일 위치로 점프할 수 있다. 컴파일러 에러는 점점 친절하게 진화하고 있다. 그러나 링크 에러 메세지는 어렵다. 초보자 뿐만 아니라 경험 많은 C/C++ 프로그래머도 링크 에러가 나오면 쉽지 않다.

 

그래도 링크 에러는 어렵다

혼자 만든 프로그램에서 겪는 링크 에러는 쉽게 잡을 수 있다. 그러나 규모가 큰 소프트웨어를 개발할 때, 특히 다른 사람들이 만든 라이브러리를 써야 할 때 겪는 링크 에러는 매우 어렵다.

FileZilla FTP 클라이언트 소스를 받아서 수정을 좀 하려고 했다. 다운로드 받아 보니 친절하게도 VC++ 솔루션 파일이 있었다. 열어서 컴파일해봤다. 컴파일 에러가 무수히 나온다. 필요한 wxWidgets도 깔아주고 컴파일러 에러를 다 잡았다. 이제 링크 에러가 수 천 개 뜬다… 이 쯤이야 하면서 잡아간다. 많이 잡았다. 그런데 열 몇 개 정도는 끝내 못 잡고 말았다. 각종 외부 라이브러리를 많이 쓰다 보니 링킹이 너무 어려운 것이었다. 결국 알고 보니 VC++에서는 링킹이 안 된다는 것이었다. 거기서 시키는 대로 mingw를 깔고 겨우 해결할 수 있었다. 그것도 조금만 라이브러리 버전 등을 실수해도 수 많은 링크 에러를 만나게 된다.

C와 C++ 언어 사이에서도 extern “C” 같은 것을 모르면 링크 에러로 고생한다. extern “C”나 C++ 함수의 name mangling에 대한 자세한 설명은 귀찮아서 생략한다. C++ 클래스 멤버 함수에서 링크 에러가 발생하면 괴상한 문자들로 가득 찬 함수 이름에 좌절한다. 함수 호출 규약(calling convention)도 신경 써야 한다.

링크 에러는 파일 질라 같이 여러 라이브러리를 가져다 쓸 때 특히 어렵다. DLL과 static library가 섞여 있을 때도 어렵다. 모든 외부 라이브러리를 일관되게 링크를 시켜야 한다.  가장 좋은 예는 Codejock의 GUI 툴킷에서 찾아볼 수 있다. 이 툴킷을 static library 형태로 쓸 때 특히 문제다. 이 라이브러리에서도 분명 printf와 같은 기본 C/C++ 함수를 부를 것이다. MFC 같은 C++ 라이브러리도 쓴다. 그래서 내가 만드는 프로그램에 같이 포함시킬 때 주의를 기울여야 한다. 어떤 녀석은 기본 C/C++ 라이브러리를 DLL로 쓰도록 했고, 어떤 녀석은 그러하지 않다면 여지 없이 LNK2001 혹은 LNK2009 폭탄을 맞는다.

대표적으로 위 그림과 같은 런타임 라이브러리 사용을 어떻게 할 것인가를 잘 맞춰야 한다. 때에 따라서는 별도로 libc.lib, msvcrt.lib와 같은 기본 라이브러리가 자동으로 링크되지 않도록 해야 한다. 정말 겪어 보지 않고서는 모르는 문제다.

이런 걸 보면 명시적인 링킹 과정이 없어 보이는 C#이나 Java는 참으로 편리한 언어인 것 같다. 그래도 적어도 링크 에러 메세지를 조금만 가다듬어도 “체감 난이도”는 훨씬 줄어들 것이다.

반응형