요즘 한가하게 블로그에 글을 쓸 시간이 없음에도 불구, 머리 좀 식힐 겸 생각했던 이야기 하나.
옛날 옛적에
C언어는 대학교 입학해서 처음으로 배웠다. 나는 컴퓨터 공학과나 전산학과로 입학한 사람이 아니어서 그냥 혼자서 야매로 공부를 했다. Borland C/C++ 를 좀 만지기도 했으나 주로 Visual C++ 위에서 열심히 코딩 했다. 컴파일 에러는 그럭저럭 C 책 뒤져 가며 잡을 수 있었다. 그런데 나를 정말로 좌절케 했던 것은 바로 LNK2001 에러. VC++ 쓰는 사람이라면 이 에러로 고생 안 해 본 사람은 없을 것이다.
![](http://pds10.egloos.com/pds/200811/22/29/d0033129_49280884d759d.png)
요즘은 고교 과정에서 수준 높은 영어 단어를 가르치는 지는 모르겠으나, 대학교 막 입학한 나에게 ‘unresolved’라는 단어는 매우 생소했다. 사전을 찾아보니 ‘미해결된’ 이라는 뜻이었다. External, 이건 외부라는 뜻이고, symbol 은 상징/기호라는 뜻이니까 그렇다면 “미해결된 외부 기호”. 도대체 이게 무슨 뜻?? 게다가 이 에러가 어디에서 일어났는지도 알 수가 없었다. 내가 작성하고 있던 파일은 main.c 였는데 갑자기 .obj라는 파일은 뭐야? 도대체 “Linking…”은 또 무슨 소릴까? 어렸을 때 MS-DOS 디렉토리에서 LINK.EXE라는 프로그램은 본 것 같았는데.
그런데 LNK2001 에러와 쌍벽을 이루는 에러가 또 있나니 그 이름은 LNK2005.
![](http://pds12.egloos.com/pds/200811/22/29/d0033129_492808886df42.png)
LNK2001과 LNK2005의 콤보 어택은 수 많은 초보 C/C++ 프로그래머들을 당혹하게 만든 주범이다. 태어나서 GW-BASIC, QBasic만 만져 본 나로서는 이해하기 참 힘든 에러였다.
초보자를 위한 링크 에러 설명
초보자를 위해 링크 에러에 대해 최대한 간략히 설명하면: C/C++ 컴파일러는 소스 파일 하나를 독립적으로 ‘컴파일’한다. 그런데 컴파일할 때는 함수 원형 선언만 있고 구현이 없어도 문제 없이 컴파일을 할 수 있다. 단순히 함수 원형의 리턴 및 파라미터 타입만 맞으면 아무런 문제 없이 컴파일은 진행된다. 이렇게 개별 파일의 컴파일 작업이 끝나면 최종 실행 파일을 만들기 위해 모으는 작업, 즉 ‘링킹’을 한다. 이 때 하는 일이 ‘심볼’들을 찾아 연결하는 작업이다. 심볼이라는 단어는 컴파일러의 관점에서 나온 단어다. 컴파일러는 함수 및 변수 같은 것을 ‘심볼 테이블’로 관리하기 때문에 나온 말이다.
링크 과정에서는 각각의 소스 파일이 사용한 ‘심볼’을 해결해야 한다. 쉽게 말하면 사용하고 있는 함수의 실제 몸통을 어디선가 찾아야만 한다. 만약 이 몸통을 못 찾거나 두 개 이상 찾으면 링킹은 실패하고 각각 VC++에서 LNK2001, LNK2005에러가 발생한다. printf 같은 경우는 기본 라이브러리에 이미 그 구현이 있기 때문에 그냥 알아서 되는 것 뿐이다.
위의 경우에는 foo() 라는 함수에 대한 구현이 어떠한 소스 파일에서도 찾을 수가 없으면 LNK2001 에러가 나고, 두 개 이상 파일에서 foo() 함수 구현을 찾으면 LNK2005 에러가 된다.
링크 에러의 HCI 적인 고찰
왜 이 링크 에러를 어렵게 느낄까? 한번 사용자 경험 측면에서 이야기 해보자. 일반적인 컴파일러 에러의 형태는 컴파일러 종류에 상관없이 매우 비슷하다.
![](http://pds11.egloos.com/pds/200811/22/29/d0033129_4928088c168e4.png)
![](http://pds12.egloos.com/pds/200811/22/29/d0033129_4928088fd0d34.png)
너무나 당연히 “어느 소스 파일의 어느 라인에서” 이라는 정보가 들어가 있다. 그래서 main.c 파일의 6번째 줄로 가면 이 에러를 잡을 수 있다라는 것을 알려 준다. 그런데 링크 에러는? 맨 위 그림을 보면 “어디에서”에 해당하는 정보가 없다. VC++ 컴파일러가 멍청해서 그런 것일까? 아니다. 대부분이 다 그러하다.
![](http://pds10.egloos.com/pds/200811/22/29/d0033129_4928089395f05.png)
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 폭탄을 맞는다.
![](http://pds13.egloos.com/pds/200811/22/29/d0033129_49280897d054f.png)
대표적으로 위 그림과 같은 런타임 라이브러리 사용을 어떻게 할 것인가를 잘 맞춰야 한다. 때에 따라서는 별도로 libc.lib, msvcrt.lib와 같은 기본 라이브러리가 자동으로 링크되지 않도록 해야 한다. 정말 겪어 보지 않고서는 모르는 문제다.
이런 걸 보면 명시적인 링킹 과정이 없어 보이는 C#이나 Java는 참으로 편리한 언어인 것 같다. 그래도 적어도 링크 에러 메세지를 조금만 가다듬어도 “체감 난이도”는 훨씬 줄어들 것이다.
'컴퓨터 활용 > 윈도우 활용' 카테고리의 다른 글
Windows7 설치용 USB 만들기 (0) | 2013.05.12 |
---|---|
[윈도우] 윈도우 커맨드 명령어 (계산기, 그림판, 메모장, 원격 데스크톱, 프로그램 추가/삭제) (0) | 2012.08.26 |
[VMware] 왼쪽 마우스 클릭이 안될 때 해결책 (2) | 2012.06.27 |
Platman_install.vbs 에러 (0) | 2010.12.31 |
[스크랩] LNK4098, NODEFAULTLIB 경고 (0) | 2010.12.29 |
Visual Studio Debug and Release Modes (0) | 2010.11.11 |
윈도우 0xc0150002 초기화 오류 (0) | 2010.10.27 |