|
요즘 한가하게 블로그에 글을 쓸 시간이 없음에도 불구, 머리 좀 식힐 겸 생각했던 이야기 하나.
옛날 옛적에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”라는 또 알 수 없는 말이 있다. 링크 에러가 컴파일 에러에 비해 매우 어렵게 느껴지는 것은 이렇게 에러가 어디서 났는지를 쉽게 알려 주지 않기 때문이다. 만약에 컴파일러가
라고 이야기 해주면 어떨까? 단언컨데 훨씬 링크에러를 쉽게 이해할 수 있을 것이다.
대충 이 정도만 되어도 훨씬 쉬울 것이다. 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는 참으로 편리한 언어인 것 같다. 그래도 적어도 링크 에러 메세지를 조금만 가다듬어도 “체감 난이도”는 훨씬 줄어들 것이다.
최근 등록된 덧글
개발자 입장에서의 수많은 ..
by Jiyoon at 02/04 저도 아들 돌잔치때 돌잡이 .. by 박상욱 at 01/18 미국 대학원 원서 작성중에 p.. by 태클사이야 at 01/13 TO: 박PD 로그인 하지 않아.. by 박응용 at 01/10 http://gigglehd.com/zbx.. by dhunter at 12/28 우와.. 좋네요. 태반이 .. by 윤광배 at 12/17 항상 좋은 글 잘 보고 있습니.. by y2k at 11/23 글이 좋아서 제 블로그에 담.. by 쏭섭 at 11/23 최근 등록된 트랙백
메뉴릿
이글루 파인더
|