시리얼 통신 강좌 중이지만 앞으로 많은 프로그램을 작성해야 하기 때문에 컴파일에 대한 말씀을 드리면서 Makefile을 만드는 방법에 대해 설명하고자 합니다. 지금까지는 간단한 예제로 그냥 컴파일해 왔기 때문에 엉뚱한 a.out 이라는 이름으로 실행 파일이 만들어 집니다. 그러나 컴파일에도 여러 옵션이 있습니다. 그중에 -o 옵션에 대해 말씀드립니다.

컴파일 방법

  컴파일을 할 때, 소스 파일 이름만 넣어 주시면 a.out으로 실행파일이 만들어 집니다. 여기에 -o 옵션을 사용하면 만들어지는 실행파일의 이름을 지정할 수 있습니다.

]$ gcc test.c     -> 실행 파일의 이름은 a.out
]$ gcc -o test test.c    -> 실행 파일의 이름은 test

  옵션의 위치는 바뀌어도 상관없습니다.

]$ gcc test.c -o test

  리눅스, 즉 유닉스를 사용하시다 보면 텍스트 화면에서 작업을 많이 하기 때문에 사용하는 프로그램과 커맨드에 따라서 매우 다양한 옵션을 보실 수 있습니다. 예로 현재 폴더 내에 모든 .h 파일에 대해서 define 문자열을 찾는 find 명령이 아래와 같습니다.

]$ find . -name *.h -exec grep -nr "define" {} ; -print

  복잡해 보이죠? GUI에만 쩔은 저에게는 답답하기까지 합니다. 그러나 익숙해 지면 GUI와는 다른 편리함이 있더군요. 여하튼 옵션에 대한 거부감이 있다고 하시면, 여하튼 편리함을 위해 만들어진 것이라는 것을 먼저 상기해 주세요.


  그러나 여기서 한가지 더 알고 넘어 가야할 것이 있습니다. 프로그램 하나를 만든다고 하더라도 소스파일 하나로 작성하기 보다는 기능과 특징에 따라 여러 개의 파일로 나누어 작성하는 프로그램 소스의 모듈화입니다.

 

소스를 모듈화

  소스를 모듈화하는 것에 대한 장점은 참 많습니다. 기능과 특징 별로 나누었기 때문에 소스를 추적하기에 편리한 점도 있지만 제 개인적인 경험과 생각으로는 아래와 같은 장점이 있습니다.

  1. 프로그램 작성을 더 체계적으로 작성할 수 있다.

       C 언어든 파스칼이든 프로그램은 함수를 만들어 가는 작업입니다. C의 첫 실행 위치인 main()도 함수입니다. 프로그램을 작성해 나가다 보면 새로운 함수를 계속 만들게며, 유능한 프로그래머일 수록 만들어진 함수관리를 효율적이고 철저히 관리합니다.

       함수를 부품이라고 한다면 이상이 있는 부품을 빨리 찾아 내서 고치던지 바꾸든지 처리해야 하겠습니다.


      저는 프로그램을 잘 짜는 사람을 유능한 프로그래머라고 생각하지 않습니다. 프로그램에 문제가 발생했을 경우에 빠른 시간 안에 그 원인을 찾아내고 방법을 마련해서 디버깅을 할 줄 아는 사람이야 말로 유능한 프로그래머라고 생각합니다.



       이렇게 처리하기 위해서는 부품인 함수를 프로그래머가 쉽게 손이 닿을 수 있도록 잘 정리해야 겠는데, 그 첫번째가 프로그램의 모듈화라고 말씀드리고 싶습니다. 내가 만든 프로그램이라고 하더라도 수만 라인이 넘는 소스파일이라면 위, 아래로 스크롤해 가면서 함수를 찾아 내야 한다면 그 자체가 고역일 것입니다.

      만들어지는 함수는 공통된 부분이 있고, 그 공통된 부분 별로 따로 모아 둔다면 훨씬 관리가 쉽겠지요. 입력 부분, 출력 부분, 통신 부분, GUI 처리부분, .... 프로그램을 작성하다 보면 새로운 부분이 생기는데, 이를 파일별로 따로 작성하는 것이 매우 효율적이며, 부분부분 별로 함수를 모아 놓았기 때문에 프로그램 소스 코드를 더욱 체계적으로 관리할 수 있습니다.

  2. 프로그램의 이해를 돕는다.

       함수를 만드는 이유가 이 곳 저 곳에서 자주 사용하는 기능을 따로 만들어서 간편하게 사용하기 위함도 있습니다만 경우에 따라서는 한 곳에서 한번 호출하는데도 함수로 만드는 경우가 있습니다. 이는 길든 짧든, 내가 만든 프로그램이라도 소스를 읽기 편하고, 나중에 분석하기 편하게 하기 위해 복잡해 보이는 부분을 함수로 따로 때어 낸다는 것이죠.

      훌륭한 소스란 프로그램 하나하나의 행을 눈으로 읽혀질 때 거부감 없이 바로 이해되도록 작성된 것입니다.

     프로그램 루틴 중에 어느 한 부분이 기능에 매우 충실하게 잘 작성되어 있지만 다른 코드와 함께 섞여 있다 보니 전체적으로 난잡하게 보이는 경우가 있습니다. 이럴 때에는 그 충실하고 잘 만들어진 부분을 가감히 함수로 만들어 버립니다. 이제 여러 행, 여러 페이지로 된 그 부분을 간단하고 이해하기 쉬운 함수 이름 하나로 정리가 되었습니다.

      거기에다가 그 함수를 적당한 이름의 파일로 분리했습니다. 이제 이 소스 파일에는 오로지 그 함수를 이용한 전체적인 코드만이 존재하고 전체적인 흐름이 한 눈에 들어 옵니다. 남들이 봐도, 또는 언성을 올리지 않고 설명을 해도 고개를 그떡이게 하는 이해하기 쉬운 프로그램이 좋은 프로그램입니다.

  3. 프로그램 간에 격리(Isolation)

      제가 특히 조심하는 부분 중에 하나가 바로 이부분입니다. 아무리 봐도 이상이 없는 코드인데, 실행만 하면 아주 엉뚱한 행동을 합니다. 따로 때어 내서 실행해 보면 이상이 없어요. 그런데, 프로그램 전체로 묶어서 실행하면 이게 또 영~ 엉뚱한 짓을 합니다.

      이렇게 되는 이유가 여러 가지이겠습니다만 분명히 다른 코드로부터 영향을 받기 때문입니다. 프로그램 코드는 자기와 관계된 코드하고만 서로 영향을 주고 받아야 겠습니다. 그래야 문제가 발생하면 추적할 수 있습니다. 그 수 만은 함수 중에 알지 못하는 어떤 놈으로부터 영향을 받아 어뚱한 짓을 한다면 정말 미칠 노릇이죠.

      실행 파일 하나에 모두 쓸려 들어갈 코드이지만 다른 소스코드와 분리되도록 작성된다면, 그리고 가급적이면 독립되도록 작성되어 진다면 프로그램이 더욱 안정된 코드가 될 것입니다. 그렇게 하기 위한 첫번째 작업이 모듈화입니다. 소스코드를 하나의 파일로 독립시켜 놓고 다른 소스코드에서 사용할 함수나 빌려갈 변수만 헤더파일(.h)로 공개하고 안에서는 지들 끼리만 놀게 한다는 것이죠.


  4. 라이브러리화하기 편리합니다.

       새로운 시스템을 작성한다고 해도, 이전에 작성한 코드를 거의 60%에서 80%를 다시 사용한다고 해도 과언이 아닙니다. 물론 이전에 작성한 코드를 이 만큼 사용한다고 해서 개발 기간이 60%, 80%가 절감된다는 것은 아닙니다. 그만큼은 아니더라도 이미 원하는 기능을 실현하는 함수를 다시 사용하므로 시간을 벌 수 있을 것입니다. 그리고 무엇보다고 이미 이전에 사용했던 코드이므로 어느 정도 디버깅이되고 인증이된 코드이기 때문에 새로 작성한 것 보다는 에러가 날 위험이 적습니다.

       물론 예전 코드에 알지 못했던 버그가 있을 경우 디버깅하는데 더 오랜 시간을 고생하게 하는 경우도 있습니다. 보통 이상 없이 사용했던 코드라면 과신하게 되거든요. 그러나 역시 이때 문제를 잡으면 다음 작업에서 그 고생한 만큼의 보답을 받게 됩니다.

     이런 함수를 기능 별로 특징 별로 모아 놓았기 때문에 꺼내어 쓰기가 편하고 아예 라이브러리 파일로 다시 만들어서 사용하기가 편리해 집니다.

 

모듈화한 프로그램 컴파일

  모듈화의 필요성에 따라 main.c, tcpip.c, rs232.c로 3개의 파일로 나누어서 프로그램을 작성했습니다. 이제 컴파일을 어떻게 할까요? 이렇게 합니다.

]$ gcc main.c tcpip.c rs232.c -o test

  간단하죠, 방법은. 그러나 문제가 있어요. 파일이 많아 질 경우 그 많은 파일 이름을 어떻게 일일이 타이핑해서 컴파일 하겠습니까? 또한 여기에 필요한 라이브러리 파일이나 기타 옵션을 추가하면 더욱 복잡해 지겠지요. 물론 배치파일을 사용하면 되겠습니다.

  그래도 문제가 있습니다. 이미 컴파일이 끝나서 컴파일이 필요 없는 데도 다시 컴파일하게 됩니다. 물론 작은 프로그램이야 잠시 기다리면 되겠습니다만, 큰 프로그램일 경우 컴파일 하는데, 몇 분씩이나 걸린다면 안되겠지요. 그래서 나온 것이 Makefile입니다. 다름 시간에 Makefile 에 대해 자세히 말씀을 올리도록 하겠습니다.

 

태그: *컴파일 *컴파일러 *C언어 *초보