회사 동료에게 소개 받은 책으로 (이전 교수님께 들은 얘기론, 세가 = 게임 프로그래머의 학교라는..)

PSP프로그래밍 .... 보다는 3D에 대한 기본 지식을 다시 복습하고 있습니다.

책은 세가에서 신입사원 프로그래머에게 실전 프로그래밍의 복습용으로 제작된 회사 문서를 정리하고 추가해서

펴낸 책이라고 하네요. (옆의 아이폰 터치(아이폰 요금 짜증나서 끊었기에)으로 이 책의 두께를 알 수 있습니다. 약850p)



물론 안은 일어 글씨만 잔뜩 써있는게,..... 마치 API 정복 책을 보는거 같습니다만,

확실히 설명은 잘 되어있네요. 읽다가, "아.. 이런게 있었지" 가 많이 떠오릅니다.

우리나라에서 저 책이 번역이 있으면 대학교에서 게임 프로그래밍 기본 소양으로 배울수 있을꺼 같습니다.

책 내용 레벨은 대충 대학 3학년 정도... 군대 갔다오고 나서 머리 포멧된 상태에서 저걸 정독하면
3D 기본 프로그래밍, C,C++ 기본 소양, DLL 처리 등이 재 입력됩니다 ^^..

한국인 프로그래밍에선 통할지 안통할지.. 잘 모르겠군요
(책 내용중엔 STL 사용을 가급적 배제 하라는 내용이 있습니다. (MAP이면 괜찮지만 하면서요, 메모리 관리 문제등으로..)

혹시나 해서 yes24에서 찾아봤는데..
http://www.yes24.com/24/goods/3362046
...
orz

'알고리즘 > 게임 수학 / 물리' 카테고리의 다른 글

ODE를 사용한 간단한 게임  (0) 2007.12.13
변환행렬  (0) 2007.11.11
삼각함수 항등식들  (0) 2007.09.20
게임 수학 / 물리 2강내용  (0) 2007.09.14
게임 수학 & 물리  (0) 2007.09.06


정확히 회사 동료에게 받았습니다.
正確には会社同僚からもらいました。

잘 보니 마장기신의 슈우가 타고 다니던 그거 더군요.
良くみると、シュウが乗っているあの機体ですね。

>_< 니시오카 상 땡큐~
西岡さんありがとう~

programming/general 2003/03/12 07:18

NPC의 인공지능 프로그래밍에 대해서 간단히 정리합니다. 처음이라 감이 안 오시는 분은 참고하세요. 매우 허전한 아이디어 정도이지만 화이트데이에 수위 정도의 구현이라면 무리가 없을 것입니다. (화이트 데이에서 초기 AI랑 기본 구현만 제가 해서 사실상 이 글 내용 정도밖에는 잘 ...^^ - 주로 수위가 버벅거리는 부분은 저랑 관련 있고, 재미있는 부분은 관련이 없는.. T_T)

1. Introduction

게임에서 인공지능이 적용되는 부분은 많지만 일반적인 게임에서 겉으로 드러나는 부분 중 가장 큰 비중을 가지는 부분은 NPC 의 처리 일 것입니다.
게임에 따라 인공지능의 난이도나 구성은 크게 차이가 나지만 일반적으로 가장 많이 접하는 NPC(특히나 몬스터, 주민들)은 어느 정도 비슷한 패턴을 가지고 있습니다.
NPC 처리를 크게 지능적인 판단을 하는 부분과 기본적인 행동을 하는 부분, 두 분야로 구분할 수 있을 것입니다.
신경망, 퍼지, 유전자 알고리즘 같은 고급 기술을 사용하는 전자의 처리가 인공지능의 핵심이라고 할 수도 있겠지만, 개인적으로는 게임에서의 인공지능은 오히려 후자의 처리가 비중이 더 크고 중요한 부분에 해당된다고 생각됩니다. 그래서 게임의 인공지능을 처리하기 위해선 인공지능의 분야뿐 아니라 일반적인 게임 프로그래밍에도 어느 정도 기본이 있어야  수월하게 적용할 수 있습니다.
이 글에서는 후자에 해당하는 기본적인 NPC의 행동 처리에 대해서 다루어 보도록 하겠습니다.
(중심은 NPC지만 다른 오브젝트들에도 공통적으로 적용할 수 있는 것들입니다.)

2. 행동 (상태, 명령)

- 유한 상태기계 (Finite State Machine)

유한 상태기계는 쉽게 얘기해서 미리 정해진 상태를 일정한 규칙대로 옮겨가는 것입니다. 아주 간단한 예를 들어보면 아래와 같습니다.

switch(state) {
case 식사 :
   식사();
   if (식사마침 == TRUE)
       state = 오락하기;
   break;
case 오락하기 :
   오락하기();
   if (졸림상태 > 0.9f)
       state = 잠자기;
   break;
case 잠자기 :
   잠자기();
   if (시간 == 아침)
       state = 식사;
   break;
}


매 프레임 위와 같은 처리를 반복한다면 이 오브젝트는 밥 먹고, 오락하고, 잠자기, 밥 먹기, 오락하기, ... 를 반복할 것입니다. 기본적인 행동 패턴은 위와 같이 처리할 수 있습니다. (행동 패턴 말고 switch 를 쓴다는 식의 구현으로의 접근은 아니고... 의미가...)
여기서 핵심은 상태에 맞는 *행동*을 한다는 것이고 *조건*이 되면 다른 상태로 *분기*된 다는 것입니다.

슈팅 게임의 별 볼일 없는 적(소위 자코)이나 미사일같은 오브젝트처럼 단순한 인공지능의 경우에슨 단순히 FSM 으로도 처리가 가능할 것입니다.

- 명령 시퀀스 : 스택 기계 (Command Sequence : Stack Machine)

기본적으로는 FSM 으로도 어느 정도 구현이 가능하지만 확장하려면 손이 많이 갑니다. (특히나 길 찾기 랑 결합되면 이동에 관한 여러 가지 상태를 만들어 처리해야 하기 때문에 코딩량이 많아집니다.) 이 경우에는 명령어를 쌓아 놓고 처리하는 식으로 접근하게 되면 좀 더 유연하게 처리할 수 있습니다.
스택방식을 사용하는 방법을 추천합니다. (GPG에서는 큐 방식이 소개되어 있는 데, 개인적으로 큐 방식은 기능 확장하기엔 부적절한 방식이라고 생각됩니다. 판단은 직접하세요.)

만약 길 찾기 처리를 통해 얻은 이동해야 하는 경로가 A -> B-> C-> D 라고 하면 다음과 같이 명령을 쌓아둡니다. (D, C, B, A 순으로 쌓아둡니다.)

+0003 "이동:A로"
+0002 "이동:B로"
+0001 "이동:C로"
+0000 "이동:D로"


스택의 특성상 명령을 얻게 되면 "이동:A", "이동:B", "이동:C", "이동:D" 의 순으로 들어오게 되므로 정상적으로 처리할 수 있습니다.

그냥 스택 기계만으로 FSM을 구현할 수도 있겠지만 (항상 현재 상태를 맨 위에 Push 해 두는 식으로 처리하면...) 간단하게 확장하기 위해서 상태 중에 COMMAND_SEQUENCE 상태를 두도록 하겠습니다.

switch(state) {
case 이동 :
   이동();
   if (현재 위치 == 도착점)
       state = COMMAND_SEQUENCE;
   break;
...
case COMMAND_SEQUENCE :
   state = 스택처리();
   break;
}


이런 식으로 스택 처리 기계를 위치시킵니다. 여기서 앞의 예를 적용해보기로 하고 간단히 스택 명령어 처리를 아래처럼 해보면

int 스택처리()
{
   명령 i = Pop()
   if (i.command == 이동) {
       도착점 = i.position;
       return 이동;
   }
...
}


명령을 쌓아두는 식으로 오브젝트가 A, B, C 를 거쳐서 D까지 이동함을 예상할 수 있습니다.
이 장점은 NPC가 할 일을 유동적으로 설정할 수 있다는 것입니다. 만약 엘프 같은 파티 오브젝트가 A의 위치로 가서 C를 공격하고 B의 위치로 이동해야 한다면 아래처럼 명령을 쌓아두면 됩니다.

+0002 "이동:A로"
+0001 "활쏘기:C로"
+0000 "이동:B로"


만약 상태기계로만 처리해야 한다면 여러 가지 상태와 변수들을 같이 사용해야 하지만, 명령어를 쌓아두는 방식을 손 쉽게 순차적으로 행동을 지정할 수 있습니다. (이 방식은 큐나 스택이나 쌓는 순서만 다를 뿐 동일합니다.)

명령 시퀀스를 큐(Queue)가 아닌 스택으로 처리하게 되면 몇 가지 추가적인 이점이 생깁니다. 화이트 데이 수위의 초기 명령어 시퀀스는 아래 같은 식입니다.

0004 "길찾기:위치1"
0003 "길찾기:위치2"
0002 "길찾기:위치3"
0001 "길찾기:위치4"
0000 "초기명령세팅"


수위의 순찰 코스는 1 -> 2 -> 3 -> 4 -> 1 -> ... 식으로 반복되게 설정되어 있는 것입니다. 그럼 처음 언급한 것과 차이 점은 무엇일까요 ? 바로 "이동" 명령이 아니라 "길찾기" 명령이라는 것입니다.
수위차 짠 하고 시작하는 시점에서 "길찾기" 명령을 만납니다. 이 때 길 찾기를 이용해서 '위치1' 로 이동하기 위해서 '위치A' -> '위치B' -> '위치C' -> '위치1' 로 이동해야 한다는 정보를 얻어내고 '위치1'로 이동하는 명령을 하게 되면 명령 시퀀스는 아래처럼 됩니다.

0007 "이동:위치A"
0006 "이동:위치B"
0005 "이동:위치C"
0004 "이동:위치1"
0003 "길찾기:위치2"
0002 "길찾기:위치3"
0001 "길찾기:위치4"
0000 "초기명령세팅"


이렇게 되면 순차적으로 A, B, C 를 거쳐 '위치1' 에 도착하게 되고 도착한 위치에서 '위치2'로 이동하기 위한 명령 시퀀스를 설정하게 됩니다. 이런 식으로 특별한 장치 없이도 자연스럽게 원하는 결과를 얻을 수 있습니다.
만약 큐로 처리해야 한다면 명령어에 레벨을 두어서 계층적으로 처리해야 할 것입니다. (순찰에 대한 명령 시퀀스와 길찾아 이동하는 것에 대한 명령 시퀀스가 따로 존재해야 되겠죠.)

화이트 데이에서 수위의 명령 시퀀스 중엔 아래와 같은 부분도 있습니다. (자세히 기억은 안나지만...^^) 위치 0에서 시작해서 문 1을 열고 1, 2를 거쳐서 유저가 갈 수 없는 문2를 열고 들어가서 문닫고 30초후에 다시 반복하는 식으로 유저가 갈 수 없는 문 1과 문 2를 열면서 입구만 있고 실제로는 있지도 않은 층계를 넘나들면서 뻔뻔하게 순찰을 하게 됩니다.

0009 "워프:위치0"
0008 "문열기:문1"
0007 "길찾기:위치1"
0006 "길찾기:위치2"
0005 "문열기:문2"
0004 "이동:위치3"
0003 "문닫기:문2"
0002 "사라지기"
0001 "대기:30초"
0000 "초기명령세팅"


누구나 아는 FSM 와 스택 머신을 이용하면 기본적인 NPC의 행동은 큰 무리 없이 표현할 수 있습니다.

3. 판단 (이벤트)

기본적으로 행동을 정의 했지만 좀 더 NPC의 행동을 확장하기 위해서는 여러 가지 판단을 처리해야 합니다. 이 판단을 할 시점이 먼저 중요한 데, 여기에 이벤트 시스템이 사용됩니다.

먼저 1초 마다 A라는 행동을 하기 위해서 접근한다고 생각해보면

void OnTimer()
{
   if (1초지났나() == TRUE) {
       A();
       타이머초기화();
   }
}


식으로 정의해서 OnTimer 를 매프레임 실행하는 방식으로 접근 할 수 있습니다. 이 방식에는 큰 문제가 없지만 AI를 확장하기 위해 스크립트를 도입하게 된다면 약간 조율이 필요합니다. 만약 OnTimer 를 스크립트로 작성한다면 무거운 스크립트 루틴에서 1초 지났는 지를 판단해야 되기 때문입니다. (아무리 최적화된 스크립트 시스템이더라도 매 프레임 호출되는 것은 바람직하지 못합니다.)
이런 상황은 반드시 피하기 위해서 OnTimer 는 그냥 C 루틴으로 작성하고 A 라는 행동을 스크립트로 정의합니다.
OnTimer 루틴에 의해 조건을 체크하는 것은 폴링(Polling) 방식이긴 하지만 실제로 판단에 해당되는 A는 1초가 지난 시점에 한번이기 때문에 A의 관점에서는 이벤트 방식이라고 할 수 있습니다.

이런 관점에서 NPC 인공지능에서 이벤트가 발생하는 것은 능동적인 것과 수동적인 것 두가지로 구분 할 수 있습니다.

- 능동적인 방식

능동적인 방식이란 조건을 적극적으로 오브젝트가 검색하는 것으로 위에 소개한 방식이 여기에 해당됩니다.
화이트데이의 수위의 시야에 유저가 보이는 시점에서 이벤트 스크립트가 호출되는 데, 그 것이 여기에 해당됩니다. 수위의 프로세스에 매 프레임 시야에 유저가 보이는 가를 체크하는 루틴이 있고 (실제로는 몇 프레임에 한번씩 체크합니다만...^^), 발견되는 시점에 이벤트가 생기도록 되어 있습니다. 이때 수위는 호루라기 불고 추적 상태로 전환한 후 쫓아오게 스크립트로 작성되어 있습니다.
공격 상대가 시야에 보일 때 발생하는 이벤트, 일정 시간 간격으로 발생하는 이벤트등 여러 가지가 있습니다.
요점은 앞서 얘기한 것처럼 적극적으로 조건을 체크하되 가장 핵심적인 판단에 대한 것은 이벤트 방식으로 접근해야 한다는 것입니다.

- 수동적인 방식

이것은 어떤 조건이 되면 발생하는 이벤트로, 공격을 받는 다거나, 에너지가 0이 되는 경우가 여기에 해당됩니다.
주로 오브젝트간의 메세지 교환 시에 발생하는 것으로 판단이 필요한 조건에 이벤트가 발생하도록 작성하면 됩니다.
화이트데이의 경우엔 소리를 낼 경우 수위에게 메세지를 보내는 데 수위는 그 소리를 듣고 판단을 하게 됩니다. (수위 근처에서 문을 열어서 소리를 내면 수위의 스크립트가 호출되고, 스크립트에서는 조건에 따라 해당 위치로 정찰을 가도록 되어 있습니다.)

사실상 이벤트가 능동적으로 호출 되는 지, 수동적으로 호출 되는 지는 구현상에서 구분될 뿐이고, 적절한 조건에 이벤트가 발생하는 것이 중요합니다. (게임에서는 가능한 최소의 이벤트가 호출되는 것이 인공지능 처리 속도의 관건입니다.)

이벤트가 발생 했을 때 어떤 식으로 처리할 까에 대한 것은 여기에서는 다루지는 않겠습니다. (게임마다 다르고 사용되는 기술도 다양하기 때문입니다. - 제대로 모르기도 하고... T_T)

기본적으로 확장을 하기 위해서는 스크립트 도입을 고려해 볼 필요가 있습니다. 물론 순수하게 파라메터들을 이용해서 코딩해도 충분히 확장할 수 있고, 괜찮은 방법이 될 수도 있습니다만, 인공지능에 대한 다양한 컨트롤이 필요할 경우에는 스크립트를 사용하는 것이 많이 유리합니다.

그리고 퍼지나 신경망 같은 고급 기술을 사용하면 좀 더 유연하게 풀 수 있습니다.
예를 들어 신경망을 이용해서 학습을 구현한다면 의도하지 못한 재미있는 결과를 얻을 수도 있을 것입니다. (예를 들어 여자 캐릭터에게 자주 공격 당한 NPC는 능력에 상관없이 여자 캐릭터에게 공격 받으면 도망간다거나 하는...)
퍼지를 이용하면 공격을 몇 번 받은 캐릭터 평소보다 쉽게 화를 낸다거나 하는 식의 접근도 가능할 것입니다.
그 밖에도 인공 생명을 이용한 여러 가지 알고리즘등을 활용한다면 게임에서의 NPC의 행동을 더욱 풍부하게 할 수 있을 것입니다.
(M모 게임은 NPC와 사귈 수도 있다고 하던데...)

4. 기타 아이디어

마지막으로 이동과 관련된 나름대로의 몇 가지 아이디어를 적어봅니다.

- 추적

추적의 기본적인 컨셉은 타겟의 위치로 이동입니다.
물론 무조건 타겟 방향으로 이동하는 것만으로도 어느 정도 추적이 되지만 장애물이 막고 있거나 하다면 이상해질 수 있습니다. 이때 단순히 상대방의 위치로 이동하는 식으로 접근하게 되면 웃기게 길을 따라 가게 되어 거리를 좁히기가 쉽지 않습니다. 여기서 제가 제안하는 방법은

A. 타겟이 보이고 이동 가능하면 방향을 타겟 방향으로 하고 이동
B. 아니면 타겟까지 길 찾기해서 찾은 경로의 중간 위치까지 이동

입니다. 대부분 무난하게 괜찮은 결과를 얻을 수 있습니다. (물론 부가적으로 이동 등을 잘 해야 겠습니다만...)

우선 첫번째 경우는 당연히 보면서 타겟이 따라가기 때문에 계속 시야에 있고, 이동 가능하다면 타겟과 만날 수 있습니다.
주로 문제가 되는 것은 보이지 않는 경우입니다. 가장 쉽게 추적하는 방법은 매 프레임 상대방의 위치로 길 찾기하고 그 길방향으로 이동하는 것입니다. 하지만 게임에서는 철저히 피해야 할 방법입니다. 그럼 길 찾기를 적게 하면서 효율적으로 따라가는 방법을 찾아야 하는 데, 가장 쉽게 생각할 수 있는 것은 n 초마다 혹은 m 미터 이동마다 갈 경로를 갱신하는 것입니다. 이 방법도 괜찮긴 한데, 타겟과의 거리에 따라 간격을 조절해야 하기 때문에 좀 까다로워 질 수 있습니다.
여러 가지 테스트 해 본 결과 가장 효과적인 결과를 얻는 방법이 두 번째 방법입니다. (물론 이동 속도에 따라 중간 위치는 조절될 수도 있습니다.) 오브젝트랑 타겟의 길을 만든 후에 서로를 향해 달렸다고 가정했을 때 만나는 위치까지 이동한 후에 다시 그 곳에서 같은 방법으로 경로를 결정하는 것입니다. (상대가 시야에 나타나면 목표지점 무시하고 A번의 방법으로 추적합니다.) 상대가 어떤 경로로 도망가든지 빈틈없이 적은 길 찾기 횟수로 거리를 좁히면서 추적할 수 있을 것입니다.
사실은 보이지 않는 데 길을 따라 간다는 건 사기에 가깝지만 약간의 조건만 두면 (거리가 너무 멀면 포기 같은...) 실제로 상대방의 소리나 인기척을 고려해서 추적하는 거와 거의 비슷한 결과를 얻을 수 있습니다. (게임이니까... ^^)

- 장애물 피하기

길 찾기는 두 가지 타입으로 작성합니다. 정적인 상태의 길 찾기와 동적인 오브젝트를 고려한 것 길 찾기입니다. 일반적인 길 찾기는 static 한 상태로 합니다. 동적인 오브젝트를 고려해서 길 찾기를 한다면 멍쩡한 길을 꼬불 꼬불 돌아서 가는 일이 발생합니다. (길 찾기하는 시점에는 오브젝트가 있던 자리기 때문에...)
정적인 상태의 길로 가다 보면 이동 못하는 지점이 생깁니다. 이때는 그 지점에서 내가 가고자 하는 경로 중에 오브젝트가 없는 적당한 거리로 동적인 오브젝트를 고려해서 길 찾기를 합니다. 그러면 그 지점에서 막고 있는 오브젝트를 피해서 적당한 거리로 이동하고 스택에 의해서 다시 원래의 갈 길을 가게 됩니다.
화이트데이는 복도가 좁아서 사다리가 애매하게 막고 있으면 수위가 길을 못 가는 일이 생기곤 했는데, 어느 날 작업하다 수위가 버벅거리길래 어떻게 하나 생각하고 있는 데, 갑자기 사다리를 방망이로 부셔버리고 이동하는 거 보고 놀란 적이... (인공지능 프로그래밍 하시던 아뇨 아저씨가 충돌시에 발생하는 이벤트 루틴에 충돌한 오브젝트가 사다리면 부셔버리게 만들었다는... ^^)

- 자연스럽게 이동하기

이건 기회가 되면 샘플과 함께 다루었으면 하는 내용입니다만 간단히 얘기해 보겠습니다. 만약 이동하고자 하는 경로가 A -> B 일 경우 A까지 이동한 후에 B를 향해서 돌면서 이동하게 됩니다. 2D 게임의 경우 별로 어색하지 않습니다만 3D 게임의 경우에는 충돌문제 때문에 제자리에서 꺽고 돌아야 하는 경우 엄청 이상하게 됩니다. 이 경우에는 A 까지 이동하는 중간 중간 A 와 B 사이의 적당한 지점이 이동 가능한 지 (장애물 없는 지) 체크해서 만약 이동 가능하다면 A로 이동하는 거 취소하고 그 지점으로 이동하도록 수정합니다. 이런 식으로 계속 반복하면 A 에서 B로 조금씩 적당하게 경로를 수정하면서 자연스럽게 이동하게 됩니다. (베지어 곡선 비슷하게 생각하시면 되겠네요.)

5. Reference

"AI Game Programming Wisdom"
Charles River Media 출판사
Steve Rabin 외
(번역서:정보문화사)

"Game Programming Gems 시리즈"
Charles River Media 출판사
Mark DeLoura 외
(번역서:정보문화사)

"AiWizWiki"
http://aiwiz.2ing.net

"AIwisdom.com"
http://www.aiwisdom.com

"AiStudy.co.kr"
http://www.aistudy.co.kr

"Guru who gears a.i to life"
http://www.gurugail.com

"Generation5"
http://www.generation5.org

 

이글의 출처 : http://gamecode.org/tt/category/programming/general

'알고리즘' 카테고리의 다른 글

다중패턴검색 알고리즘  (0) 2014.07.14
스즈미야 하루히의 약속  (0) 2008.01.29

공동 혹은 협력 개발 작업을 하다가 보면 서로의 정보를 공유하기 위해
매우 다양한 방법을 사용하게 됩니다.

그러한 방법 중에 하나로 서로가 개발한 부분의 사용법이나
자세한 작동 내용을 설명하기 위해 클래스나 함수들의 용법을 설명하는
문서를 자주 사용하게 됩니다.

그런데 이러한 문서를 작성하고 있다보면
개발보다 결코 쉽지 않고 만만치 않은 시간이 소비됩니다.
이미 개발하는 당사자도 많은 주석을 통해 코드 곳곳에 정보를 남겼을 것인데
이를 다시 보기 좋게 정리하고 문서에 투자하는 시간은 어쩌면 낭비되는 시간일지도 모릅니다.

이러한 고민이 아주 오래전부터 있었던 모양입니다.
벌써 많은 문서화 툴이 제작되고 사용되고 있는 중이지요.

이 글에서는 그중에 비교적 빨리 적응할 수 있고
오픈 소스 정신에 입각하여 누구나 사용할 수 있는 Doxygen을 소개하고자 합니다.

역시나 많은 분들이 사용하는 Windows XP 이상을 기반으로 설명할 것이며
DoxyWizard를 사용하는 것을 주로 설명합니다.

1. 설치
    관련 주소 : http://www.stack.nl/~dimitri/doxygen/ - 개발자의 홈페이지
 우선 다운로드 주소에서 윈도우즈용 최신 버전을 다운로드해서 설치합니다.
 그리고 그래프를 표현하기 위한 프로그램인 Graphviz도 다운로드해서 설치합니다.

2. 테스트용 소스
 다음과 같이 테스트용 소스를 만들었습니다.
 Doxygen은 다양한 형식의 주석을 지원합니다만 아래의 테스트 코드는 제가 흔히 사용하는 스타일로 적어 보았습니다. Doxygen의 문법은 KLDPWiki를 참고하시면 좋습니다.

소스 보기

디렉토리 구조

E:\Work\Doxygen_example

├ src

│ ├ main.cpp

│ └ class.cpp

└ inc

└ class.h


--- main.cpp의 코드 ---

#include "class.h"


//! 메인 함수

int main(void)

{

    // 메세지 출력

    printf(_T("Hello! Doxygen\n"));


    // 질문 메세지 박스 출력

    cDoxyClass test;

    bool ans = test.showQuestionBox(_T("Doxygen 마음에 드십니까?"), _T("질문"));


    // 반응 출력

    if (ans)

        printf(_T("아이고 감사합니다.\n"));

    else

        printf(_T("이런 죄송합니다.\n"));


    // 정상 종료

    return 0;

}


--- class.h의 코드 ---

//! @brief Doxygen 예제용 클래스

//!

//! Doxygen 주석 문법을 설명하기 위한 간단한 예제용 클래스입니다.\n

//! 뭐 특별히 하는 일은 없습니다.

lass cDoxyClass

{

protected:

    int m_nNum;    //!< 메세지 박스 출력 횟수

public:

    //! 생성자

    cDoxyClass();

    //! 소멸자

    virtual ~cDoxyClass();

public:

    //! @brief 질문 메세지 박스 출력

    //!

    //! 최상위에 Yes/No로 질문하는 메세지 박스를 출력합니다.

    //! @return Yes를 선택하면 true, 그 외에는 false

    bool showQuestionBox(

         const TCHAR* msg            //!< 질문 내용

        , const TCHAR* title = NULL    //!< 메세지 박스 제목

        );


    //! 메세지 박스 출력 횟수 얻기

    //! @return 메세지 박스 출력 횟수

    int getNum(void);

};


//! @brief Doxygen 예제용 하위 클래스 1

class cDoxySubClass1 : public cDoxyClass

{

public:

    int getNum(void) { return 1; }

};


//! @brief Doxygen 예제용 하위 클래스 2

class cDoxySubClass2 : public cDoxyClass

{

public:

    int getNum(void) { return 2; }

};


--- class.cpp의 코드 ---

#include "class.h"


///////////////////////////////////////////////////////////////////////////////

// Doxygen 예제용 클래스


//! 생성자

cDoxyClass()

{

    m_nNum = 0;

}

//! 소멸자

~cDoxyClass()

{

}


//! @brief 질문 메세지 박스 출력

bool showQuestionBox(

     const TCHAR* msg            //!< 질문 내용

    , const TCHAR* title = NULL    //!< 메세지 박스 제목

    )

{

    m_nNum++;

    if ( IDYES == MessageBox(NULL, msg, title, MB_YESNO | MB_ICONQUESTION | MB_TOPMOST) )

        return true;

    return false;

}


//! 메세지 박스 출력 횟수 얻기

//! @return 메세지 박스 출력 횟수

int getNum(void)

{

    return m_nNum;

}

------

※ 주의사항 : 한글을 혼용하기 위해서 UTF-8로 소스를 작성하시는 것이 좋습니다. 그리고 UTF-8로 비주얼 스튜디오에서 작업하실 때 파일 첫부분에 빈공백을 넣어주어야 정상적으로 작동하는 것을 확인했습니다.

VS2005의 UTF-8 저장하는 법

Visual Studio .NET 2005에서는 파일 메뉴의 저장 고급 옵션에서 파일의 형식을 선택할 수 있습니다. 예전엔 ANSI 파일 말고는 읽지도 못했는데 엄청난(남들 다 하지만 MS는 안하는?) 발전이라고 할 수 있겠습니다. 여기서 서명있는 UTF-8 형식을 선택하면 이후부터 해당 파일은 UTF-8로 저장됩니다.
 그러나 UTF-8은 분명 자동인식 할 수 있는 형식임에도 불구하고 다른 편집기에서 저장한 서명없는 UTF-8 문서는 읽지 못하는 고지식한 면을 보이기도 합니다. 이럴 때는 별 수 없이 ANSI로 다시 저장하고 VS2005에서 읽은 후 서명있는 UTF-8로 바꿔줘야 합니다.
 그리고 이 UTF-8 서명 때문인지 소스 파일의 첫줄에 빈줄을 넣어주어야 Doxygen이 문제없이 인식합니다.


3. 문서 생성 - Step1
 자 위의 소스가 작동하는지 안하는지는 머리 아프게 고민하지 말고 바로 문서를 만들어 보도록 하겠습니다. 우선 DoxyWizard를 실행합니다.

  Wizard 버튼을 눌러서 몇가지 설정을 따라합니다.

Wizard 설정

프로젝트 이름과 버전 혹은 코드명을 적고,
소스코드의 최상위 디렉토리를 선택합니다. (나중에 개별로 추가도 가능합니다.)
그리고 Scan recursively를 체크해서 하위 디렉토리까지 찾도록 했습니다.
마지막으로 문서가 생성될 위치를 선택합니다.

우선 C++ 출력에 최적화 시켰습니다.

출력은 웹문서로만 하게 선택했습니다.(PHP가 된다면 검색도 달아줍니다!)
PDF로도 출력이 가능합니다.

GraphViz로 각종 다이어그램도 추가했습니다.

OK 버튼을 누르고 나면 대다수 설정을 마쳤습니다. 추가로 몇가지 설정을 더 살펴보고 문서를 생성하도록 하겠습니다.

 Expert 버튼을 눌러서 고급 설정으로 들어갑니다.

Expert 설정

Project 탭입니다. 문서 프로젝트의 전반적인 옵션을 설정합니다.
우선 손상된 한글을 고쳐주고 OUTPUT_LANGUAGE를 Korean으로 바꿔줍니다.

그리고 아래쪽으로 내려가면 파일이름을 표시할 때 앞쪽의 긴 디렉토리명을 떼어낼 수 있도록 STRIP_FROM_PATH라는 옵션에 상위 디렉토리를 추가해줍니다.

SHORT_NAMES라는 옵션을 체크하지 않으면 GraphViz에서 오류를 발생시킬 수도 있습니다.

Build 탭으로 이동하면 좀 더 세부적인 옵션들이 있습니다.
EXTRACT_ALL과 EXTRACT_ANON_NSPACES를 체크하지 않으면 네임스페이스에 속하지 않은 함수나 구조체들이 포함되지 않을 수 있습니다.

Input 탭에서는 소스 파일이 있는 디렉토리를 별도로 추가할 수 있습니다.

Source Browser 탭은 문서에 소스를 어떻게 포함시킬지 결정합니다.
SOURCE_BROWSER 옵션에 체크를 하면 문서에 컬러링과 링크를 제공하는 소스 전체를 포함시켜 주기 때문에 문서에서 바로 소스를 확인할 수 있습니다.
그리고 VERBATIM_HEADERS를 사용하면 cpp와 같은 소스를 포함하지 않고 헤더 파일의 소스만 포함시켜서 라이브러리 등을 전달하고자 할 때 꽤나 유용합니다.

HTML 탭에서 생성되는 웹문서의 옵션을 선택할 수 있습니다.
GENERATE_TREEVIEW 옵션을 체크하면 좌측에 프레임으로 이루어진 트리뷰를 추가해 줍니다.
Dot 탭에서는 포함될 각종 그래프 옵션을 설정할 수 있습니다.
특히 CLASS_DIAGRAMS 옵션은 꽤나 유용하므로 체크해서 사용하시면 좋습니다.

물론 설치된 GraphViz의 위치를 설정해주는 것도 잊으시면 안됩니다.

OK 버튼을 눌러서 설정을 적용합니다.
소개된 것 외에도 많은 옵션이 있으므로 Doxygen 문서를 확인하시면서 이것저것 시도해서 확인해 보시는 것도 좋습니다.

4. 문서 생성 - Step2
 우선 설정 파일을 저장해야 합니다. doc 디렉토리를 만들고 Save 버튼을 눌러서 그곳에 저장하겠습니다.

그러면 상태가 '저장됨'으로 바뀝니다.

저장하지 않으면 문서를 생성할 수 없을 뿐더러 아무 경고 없이 이제까지 한 설정이 날아갈 수도 있습니다. 물론 한번 저장해둔 설정은 Load 버튼으로 언제든지 다시 불러서 계속 생성할 수 있습니다.

5. 문서 생성 - Step3
 Doxygen이 작업할 디렉토리를 선택합니다. 로그 파일이나 생성 중의 중간 파일 등이 사용할 위치입니다. 그냥 doc 디렉토리를 선택하겠습니다.


6. 문서 생성 - Step4
 Start 버튼을 눌러서 최종적인 문서를 생성합니다. 만약 오류가 있었다면 로그창에 표시됩니다. 그러면 해당 오류 부분을 바로잡아 주고 다시 생성하면 됩니다. 소스 코드에는 어떠한 위해도 가하지 않으므로 Doxygen의 오류 코드를 심각하게 걱정할 필요는 없습니다. 제 경험상으로는 GraphViz와의 오류가 많았습니다만 한번 작동하는 옵션 조합을 발견하고 나면 문제없이 잘 작동했습니다. SHORT_NAMES라는 옵션을 한번 확인해 보시기 바랍니다.


7. 문서 확인
 바야흐로 생성된 문서의 결과를 확인할 때가 되었습니다. 작업 디렉토리로 선택했던 doc 디렉토리를 보면 html이라는 디렉토리가 생성되었을 것입니다. 이 디렉토리 안의 index.html을 열어서 확인해 보겠습니다.

클래스 계통도까지 삽입되어서 매우 깔금한 문서가 만들어 졌습니다. (그런데 프로젝트 제목의 한글 부분은 손상되었군요. 프로젝트 제목은 한글로 입력하면 안되겠습니다.)

그리고 VERBATIM_HEADERS 옵션을 사용했기 때문에 헤더 파일의 전체 코드가 포함되었습니다.


DoxyWizard의 사용법을 중심으로 Doxygen 사용법을 알아보았습니다.
많은 분들이 Doxygen을 사용하여 문서화의 작업 시간을 단축하셨으면 하는 바램입니다.

 

출처 : http://blog.tinywolf.com/2

3.1 머릿말

소스 한두 개로 이루어진 C/C++ 언어 교양과목 과제물을 제출하는 것이 아니라면 약간만 프로젝트가 커져도 소스는 감당할 수 없을 정도로 불어나게 되고 그것을 일일이 gcc 명령행 방식으로 처리한다는 것은 상당히 곤역스러운 일입니다.

그래서 하나의 프로젝트를 효율적으로 관리하고 일관성있게 관리하기 위하여 Makefile 이라는 형식을 사용하고 make 라는 유틸리티를 사용합니다.

여러분이 리눅스에서 소스 형태로 되어 있는 것을 가져와서 컴파일하게 되면 보통 마지막에는 make 라는 명령, 또는 make <어쩌구> 이런 식으로 치게 됩니다.

make 라는 유틸리티는 보통 현재 디렉토리에 Makefile 또는 makefile 이라는 일정한 규칙을 준수하여 만든 화일의 내용을 읽어서 목표 화일(target)을 만들어냅니다. Makefile의 이름을 다르게 명시하고 싶을 때는 다음과 같이 합니다.

$ make -f Makefile.linux

보통 멀티플랫폼용 소스들은 Makefile.solaris, Makefile.freebsd, Makefile.hp 이런 식으로 Makefile 을 여러 개 만들어두는 경향이 있지요. 또는 적절하게 만들어두어 다음과 같이 make <플랫폼> 라는 식으로 하면 컴파일되도록 하기도 합니다.

$ make linux

이런 일은 보통의 관례일 뿐이죠. 더 예를 들어보자면 이런 식입니다. 우리가 커널 컴파일 작업할 때를 보십시요.

$ make config /* 설정 작업을 한다 */

$ make dep /* 화일 의존성을 검사한다 */

$ make clean /* 만든 화일들을 지우고

깨긋한 상태로 만든다 */

$ make zImage /* zImage(압축커널)를 만든다 */

$ make zlilo /* 커널을 만들고 LILO를 설정한다 */

$ make bzImage /* bzImage(비대압축커널)를 만든다 */

$ make modules /* 커널 모듈을 만든다 */

$ make modules_install /* 커널 모듈을 인스톨한다 */

복잡한 것같아도 우리는 항상 make, make, make ... 일관성있게 make 라고만 쳐주면 됩니다. ^^ 분량이 작은 소스들의 경우에는 일반적으로 다음만 해도 되는 경우가 많죠.

$ make 또는 make all

$ make install

영어권에 사는 사람들에게는 더욱 친밀하게 느껴질 겁니다. 그렇겠죠? ``만들라!''라는 동사를 사용하고 있는 것이고 그 다음에는 그들의 정상적인 어순에 따라 목적어가 나오죠.

$ make install.man

또한 관례상 ``맨페이지'' 같은 것은 별도로 인스톨하도록 배려하는 경우가 많습니다. 프로그램에 대해 잘 아는 사람이라면 맨페이지를 자질구레하게 설치하고 싶지 않을 때도 많으니까요.

다른 사람에게 공개하는 소스라면 더욱 make 를 사용해야 합니다. 그들뿐 아니라 여러분 자신도 make 라고만 치면 원하는 결과가 나올 수 있도록 하는 것이 좋습니다. 많은 소스를 작성하다 보면 여러분 스스로도 까먹기 쉽상입니다.

일단 make를 사용하는 일반적인 관례를 익히는 것이 중요하다고 봅니다. 리눅스 배포판 패키지만 설치하지 마시고 적극적으로 소스를 가져다 컴파일해보십시요. 실력이든 꽁수든 늘기 시작하면 여러분은 더욱 행복해지실 수 있습니다. =)

 

3.2 make 시작해 봅시다.

일관성있게 make라고만 치면 모든 일이 술술 풀려나가도록 하는 마술은 Makefile이라는 것을 어떻게 여러분이 잘 만들어두는가에 따라 결정됩니다. 바로 이 Makefile 을 어떻게 만드는지에 대하여 오늘 알아봅니다.

상황 1)

$ gcc -o foo foo.c bar.c

여기서 foo 라는 실행화일은 foo.c, bar.c 라는 2 개의 소스로부터 만들어지고 있습니다.

여러분이 지금 계속 코딩을 하고 있는 중이라면 이 정도쯤이야 가상콘솔 또는 X 터미널을 여러 개 열어두고 편집하면서 쉘의 히스토리 기능을 사용하면 그만이지만 하루 이틀 계속 해간다고 하면 곤역스러운 일이 아닐 수 없습니다.

자, 실전으로 들어가버리겠습니다. vi Makefile 해서 만들어봅시다. ( 편집기는 여러분 마음 )

foo: foo.o bar.o

gcc -o foo foo.o bar.o

foo.o: foo.c

gcc -c foo.c

bar.o: bar.c

gcc -c bar.c

입력하는데 주의하실 것이 있습니다. 자, 위 화일을 보십시요. 형식은 다음과 같습니다.

목표: 목표를 만드는데 필요한 구성요소들...

목표를 달성하기 위한 명령 1

목표를 달성하기 위한 명령 2

...

Makefile은 조금만 실수해도 일을 망치게 됩니다.

맨 첫번째 목표인 foo 를 살펴보죠. 맨 첫 칸에 foo: 라고 입력하고 나서 foo가 만들어지기 위해서 필요한 구성요소를 적어줍니다. foo가 만들어지기 위해서는 컴파일된 foo.o, bar.o 가 필요합니다. 각 요소를 구분하는데 있어 콤마(,) 같은 건 사용하지 않고 공백으로 합니다.

중요! 중요! 그 다음 줄로 넘어가서는 <탭>키를 누릅니다. 꼭 한 번 이상은 눌러야 합니다. 절대 스페이스키나 다른 키는 사용해선 안됩니다. 목표 화일을 만들어내기 위한 명령에 해당하는 줄들은 모두 <탭>키로 시작해야 합니다. Makefile 만들기에서 제일 중요한 내용입니다. <탭>키를 사용해야 한다는 사실, 바로 이것이 중요한 사실입니다.

foo를 만들기 위한 명령은 바로 gcc -o foo foo.o bar.o 입니다.

다시 한 번 해석하면 이렇습니다. foo 를 만들기 위해서는 foo.o와 bar.o가 우선 필요하다.( foo: foo.o bar.o )

일단 foo.o, bar.o 가 만들어져 있다면 우리는 gcc -o foo foo.o bar.o 를 실행하여 foo 를 만든다.

자, 이제부터 사슬처럼 엮어나가는 일만 남았습니다.

foo를 만들려고 하니 foo.o와 bar.o 가 필요합니다!

그렇다면 foo.o는 어떻게 만들죠?

foo.o: foo.c

gcc -c foo.c

바로 이 부분입니다. foo.o는 foo.c를 필요로 하며 만드는 방법은 gcc -c foo.c입니다.

그 다음 bar.o 는 어떻게 만들죠?

bar.o: bar.c

gcc -c bar.c

이것을 만들려면 이것이 필요하고 그것을 만들기 위해서는 또 이것이 필요하고...

소스를 만들어서 해봅시다.

foo.c 의 내용

 

extern void bar ( void );

int

main ( void )

{

bar ();

return 0;

}

bar.c 의 내용

 

#include <stdio.h>

void

bar ( void )

{

printf ( "Good bye, my love.\n" );

}

Makefile을 위처럼 만들어두고 그냥 해보죠.

$ make 또는 make foo

gcc -c foo.c

gcc -c bar.c

gcc -o foo foo.o bar.o

명령이 실행되는 순서를 잘 보십시요. 여기서 감이 와야 합니다. ^^

$ ./foo

Good bye, my love.

다시 한 번 실행해볼까요?

$ make

make: `foo' is up to date.

똑똑한 make는 foo를 다시 만들 필요가 없다고 생각하고 더 이상 처리하지 않습니다.

이번에는 foo.c 를 약간만 고쳐봅시다. return 0; 라는 문장을 exit (0); 라는문장으로 바꾸어보죠. 그리고 다시 한 번 다음과 같이 합니다.

$ make

gcc -c foo.c

gcc -o foo foo.o bar.o

자, 우리가 원하던 결과입니다. 당연히 foo.c 만 변화되었으므로 foo.o 를 만들고 foo.o가 갱신되었으므로 foo도 다시 만듭니다. 하지만 bar.c는 아무변화를 겪지 않았으므로 이미 만들어둔 bar.o 는 그대로 둡니다.

소스크기가 늘면 늘수록 이처럼 똑똑한 처리가 필요하지요.

$ rm -f foo

$ make

gcc -o foo foo.o bar.o

이것도 우리가 원하던 결과입니다. foo 실행화일만 살짝 지웠더니 make는 알아서 이미 있는 foo.o, bar.o 를 가지고 foo 를 만들어냅니다. :)

상황 2) 재미를 들였다면 이번에는 청소작업을 해보기로 합시다.

clean:

rm -f foo foo.o bar.o

이 두 줄을 위에서 만든 Makefile 뒷부분에 추가해보도록 합시다.

$ make clean

rm -f foo foo.o bar.o

$ make

gcc -c foo.c

gcc -c bar.c

gcc -o foo foo.o bar.o

make clean이라는 작업 또한 중요한 작업입니다. 확실히 청소를 보장해주어야 하거든요.

make, make clean 이런 것이 되면 상당히 멋진 Makefile 이라고 볼 수 있죠? 이번 clean 에서 보여드리고자 하는 부분은 이런 것입니다.

우리의 머리 속에 clean 이라는 목표는 단지 화일들을 지우는 일입니다.

clean: 옆에 아무런 연관 화일들이 없지요?

그리고 오로지 rm -f foo foo.o bar.o 라는 명령만 있을 뿐입니다. clean이라는 목표를 수행하기 위해 필요한 것은 없습니다. 그러므로 적지 않았으며 타당한 make 문법입니다.

상황 3)

all: foo

이 한 줄을 Makefile 맨 앞에 넣어두도록 합시다.

$ make clean

$ make all

gcc -c foo.c

gcc -c bar.c

gcc -o foo foo.o bar.o

이번예는 all 이라는 목표에 그 밑에 나오는 다른 목표만이 들어있을 뿐, 아무런 명령도 없는 경우입니다. 보통 우리는 make all 하면 관련된 모든 것들이 만들어지길 원합니다.

all: foo1 foo2 foo3

foo1: <생략>

foo2: <생략>

foo3: <생략>

이런 식으로 해두면 어떤 장점이 있는지 알아봅시다.

보통 make all 하면 foo1, foo2, foo3가 모두 만들어집니다. 그런데 어떤 경우에는 foo1만 또는 foo2만을 만들고 싶을 때도 있을 겁니다. 괜히 필요없는 foo3 같은 것을 컴파일하느라 시간을 보내기 싫으므로 우리는 단지 다음과 같이만 할 겁니다.

$ make foo1

$ make foo2

물론 일반적으로 다 만들고 싶을 때는 make all 이라고만 하면 됩니다.

make all 이건 아주 일반적인 관례이지요. 그리고 외우기도 쉽잖아요?

 

3.3 꼬리말 규칙, 패턴 규칙

잘 관찰해보시면 어쩌구.c -----------> 어쩌구.o 라는 관계가 매번 등장함을 알 수 있습니다. 이것을 매번 반복한다는 것은 소스 화일이 한 두 개 정도일 때야 모르지만 수십 개가 넘게 되면 정말 곤역스러운 일이라고 하지 않을 수 없지요.

다음과 같은 표현을 Makefile 에서 보는 경우가 많을 겁니다.

.c.o:

gcc -c ${CFLAGS} $<

여기서 .c.o 의 의미를 생각해보겠습니다. ".c 를 입력화일로 받고 .o 화일을 만든다"

gcc -c ${CFLAGS} $<

이 문자을 보면 일단 눈에 띄는 것은 ${CFLAGS}라는 표현과 $< 라는 암호와도 같은 표현입니다. 여기서는 일단 $< 라는 기호의 의미를 알아보겠습니다.

유닉스에서 쉘을 잘 구사하시는 분들은 눈치채셨을 겁니다. 작다 표시(<)는 리다이렉션에서 입력을 의미하는 것을 아십니까? 그렇다면 $< 는 바로 .c.o 라는 표현에서 .c 즉 C 소스 화일을 의미합니다.

예를 들어 foo.c 가 있다면 자동으로

gcc -c ${CFLAGS} foo.c

가 수행되며 gcc 에 -c 옵션이 붙었으므로 foo.o 화일이 만들어질 것입니다.

 

3.4 GNU make 확장 기능

.c.o 라는 전통적인 표현 말고 GNU 버전( 우리가 리눅스에서 사용하는 것은 바로 이것입니다 )의 make 에서 사용하는 방법을 알아봅시다.

위에서 예로 든 것을 GNU 버전의 make 에서 지원하는 확장문법을 사용하면 다음과 같습니다.

%.o: %.c

gcc -c -o $@ ${CFLAGS} $<

그냥 설명 전에 잘 살펴보시기 바랍니다.

우리가 위에서 알아보았던 표준적인 .c.o 라는 꼬리말 규칙(Suffix rule)보다 훨씬 논리적이라는 것을 발견하셨습니까?

우리가 바로 전 강의에서 main.o : main.c 이런 식으로 표현한 것과 같은 맥락이지요? 이것을 우리는 패턴 규칙(Pattern rule)이라고 부릅니다. 콜론(:) 오른쪽이 입력 화일이고 왼쪽이 목표 화일입니다. 화일명 대신 퍼센트(%) 문자를 사용한 것만 유의하면 됩니다. 여기서 foo.c 라는 입력화일이 있다면 % 기호는 foo 만을 나타냅니다.

gcc -c -o $@ ${CFLAGS} $<

라는 표현을 해석해봅시다. ( 후 마치 고대 문자판을 해석하는 기분이 안드십니까? ^^ )

$< 는 입력화일을 의미하고 $@ 은 출력화일을 의미합니다. .c.o와 같은 꼬리말 규칙과 별 다를 바 없다고 생각하실 지 모르나 -o $@ 를 통하여 .o 라는 이름 말고 전혀 다른 일도 해낼 수 있습니다.

다음 예는 그냥 이런 예가 있다는 것만 한 번 보아두시기 바랍니다.

%_dbg.o: %.c

gcc -c -g -o $@ ${CFLAG} $<

DEBUG_OBJECTS = main_dbg.o edit_dbg.o

edimh_dbg: $(DEBUG_OBJECTS)

gcc -o $@ $(DEBUG_OBJECTS)

%_dbg.o 라는 표현을 잘 보십시요. foobar.c 라는 입력화일(%.c)이 있다면 % 기호는 foobar 를 가리키므로 %_dbg.o 는 결국 foobar_dbg.o 가 됩니다.

기호정리

$< 입력 화일을 의미합니다. 콜론의 오른쪽에 오는 패턴을 치환합니다.

$@ 출력 화일을 의미합니다. 콜론의 왼쪽에 오는 패턴을 치환합니다.

$* 입력 화일에서 꼬리말(.c, .s 등)을 떼넨 화일명을 나타냅니다.

역시 GNU 버전이라는 생각이 들지 않으시는지요?

 

3.5 매크로(Macro) 기능

앞에서도 잠깐씩 나온 ${CFLAGS} 라는 표현을 보도록 합시다.

gcc 옵션도 많이 알고 make을 능수능란하게 다룰 수 있는 사람들은 다음과 같이 해서 자신의 프로그램에 딱 맞는 gcc 옵션이 무엇인지 알아내려고 할 것입니다.

$ make CFLAGS="-O4"

$ make CFLAGS="-g"

이제 매크로에 대한 이야기를 나눠볼까 합니다. 이 이야기를 조금 해야만 위의 예를 이해할 수 있다고 보기 때문입니다. 그냥 시험삼아 해보십시다. 새로운 것을 배우기 위해서는 꼭 어떤 댓가가 와야만 한다는 생각을 버려야겠지요?

myprog: main.o foo.o

gcc -o $@ main.o foo.o

이것을 괜히 어렵게 매크로를 이용하여 표현해보기로 하겠습니다.

OBJECTS = main.o foo.o

myprog: $(OBJECTS)

gcc -o $@ $(OBJECTS)

여러분은 보통 긴 Makefile을 훔쳐 볼 때 이런 매크로가 엄청나게 많다는 것을 보신 적이 있을 겁니다. ^^

ROOT = /usr/local

HEADERS = $(ROOT)/include

SOURCES = $(ROOT)/src

예상하시듯 위에서 HEADERS는 당연히 /usr/local/include가 되겠지요?

다음과 같은 문장도 있습니다.

ifdef XPM

LINK_DEF = -DXPM

endif

$ make XPM=yes

이렇게 하면 ifdef endif 부분이 처리됩니다.

자, make CFLAGS="-O" 이런 명령을 한 번 봅시다. ${CFLAGS}에서 {} 표현은 유닉스 쉘에서 변수값을 알아낼 때 쓰는 표현입니다. CFLAGS 값을 여러분이 Makefile에 고정적으로 집어넣지 않고 그냥 make 만 실행하는 사람에게 선택권을 주기 위해서 사용하거나 자기 스스로 어떤 옵션이 제일 잘 맞는지 알아보기 위해서 사용합니다. 다른 옵션으로 컴파일하는 것마다 일일이 다른 Makefile을 만들지 말고 가변적인 부분을 변수화하는 것이 좋습니다.

 

3.6 마지막 주의 사항

target:

cd obj

HOST_DIR=/home/e

mv *.o $HOST_DIR

하나의 목표에 대하여 여러 명령을 쓰면 예기치 않은 일이 벌어집니다. 기술적으로 말하자면 각 명령은 각자의 서브쉘에서 실행되므로 전혀 연관이 없습니다. -.- cd obj 도 하나의 쉘에서 HOST_DIR=/home/e도 하나의 쉘에서 나머지도 마찬가지입니다. 각기 다른 쉘에서 작업한 것처럼 되므로 cd obj 했다

하더라도 다음번 명령의 위치는 obj 디렉토리가 아니라 그대로 변함이 없이 현재 디렉토리입니다. 세번째 명령에서 HOST_DIR 변수를 찾으려 하지만 두번째 명령이 종료한 후 HOST_DIR 변수는 사라집니다.

target:

cd obj ; \

HOST_DIR=/hom/e ; \

mv *.o $$HOST_DIR

이렇게 적어주셔야 합니다. 세미콜론으로 각 명령을 구분하지요. 처음 두 줄의 마지막에 쓰인 역슬래쉬(\) 문자는 한 줄에 쓸 것을 여러 줄로 나누어 쓴다는 것을 나타내고 있습니다.

주의! 세번째 줄에 $HOST_DIR이 아니라 $$HOST_DIR인 것을 명심하십시요. 예를 하나 들어보죠. ^^

all:

HELLO="안녕하세요?";\

echo $HELLO

Makefile의 내용을 이렇게 간단하게 만듭니다.

$ make

HELLO="안녕하세요?";\

echo ELLO

ELLO

<verb>

우리가 원하는 결과가 아니죠?

$HELLO를 $$HELLO로 바꾸어보십시요.

<verb>

$ make

HELLO="안녕하세요?";\

echo $HELLO

안녕하세요?

all:

@HELLO="안녕하세요?"; echo $$HELLO

명령의 맨 처음에 @ 문자를 붙여봅시다.

$ make

안녕하세요?

 

3.7 잠시 마치면서

Makefile에 대한 내용은 이것보다 훨씬 내용이 많습니다. 하지만 모든 것을 다 알고 시작할 수는 없겠지요? 이 정도면 어느 정도 충분하게 창피하지 않을 정도의 Makefile을 만들 수 있습니다.

참고로 autoconf/automake라고 하는 아주 훌륭한 GNU make 유틸리티를 시간나면 배워보시는 것도 좋습니다.

시간을 내서 리눅스에서의 C 프로그래밍에 필요한 다른 여러 가지 유틸리티들( 간접적이든 직접적이든 grep, awk, rcs, cvs 등 )의 간단/실전 사용법도 올려드릴까 생각 중입니다. ^^

'리눅스 서버에 대해서 > 리눅스 팁들' 카테고리의 다른 글

쉘 프로그래밍 강좌  (0) 2013.01.10
GNU Make 강좌  (0) 2013.01.07
이클립스에서 C++ 환경 만들기  (0) 2012.12.10
Samba Domain Setting  (0) 2010.08.29
vi(Visual) Editor 사용법  (0) 2008.06.15

+ Recent posts