일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
[GGG] 연가시님께서 제보해 주신 버그 패치 입니다. 문제점은 기본 인코딩을 유니코드로 해 둘 경우 새로 연 파일에 적용이 되지 않는다는 것입니다. 아래 링크는 연가시 님께서 제작하신 문제점 동영상입니다. http://fuwafuwa.tistory.com/7 아래는 문제점을 패치한 실행 파일입니다. Notepad++이 설치된 폴더에 덮어 씌우시면 됩니다. |
[GGG] 저는 작업할 때 MP3를 틀어놓고 하는 편입니다. 뭐 MP3 프로그램이야 워낙 많아서 아무거나 쓰고 있는데, 늘 느끼는 문제가 MP3 파일마다 녹음된 볼륨이 제각각이란 것이었죠. ㅠㅠ 똑같은 볼륨으로 들어도 어떤거는 굉장히 시끄럽고, 어떤거는 조용하다는 것이 문제점입니다. 한때는 귀차니즘에도 불구하고 노멀라이징 유틸리티를 구해서 볼륨을 정규화 시키는 작업도 해보았지만, 별로 소용이 없었습니다. 그러다 요즘 갑자기 파일마다 볼륨을 기억하고 있으면 좋겠다라는 생각을 하게되었습니다. 저 대신 프로그램이 파일 재생할때 볼륨을 알아서 조절해 주는 것이죠. ㅎㅎ^^ 그래서 만들어 보았습니다. 초간단 MP3 플레이어 입니다. 볼륨 기억 외에는 아무런 기능도 없습니다. ㅋㅋ
시작하면 왼쪽 위와 같은 화면이 표시됩니다. 파일 추가는 파일을 프로그램으로 드래그/드롭 하시면 됩니다. 메뉴는 프로그램에서 오른쪽 버튼을 누르면 튀어나옵니다. 재생을 하면 오른쪽 그림과 같이 표시 됩니다. 프로그레스바를 움직여서 특정 위치로 옮겨갈 수 있습니다. 가운데 표시된 28%가 볼륨 입니다. ![]() 줄이면 이렇게 표시됩니다. 이렇게 띄워놓고 사용하시는 분들은 거의 없겠지만.. ㅋㅋ 사용해 보고 싶으신 분들은 아래 압축 파일을 다운받아서 실행 파일을 더블 클릭하시면 됩니다. 실행 파일만 있으면 되고, 재생 목록은 같은 폴더의 ini 파일에 저장합니다. 별도의 레지스트리나 파일은 사용하지 않습니다. ![]() |
[GGG]
요롷게 생긴 색깔 추출 유틸리티입니다. 화면상의 픽셀의 RGB 값을 추출하는 용도죠. 스킨 작업이나 Visual C++ 컬러 설정할 때 요긴하게 쓰실 수 있습니다. 아래 페이지에 가시면 자세한 설명을 보실 수 있습니다. http://blog.pczet.com/tt/45 |
[GGG] Notepad++ 4.1.2 버전의 경우 형식 메뉴에서 인코딩 방식을 유니코드로 변경한 다음 ANSI로 돌아오면 코드 페이지가 엉뚱하게 설정됩니다. 그래서 한글 조합이 이상하게 되는 현상이 발생하죠. 이는 일본어, 중국어등의 특수한 코드 페이지를 사용하는 곳에서는 공통으로 발생하는 현상입니다. 아래 실행 파일로 덮어 씌우면 제대로 된 코드 페이지로 설정됩니다. 제가 지금껏 제공했던 Scintilla용 IME 패치에서 문제점으로 지적된 것들을 고친 버전입니다. 이 버전은 Scintilla를 DLL 형태로 사용하는 대다수 프로젝트에 모두 사용할 수 있습니다. Notepad++, TSVN 아무 때나 DLL만 덮어 씌우면 됩니다. 패치에 사용된 소스는 현재 Scintilla 홈페이지에서 배포하고 있는 1.74 버전입니다. 수정 내용은 아래와 같습니다.
삽질 에피소드 Scintilla의 Undo는 다른 것들과는 조금 다릅니다. 대표적인 예가 치환의 경우입니다. 블록을 잡고 치환할 경우 다른 에디터들은 치환을 하나의 Undo 과정으로 처리합니다. 하지만 Scintilla는 삭제, 추가라는 두 단계의 Undo 과정을 거치죠. 어제 테스트 하던 중 발견한 이상한 현상은 이런 것이었습니다. "abcd^"상태에서 d를 지웁니다. "abc^"가 되겠죠. 여기서 Undo를 수행하면 "abcd^"가 되는게 정상입니다. 그런데 "abc^d"가 되는 겁니다. ^는 카렛의 위치를 나타냅니다. 그래서 저는 Undo 시에 카렛 위치가 계산이 잘못되어서 그랬구나 하고 관련 부분을 고쳤습니다. 에러가 나더군요. 계산이 중복 적용되면서 나는 에러였습니다. 이상한 일이었습니다. 소스 상에는 모든 것이 완벽한데 카렛은 계속 앞에 있는 것이었죠. 결국 알고 봤더니 원인은 제가 테스트 하던 Notepad++이란 프로그램에 있었습니다. 그쪽에서 자꾸 앞으로 가도록 메시지를 날리고 있었던 것이었죠. 제 PC에 설치된 버전이 4.0.2였는데, 4.1.2를 설치하니 그 문제가 해결되더군요. 이 것 때문에 날린 시간을 생각하면 ^^ |
[GGG]![]() 어제 DrawThemeBackground와 관련된 정보를 검색하다가 알게된 사이트입니다. 제가 처음 본 페이지가 위 캡쳐 화면입니다. 내용을 보기 전에. 오 외국 사이트치고 디자인이 깔쌈한데 라고 잠시 생각을 했습니다. 그러고는 누가 운영하는 곳인지 알아볼라고 home을 눌러 봤습니다. Welcome to shell:revealed, the home of the Windows Client team. We're the folks that build the core user interface for Windows and we love every minute of it! the home of the Windows Client team. 대단한 곳이었군요. 바로 RSS를 추가 했습니다. 아직 글을 다 읽어 보진 않았지만 Windows Client team에서 뿜어져 나오는 포스가 ㅋㅋ 심심할때 하나씩 읽어봐야 겠습니다. 과연 정말 쉘에 관한 모든것을 알려줄지는 ㅎㅎ |
[GGG]![]() 여러분의 즐거운 TSVN 생활을 위한 TSVN 패치입니다. 한글 IME가 내부에서 조합되고, 캐럿 모양이 정상적으로 표시됩니다. 패치를 적용하시려면 TSVN이 설치된 폴더에 있는 bin 폴더에 아래에 있는 SciLexer.dll을 덮어 씌우면 됩니다. 늘 그렇듯이 버그는 댓글이나 codewiz앳지메일닷컴으로 보내주시면 됩니다. 패치가 만들어 질 수 있도록 해주신 pysrasis님께 감사의 말을 전합니다. 그럼 즐TSVN 하세요. P.S.) Notepad++에 있는 것도 동일한 dll인데 동작이 조금 틀리더군요. Notepad++ dll을 덮어 씌우면 IME가 내부에서 조합되긴 하는데 캐럿이 이상하게 표시됩니다. 아마 야매로 패치해서 그런것 같아요 ㅋ |
[GGG]![]() 비스타하면 딱 떠오르는게 보안이죠. 프로세스에도 이런 보안적인 속성이 강화되었습니다. 그 중에 새로 생긴게 보호된 프로세스 입니다. 이 보호된 프로세스의 목적은 캡처를 방지하는 겁니다. 기존에 스트림으로 전송되는 정보들을 중간에 후킹해서 손쉽게 캡쳐하던 것을 막기 위한 것으로 생각하면 됩니다. 위에 보면 audiodg.exe가 보호된 프로세스입니다. 그런데 위 목록에서 보면 다른 프로세스와 별반 다른 점이 없습니다. 그렇다면 어떻게 저놈이 보호된 프로세스인 줄 알 수 있을까요? 속성을 보면 알 수 있습니다. audiodg.exe에서 오른쪽을 클릭해서 속성을 열어보면 여러 가지 정보를 볼 수 있습니다. 아래 화면은 audiodg.exe의 속성 화면을 캡쳐한 것입니다. 빨간색으로 칠해진 부분이 n/a로 되어 있죠. 구할 수 없음을 뜻합니다. 이를 통해서 역으로 보호된 프로세스라고 판단하는 것이죠. 정보가 구해지면 일반 프로세스이고, 아래 정보들이 구해지지 않으면 보호된 프로세스인 겁니다.
비스타에서 OpenProcess로 보호된 프로세스를 열려고 하면 실패합니다. 권한이 없다고 하죠. 최소 권한으로 생각되는 PROCESS_QUERY_INFORMAITON을 집어넣어도 권한이 없다고 중얼거립니다. 당황스럽죠. 보호된 프로세스가 생기면서 프로세스의 권한도 하나가 추가되었습니다. PROCESS_QUERY_LIMITED_INFORMATION (0x1000)이죠. 보호된 프로세스 핸들은 이 권한을 통해서 획득할 수 있습니다. 또한 권한 이름에서도 알 수 있듯이 이 권한으로 구할 수 있는 정보는 제한적입니다. 보호된 프로세스와 관련된 짧은 FAQ Q1. 보호된 프로세스는 어떻게 만들어지나요? 현재까지 알려진 바로는 비스타에 포함된 Protected Media Path에 의해서만 생성된다고 합니다. 이 프로세스에 포함되기 위해서는 특수한 윈도우 인증 과정을 거쳐야 한다고 합니다. Q2. 보호된 프로세스를 우회하는 방법은 없나요? 물론 있습니다. 커널 모드 드라이버를 사용하면 가능합니다. Alex Ionescu의 "Why Protected Processes Are A Bad Idea"란 글에 보호된 프로세스를 뚫는 툴에 관한 소개가 있습니다. 재미난 사실은 공식 문서 끝부분에 커널 모드 드라이버를 사용해서 보호된 프로세스를 우회하도록 만들지 말라고 언급하고 있습니다. Alex Ionescu 씨에게는 그 이야기가 커널 모드 드라이버를 사용하면 우회가 가능하다는 이야기로 들렸다고 합니다. 그렇긴 하죠. ㅋ Q3. 보호된 프로세스가 좋은 건가요? 미디어 관련 저작권을 보호한다는 좋은 취지하에 개발된 기술이지만 다음과 같은 점들은 약점으로 생각됩니다.
Q4. 보호된 프로세스와 관련한 더 많은 자료는 어디에 있나요? 보호된 프로세스와 관련된 공식 문서 http://www.alex-ionescu.com/?p=34 http://channel9.msdn.com/Showpost.aspx?postid=233976 http://blogs.technet.com/steriley/archive/2006/07/21/442870.aspx Making it Clear Just Why Protected Processes are a Bad Idea |
[GGG] 몇 일 전 콘솔 프로그램에서 타이머를 사용하는 프로그램을 작성할 일이 있었습니다. SetTimer 함수에 첫 번째 인자를 NULL로 넣으면 메시지로 날라오지 않고 콜백으로 동작하죠. 그래서 당연히 그렇게하면 콘솔 프로그램에서도 사용할 수 있다고 생각했습니다. 왠걸. 타이머 콜백이 호출이 되질 않는 것입니다. 코드를 고쳐가며 쌩 쑈를 해보았지만 안되었습니다. 그러다 포기하고 구글님에게 물으러 갔죠. INFO: SetTimer() Should Not Be Used in Console Applications 구글님은 역시나 저를 실망시키지 않았습니다. 내용의 요는 SetTimer가 동작하기 위해서는 메시지 루프를 포함하고 있어야 한다는 것 입니다. 그동안 저는 SetTimer가 그냥 커널에서 계산해서 호출해 주는 정도로 알고 있었는데, 그게 아니었나 봅니다. 그래서 ReactOS 홈페이지에서 관련 소스를 한번 찾아봤습니다. ReactOS ReactOS는 윈도우 클론을 만들어 보자는 취지의 오픈 소스 프로젝트입니다. 그래서 윈도우의 중요한 코어 부분을 직접 구현하는 작업을 하고 있죠. 굉장한 작업이죠. 공개가 하나도 되지 않은 윈도우 운영체제를 리버싱해서 똑같은 것을 만드는 일이니까요. 그런데 참 신기한 사실은 몇 해전 유출되었던 윈도우 2000의 소스 코드와 ReactOS의 소스 코드가 굉장히 유사하다는 점 입니다. 비 공개된 커널 구조체의 필드명 같은 것들 말이죠. 함수명도 마찬가지 입니다. 그 원인이야 어찌됐는 저희한테는 쌩큐죠. 윈도우 소스 코드를 홈페이지를 통해서 볼 수 있으니 말이죠. 저도 예전 유출되었던 윈도우 소스 코드를 ctag으로 만들어서 보곤 했는데 doxygen으로 작업된 것이 백만배 정도는 더 편리 하더군요. 전체적인 함수의 흐름은 다음과 같습니다. 마지막 함수를 보면 삘이 확 오죠. ㅋ SetTimer -> NtUserSetTimer -> IntSetTimer -> MsgSetTimer MsgSetTimer 함수의 핵심 부분은 아래와 같이 구현되어 있습니다. 타이머가 발생해야 하는 시간을 현재 시간 기준으로 계산하고 그 타이머 객체를 메시지 큐에 추가하는 것이 전부죠. 이제야 왜 SetTimer에 메시지 큐가 필요한지 이해가 되죠. 돌이켜 생각해 보니 바쁜 커널이 모든 타이머를 일일히 호출해 주는 건 정말 쓸데없는 짓인것 같네요. [CPP]KeQuerySystemTime(&CurrentTime); Timer->ExpiryTime.QuadPart = CurrentTime.QuadPart + (ULONGLONG) Period * (ULONGLONG) 10000; Timer->Period = Period; Timer->TimerFunc = TimerFunc; InsertTimer(MessageQueue, Timer);[/CPP] 콘솔 프로그램에서 메시지 큐 없이 SetTimer를 써야 한다면 위 글에서 나와있는대로 멀티미디어 타이머를 사용하는 방법이 있습니다. 그것도 여의치 않다면 별도의 스레드에서 직접 호출해 주는 방법 밖에는 없습니다. 그런데 이 때 한 가지 주의 해야 할 점은 멀티미디어 타이머든 자신이 직접 만든 스레드에서 호출하든 이 것들은 원래 스레드 컨텍스트에서 호출되지 않는다는 점 입니다. 따라서 타이머 프로시저가 공유 자원에 접근하는 경우라면 동기화에 신경을 써야 합니다. |
[GGGG]NIM 게임은 테이블 위에 놓여진 13개의 동전을 번갈아 가면서 1~3개까지 가져가서 마지막 남은 동전을 가져가는 사람이 지는 게임이다. NIM 게임에서 컴퓨터가 이기기 위한 알고리즘을 다양한 방법으로 구현해 보도록 하자. 1. 결정 트리 13개라는 동전의 개수는 우리가 한번에 생각하기에는 너무 많은 동전의 개수다. 따라서 게임이 진행된 후 자기 차례에 4개의 동전이 남은 상황을 생각해 보자. 이 상황에서 벌어질 수 있는 모든 일이 <그림 1>에 나와있다. <그림 1>과 같은 트리를 보통 결정 트리 내지는 게임 트리라고 부른다. 이 트리는 동전이 4개 남은 상황에서 앞으로 발생할 수 있는 모든 경우를 담고 있다. 트리를 보는 방법은 간단하다. 루트에 있는 4는 현재 동전이 네 개 남아있음을 나타낸다. 자식으로 3, 2, 1이 있는 것을 볼 수 있다. 각각은 우리가 한 개, 두 개, 세 개의 동전을 가져간 경우에 테이블에 남게 될 동전의 개수다. 각각의 상황에서 상대방이 선택할 수 있는 모든 경우가 그 노드의 자식으로 있다. 예를 들면, 3개가 남은 상황에서는 상대방의 선택에 따라 테이블에 동전이 두 개, 한 개, 내지는 없을 수 있는 것이다. 자 그렇다면 4개가 남은 상황에서 우리는 이기기 위해서 몇 개의 동전을 가져가야 할까? 당연히 똑똑한 플레이어라면 3개를 집을 것이다. 왜냐하면 세 개를 가져가고 나면 테이블엔 동전이 하나 밖에 남지 않아 무조건 이기기 때문이다. 반면에 한, 두 개를 가져간 경우엔 상대방의 선택에 따라 우리가 질 수도 있다. ![]() 좀 더 복잡한 상황을 생각해 보자. 5개, 6개, 7개, 8개가 테이블에 남은 상황에 대한 결정 트리가 <그림 2>에 나와있다. 5개 남은 경우엔 이길 수 있는 방법이 없고, 6개 남은 경우엔 하나를 가져감으로써 이길 수 있다. 나머지 경우에 대해서도 곰곰 생각해 보자. ![]() 2. 분할 정복법 (Divide-and-Conquer) 분할 정복법은 큰 문제를 여러 개의 작은 문제로 분할한 다음 각각의 결과를 결합해서 큰 문제에 대한 해답을 찾는 방법이다. 큰 문제에서 점차적으로 작은 문제로 내려가는 형태를 취하기 때문에 이런 방식의 접근 법을 하향식(top-down) 접근법이라고 부른다. 앞서 살펴본 결정 트리를 위에서 아래로 내려가면서 문제를 푼다고 생각하면 간단하다. <리스트 1>에 분할 정복법을 사용한 코드가 나와있다. IsBad는 테이블에 coins의 개수만큼 동전이 남아있는 경우에 상대방이 이길 수 있는지 없는지를 나타낸다. IsBad(1)은 당연히 TRUE가 된다. IsBad(2), IsBad(3), IsBad(5)등의 리턴 값을 앞서 게임 트리를 보면서 생각해 보도록 하자. GetCoinCount는 테이블에 n개의 동전이 남았을 때 우리가 이기기 위해서 선택해야 하는 동전의 개수를 리턴 하는 함수다. GetCoinCount(4)는 3을 리턴 한다. 만약 n개의 동전이 남은 상황에서 이길 수 있는 방법이 없다면 0을 리턴 한다. 따라서 GetCoinCount(1), GetCoinCount(5)는 0을 리턴 한다. 리스트 1 top-down 방식으로 풀어본 NIM 게임 [CPP]int GetCoinCount(int coins); int IsBad(int coins) { if(coins <= 1) return TRUE; else return GetCoinCount(coins) == 0; } int GetCoinCount(int coins) { for(int i=1; i<=3; ++i) { if(coins - I <= 0) break; if(IsBad(coins - i)) return i; } return 0; }[/CPP] 분할 정복법은 코드를 작성하기 쉽고 직관적이라는 장점이 있다. 하지만 분할 정복법의 가장 큰 단점은 결정 트리가 커질수록 경우의 수가 많아지고 반복적인 부분이 늘어나기 때문에 코드의 수행 속도가 비 정상적으로 느려진다는 단점이 있다. 얼마나 반복적인 부분이 많이 호출 되는지 알고 싶은 독자는 GetCoinCount, IsBad 함수의 시작 부분에 각각 printf를 추가한 다음 GetCoinCount(4)를 호출해 보도록 하자. 3. 동적 계획법 (Dynamic Programming) 동적 계획법 또한 분할 정복법과 마찬가지로 작은 문제의 답을 통해서 큰 문제의 답을 구한다는 점에서는 비슷하다. 하지만 동적 계획법은 분할 정복법과는 달리 한번 구해진 답을 계속 계산하지 않고 메모리에 저장해 두고 참고한다. 분할 정복법과 동적 계획법의 차이는 <그림 1>과 <그림 2>의 차이라고 할 수 있다. <그림 1>에는 동일한 노드가 여러 번 그려져 있다. 2개가 남은 경우에 대한 결정 트리가 두 번 포함된 것을 보면 알 수 있다. 하지만 <그림 2>에서는 이미 구해진 트리에 대한 부분은 표시를 하지 않았다. 전자가 분할 정복법 이라고 한다면, 후자의 그림은 동적 계획법이 된다. 분할 정복법이 트리를 위에서 아래로 타고 내려오는 형태라면, 동적 계획법은 트리를 하나의 노드에서 점진적으로 키워 나가는 형태를 취한다. 그래서 동적 계획법은 상향식(bottom-up) 접근 방법이다. 동적 계획법은 일반적으로 테이블을 사용해서 구현된다. 작은 문제에 대한 해답을 테이블에 기록해 둔 다음 이후에 그 값이 필요하다면 테이블을 참조해서 이미 구해진 답을 이용하는 것이다. <리스트 2>에는 NIM 게임 테이블을 생성시키는 코드가 나와있다. GenerateNimTable 함수에 생성할 테이블 포인터와 개수를 입력하면 개수만큼 답을 구해서 테이블을 채워준다. win은 해당 노드에서 우리가 이길 수 있는지 없는지를 나타낸다. solution에는 각각의 선택에 대한 승, 패 여부가 기록된다. T가 GenerateNimTable 함수를 통해 생성한 테이블이라고 할 때, 5개가 남은 상황에서 우리가 이길 수 있는지 여부는 T[5].win을 참고하면 된다. 만약 이길 수 있는 노드라면 몇 개를 가져가야 이길 수 있는지는 T[5].solution[0], T[5].solution[1], T[5].solution[2]를 살펴보면 알 수 있다. 리스트 2 NIM 게임 테이블을 생성하는 코드 [CPP]#define MAX_SEL 3 typedef struct _NIMITEM { bool win; bool solution[MAX_SEL+1]; } NIMITEM, *PNIMITEM; void GenerateNimTable(PNIMITEM tb, int last) { tb[1].win = false; for(int i=1; i<=MAX_SEL; ++i) tb[1].solution[i] = false; bool win; for(int i=2; i<=last; ++i) { win = false; for(int j=1; j<=3; ++j) { if(i-j > 0) { if(tb[i-j].win) tb[i].solution[j] = false; else { tb[i].solution[j] = true; win = true; } } else tb[i].solution[j] = false; } tb[i].win = win; } }[/CPP] 일반적으로 동적 계획법은 분할 정복법보다 이해하기 힘들고, 덜 직관적이다. 하지만 수행 속도는 분할 정복법보다 훨씬 빠르다는 장점이 있다. 왜냐하면 반복적인 부분을 다시 계산하지 않기 때문이다. 4. 휴리스틱 휴리스틱이란 우리가 경험이나 관찰에 의해서 알고 있는 지식을 통해서 알고리즘을 설계하는 것을 말한다. 교통 신호 제어 시스템의 알고리즘을 설계할 때 출, 퇴근 시간에 붐비는 도로에 더 많은 가중치를 두는 것이 그러한 예라고 할 수 있다. <표 1>은 앞서 살펴보았던 동적 계획법으로 만들어진 테이블의 결과를 담고 있다. 테이블을 정렬해 두어서 규칙이 쉽게 눈에 들어올 것이다. 남은 동전의 개수에서 1을 뺀 다음 4로 나눈 나머지가 이기기 위해서 우리가 선택해야 하는 동전의 개수다. 여기서 0이 나오면 그 상황에서 우리가 반드시 이길 수 있는 방법은 없다는 것을 나타낸다. <리스트 3>에는 이러한 방법을 사용해서 개수를 구하는 GetCoinCount 함수가 나와있다. 표 1 동전 개수에 대한 승, 패 여부 개수 승, 패 개수 승, 패 개수 승, 패 1 패 5 패 9 패 2 승 (1개) 6 승 (1개) 10 승 (1개) 3 승 (2개) 7 승 (2개) 11 승 (2개) 4 승 (3개) 8 승 (3개) 12 승 (3개) 리스트 3 휴리스틱 방법을 사용한 코드 [CPP]int GetCoinCount(int coins) { return (coins - 1) % 4; }[/CPP] 휴리스틱 방법은 대부분의 경우 수행 속도가 빠르고, 좋은 실행 결과를 가진다는 장점이 있다. 하지만 그런 만큼 휴리스틱 알고리즘을 생각해 내기는 가장 어렵다. 물론 이렇게 간단한 문제에 대해서는 쉽게 답이 나오지만 복잡한 문제에 대해서는 몇 년을 고민해도 답이 나오지 않는 경우가 많다. 또한 휴리스틱을 사용한 코드는 별도의 주석이 없으면 나중에 유지 보수 하는 사람이 이해하기 어려운 코드가 되기 쉽다. 앞서 구현한 GetCoinCount의 경우도 왜 저렇게 하는지 부연 설명이 없다면 코드를 유지 보수하는 사람에게는 마치 마법 같은 코드가 되는 것이다. 따라서 이러한 코드에는 반드시 숫자들이 어디서 왔는지 그리고 그 원리가 무엇인지를 주석으로 남겨둘 필요가 있다. |
[GGGG]C언어를 다년간 사용한 프로그래머들도 종종 매크로 연산자에 대해서 정확하게 이해하고 있지 못한 경우가 많다. 매크로를 사용하지 않는다고 대답할 수도 있지만, 이미 복잡한 매크로를 사용한 수많은 소스가 있다는 점을 생각해 본다면 분명하게 이해해 두는 것이 정신 건강에 좋을 것이다. 복잡한 매크로 표현 식을 구성하는데 많이 사용되는 방법에 대해서 살펴보자. 첫째, 문자열 리터럴은 합쳐진다. 이는 매크로라기 보다는 C언어의 특징이다. 이 기능을 사용하면 긴 출력 문장을 손쉽게 여러 개의 부분 문자열로 나눌 수 있다. 또한 다음에 소개될 매크로 연산자를 사용할 때의 표현식도 좀 더 풍부하게 구성할 수 있다는 장점이 있다. [CPP]printf("이름: %s\n" "나이: %d\n" "전화번호: %s\n", a, b, c);[/CPP] 위의 코드를 보자. printf 다음에는 총 세 개의 연속된 문자열 리터럴이 나타난다. 이 세 개의 리터럴은 합쳐져서 "이름: %s\n나이: %d\n전화번호: %s\n" 과 동일한 문자열이 된다. 둘째, ## 연산자를 사용해서 토큰을 합성해서 만들어 낼 수 있다. ##은 합치기 연산자 이다. 다음과 같은 기능을 생각해 보자. COUNT(start)라고 선언을 하면 DWORD startCnt; 라는 변수를 선언하는 기능이다. 이를 매크로를 이용해서 만들어 보면 아래와 같다. [CPP]#define COUNT(val) DWORD val##Cnt[/CPP] 셋째, # 연산자는 전달된 인자를 문자열로 변환시킨다. start를 매크로 인자로 전달했다면 "start"가 된다는 말이다. 변수 값을 출력하는 매크로를 생각해 보자. PRINT(start)를 하면 화면에 start = 3과 같은 형태로 출력하고 싶은 경우다. 이럴 땐 아래와 같이 매크로를 만들면 된다. #연산자와 위에서 소개한 문자열 리터럴이 합쳐진다는 점을 이용한 것이다. 좀 꽁수 같이 보인다면 두 번째 방식같이 구성할 수 도 있다. [CPP]#define PRINT(val) printf(#val " = %d", val) #define PRINT(val) printf("%s = %d", #val, val)[/CPP] 넷째, #@ 연산자는 전달된 인자를 문자로 변환시킨다. a를 매크로 인자로 전달했다면 'a'를 만들어 주는 것이다. 아래와 같이 전달된 인자에 대한 문자를 생성해주는 매크로를 예로 들 수 있다. [CPP]#define makechar(val) #@val[/CPP] 다섯째, 매크로의 내용이 복잡하고 한 줄 이상의 표현식이 필요한 경우엔 주로 do ~ while(0)문을 사용한다. 이렇게 하는 이유는 do ~ while(0)가 하나의 구문으로 해석되고 자체 블록을 가지기 때문이다. 이것을 단순 괄호({, })로 대체하면 안 된다. 중첩 if문에서 에러가 나기 때문이다. 주로 아래와 같이 사용한다. [CPP]#define COMPLEX_MACRO(a,b,c,d) do { \ int complex_variable; \ // some processing; \ } while(0)[/CPP] 여섯째, 릴리즈 버전에서는 무시되는 매크로를 구성하는 경우다. 주로 디버깅 출력을 하는 매크로가 여기에 속한다. Visual C++ 6.0에서는 '?' 연산자를 사용해서 쇼트 서킷을 구성하는 방법을 주로 사용했다. 하지만 이 후 출시된 Visual C++에는 __noop이라는 내장 함수를 가지고 있다. 이 함수는 인자를 모두 무시하는 기능을 한다. 따라서 예전에 복잡한 쇼트서킷을 사용했던 함수를 두 번째와 같이 간단하게 구성할 수 있다. [CPP]#define TRACE 1 ? 0 : OutputDebugString #define TRACE __noop[/CPP] |