열차 좌석 배당 알고리즘

열차의 승차권을 구입하면 좌석은 어떤 식으로 배당될까?
객차 하나당 좌석은 차량에 따라 60~70개 정도가 있으며, 열차 한 편성은 일반실만 생각하더라도 최하 4량부터 시작하고 KTX의 경우 거의 15량에 가깝다. 수백 개의 좌석들은 어떤 순서와 원칙대로 승객에게 팔려 나갈까?

난 철덕후로서 그 알고리즘이 예전부터 굉장히 궁금했다. 여러분은 그렇지 않은가?

버스 정도면 그냥 아무렇게나 랜덤으로 배당해도 별 무리가 없을 것이다.
우등 고속버스는 가장 쉽다. 승차 정원부터가 30명이 채 안 되는 소규모인 데다, 좌석이 구조적으로 2개짜리와 1개짜리로 나뉘어 있으니 말이다.

단독 승객에게는 진행 방향 기준으로 오른쪽의 단독 좌석부터 먼저 배당해 주고, 그게 매진되거나 2인 승객이 있으면 2인 좌석을 준다. 상석인 맨 앞자리는 약간 나중에 팔리도록 다른 가중치를 부여하고, 반대로 최악의 자리인 맨 뒷자리는 최하위 우선순위로 팔리게 하면 될 것이다.

그러나 열차는 단순하게만 좌석을 배당해서는 대략 곤란하다.
1부터 n호차까지, 그리고 진짜 무식하게 1번부터 m호석까지 앞에서 뒤로 순서대로 꽉꽉 승객을 채워 넣어서 뒤의 객차는 텅 빈 채로 달리게 할 리는 없을 테고..

그렇다고 좌석을(특히 단독 승객) 완전 랜덤으로만 여기저기 들쭉날쭉으로 배당하면 좌석의 단편화(fragmentation)가 너무 심해진다. 그래서 승객이 얼마 타지도 않은 상태인데 이따금씩 타는 2인 이상의 다수 승객은 이어진 좌석을 못 구해서 서로 찢어져서 앉아야 하는 일이 벌어질 수 있다.

결국 본인이 추측하기로는 열차의 좌석 배당은 저 양 극단의 중간을 절충하는 방식으로 이뤄질 것 같다.
두세 개의 객차를 묶음으로 나눠서 한 묶음 안에서 좌석을 무작위로 배당한 뒤, 그 묶음의 좌석이 다 매진되면 다음 묶음으로 간다. 각 묶음은 1~3호차, 4~6호차, 7~9호차 같은 규칙으로 만들 수도 있고, 반대로 1, 4, 7호차와 2, 5, 8호차, 3, 6, 9호차 같은 규칙으로 만들 수도 있다.

그리고 각 객차 안에서는 전체의 50~60% 정도는 단독 승객이 무작위로 띄엄띄엄 앉을 수 있게 배려한다. 즉, 2개짜리 좌석이라도 한 자리에 단독 승객이 있으면 거기는 일단 건너뛰고 다른 빈 자리를 찾는다는 뜻이다. 그러나 나머지 자리는 가능한 한 2인 승객이 한꺼번에 찜할 수 있게 비워 두며, 한 객차의 좌석의 10~20% 정도는 마치 KTX 동반석처럼 4인 가족이 연속해서 앉을 수 있게, 가능한 한 1~2인 승객에게 금세 팔리지 않도록 비워 둔다.

단독 승객의 경우 창측 좌석이 내측 좌석보다 먼저 팔리게 하는 건 기본이다. 또한 열차에서는 출입문과 가까운 맨 앞이나 맨 뒤 좌석이 '안 좋은 자리'이므로 이것 역시 다른 좌석이 모두 팔린 뒤에 나중에 팔리게 해야 할 것이다.
단독 승객용 좌석과 2인 이상 승객용 좌석 영역을 정하는 것 역시 '엿장수 마음대로' 무작위로 하면 되며, 그 비율 역시 평소에 승차권이 팔리는 단위 통계를 근거로 합리적으로 정하면 될 것이다.

저런 균형적인 요소에 덧붙여 환승 동선도 고려 대상이 된다.
국내의 예를 들면 KTX 천안아산 역과 장항선 아산 역은 남쪽 끝에서 만난다. 그리고 KTX는 한 편성이 무려 400m가 약간 안 되는 매우 긴 열차이다. 그렇기 때문에 경부선 KTX를 타다가 천안아산 역에서 장항선으로 환승하는 승객은 부산 방면(하행) 열차의 경우 최대한 앞쪽 객차로 좌석이 배당되고, 서울 방면 열차는 뒤쪽 객차로 좌석이 배당된다. 지하철에서 환승을 빨리 할 수 있는 객차의 위치와 정확히 같은 개념이며, 한국 철도도 그 정도 센스는 이미 갖추고 있다.

이 정도면 내가 보기에 열차 좌석 배당 전략을 짜는 건, 마치 열차 시각표를 짜는 것에 필적하는 철도 영업 기술의 결정체가 될 수도 있을 것 같다.
현실성 있는 열차 운행 시각표를 짜기 위해서는 그 나라의 철도 인프라와 지형 특성, 차량 제원, 승객 패턴 등의 알토란 같은 영업 기밀이 총동원되어야 한다. 이런 걸 계획하는 건 인원을 더 투입한다고 신속하게 되는 게 아니며, 핵심 똘똘이 인력 한두 명이 다 도맡아 한다.

좌석 배당도 마찬가지일 거라는 말이다. 철덕이라면 반드시 정복해야 하는 분야 중 하나 되시겠다.
비행기는 무게 배분이(한쪽에만 승객 무게가 지나치게 쏠리지 않게) 좌석 배당에 감안되는 요인이라고 하는데, 철도는 무게 배분 걱정은 할 필요가 없는 대신 길다는 특성상 다른 변수가 존재하는 셈이다.

자, 여기까지만 글을 쓰려고 했는데, 빈 좌석에다 승객을 일정 규칙대로 채워 넣는 과정을 생각하자니 컴퓨터그래픽에서 중요하게 다뤄지는 알고리즘 분야가 문득 떠오르더라.
바로 디더링이다.

디더링은 적은 수의 색깔을 섞어서 더 화려한 색깔을 아쉬운 대로 표현하는 기법이다. 색을 물리적으로 섞을 수는 없으니 결국 서로 다른 색깔을 번갈아가며 늘어놔야 하는데, 한 색깔이 뭉치는 게 아니라 서로 다른 색깔들끼리 최대한 고르게 퍼지도록 픽셀을 배열해야 한다.

본인은 과거에 Windows 3.x 시절에 그림판에서 임의의 RGB 값을 주면 그 색을 16컬러만으로 디더링하여 표현하는 걸 보고 무척 신기해했었다. 가령, 흑에서 백으로 단계를 증가시킬 때, 검은색에서 흰색 점이 차츰 늘어나는 순서가 어떻게 정해지는지가 무척 궁금했다.

그 규칙을 디더링에서 threshold matrix라고 부른다. 일반적인 그래픽 프로그램에서는 8*8짜리를 사용한다. (출처는 위키백과) 저기서 1부터 16까지의 점을 순서대로 채우면 25% 음영이 그려지고, 32까지 채우면 흑백이 딱 반반씩 번갈아가며 등장하는 50% 음영이 되는 식이다.

사용자 삽입 이미지
처음에는 4픽셀 간격으로 띄엄띄엄 점을 그리고, 나중에는 그 사이의 4픽셀 간격을 채우는 식으로, 점들이 뭉치지 않고 어떤 경우에도 최대한 흩어져서 퍼져 있게 한다. 임의의 격자 크기가 주어졌을 때 threshold matrix를 생성하는 프로그램을 만들 수도 있을 법해 보이는데 그리 만만한 일은 아닌 것 같다. 마방진도 아니고 말이다.

더 나아가 임의의 색을 16컬러 디더링 패턴으로 표현해 내는 프로그램을 직접 짜 보면 어떨까? 주어진 색을 가장 가깝게 표현할 수 있는 2색 또는 3색 조합을 구한 뒤, 그 비율만큼 threshold matrix를 각각의 색으로 채우면 될 것이다. 색조합을 구하는 것은 미지수의 개수가 식의 개수보다 더 많아서 답이 하나로 딱 떨어지지 않는 부등식이 될 터이니, LP(선형 계획법) 같은 계산 기법이 동원돼야 하지 않을까 싶다.

그렇게 threshold matrix만을 정석대로 적용하면 ordered dithering이 된다. 그러나 그것만으로는 그림이 칙칙하고 보기가 안 좋기 때문에, 디더링된 색깔의 픽셀이 인접 픽셀에 시각적으로 끼치는 영향을 감안하여(error diffusion) 더 정교하게 디더링을 수행하는 알고리즘이 실생활에서 쓰인다. 더 깊게 들어가는 건 이 글의 범위를 벗어나므로 자세한 설명을 생략하겠다.

뜬금없이 디더링 얘기를 꺼낸 이유는.. 저렇게 디더링 점을 찍어 나가는 게 마치 열차 좌석을 배당하는 것과 비슷한 심상이 느껴져서이다. 열차 좌석의 점유 여부를 흑백 픽셀로 표현하고 시간이 흐름에 따라 픽셀들의 상태를 표시하는 시뮬레이션을 돌려 보면 재미있을 것 같다. 한쪽은 검은 색이 듬성듬성 있고, 한쪽은 검은 색이나 흰 색이 좀 연속해서 있겠지 아마?

철도의 좌석 배당 알고리즘과 래스터 그래픽의 디더링 알고리즘은 서로 따로 생각하고 있었던 주제인데 이렇게 한 글로 연결이 됐다. 마치 예전에 내가 열차의 급행 등급과 셸 정렬을 한데 묶어서 글을 썼듯이 말이다. 참 신기한 일이 아닐 수 없다. ㅋㅋㅋㅋ

Posted by 사무엘

2013/04/08 08:18 2013/04/08 08:18
, , , ,
Response
No Trackback , 4 Comments
RSS :
http://moogi.new21.org/tc/rss/response/815

아동 유괴 범죄 생각

* 어느 카테고리에다 넣어야 할지가 굉장히 모호한 글이 돼 버렸다.. -_-

1. 여성이 저지른 아동 유괴 범죄

세상에 수많은 흉악 범죄 중에서도 어린이 납치· 유괴 범죄의 죄질은 가히 톱클래스 급이라 할 수 있다. 피해자 부모에게 씻을 수 없는 희망고문과 상처를 안기고 그 후유증 또한 이루 말할 수 없다.

우리나라는 1991년에 개구리 소년뿐만 아니라 이 형호 군 납치· 살해 사건이 유명한 영구미제 사건으로 알려져 있으며 이 이야기는 <그놈 목소리>라는 영화로도 제작되었다. 용의자는 애를 납치 당일에 애초에 죽여 버렸으면서 그 후에 집요하게 집으로 낚시성 금품 요구 전화를 걸었다는 점 때문에 사람들의 분노를 더욱 이끌었다.

그런데 이런 끔찍한 범죄는 꼭 한눈에 보기에도 양아치+싸이코패스 기질이 충만한 젊은 남자만 저지른 게 아니라는 점이 더욱 충격적이다.
1997년, 박 초롱초롱빛나리 양을 공범도 없이 혼자 유괴· 살해한 전 현주는 당시 겨우 20대 후반의 유부녀였고 게다가 임산부였다.

한때 경찰이 용의자가 있다는 단서를 확보한 카페를 급습해서 손님들을 검문했었다. 그러나 다른 손님들과 마찬가지로 용의자인 전 씨도 보내 줄 수밖에 없었다. 임산부지, 아픈 체하지, 이 상황에서 더 조사를 하는 건 100% 공권력 남용으로 여론을 악화시킬 지경이었으니, 이때 용의자를 놓친 것은 절대로 경찰의 무능 탓을 할 수 없었다.

그래도 이 사람은 프로 범죄자가 아니었기에 범행에 여러 허점을 드러내면서 잡히긴 했다. 범행 동기는 간단히 요약하면 그냥 거짓말+허영심을 채우기 위한 돈 때문이었다. 그랬는데 납치한 애가 울고불고 하면서 자기가 통제를 못 할 지경이 돼 가자 살해하게 된 것.

순간의 잘못된 선택으로 인해 가해자의 인생도 완전히 끝났다. 그녀는 무기징역을 선고받고 지금까지 복역 중이다. 남편으로부터는 당연히 이혼 당하고, 배 속에 있던 아이의 양육권도 빼앗겼다.

이 사건 이후, 아이 이름을 너무 튀거나 특이하게 짓는 관행마저 사라졌을 정도라고 한다.
마치 지존파 일당이 검거된 이후 그랜저의 판매량이 잠시 감소한 것과 비슷한 맥락..;; (그랜저급 이상을 굴리는 부유층을 범행 대상으로 삼았다고 그랬으니)

2. 곽 재은 양 유괴· 살해 사건

그런데 박 나리 양 사건은 1990년 6월에 벌어진 곽 재은 양 유괴· 살해 사건과 거의 똑같은 판박이였다. 가해자 홍 순영은 스펙과 직장을 거짓으로 위조해서 대학생 신세를 하고 남자친구까지 사귀었던 20대 아가씨였다. 그랬는데 자신의 정체가 탄로나게 생기고 결혼에도 애로사항이 꽃피게 되자, 이 상황을 돈으로 무마하려고 우연히 이름을 알게 된 어느 유치원생을 꾀어 냈다. 부모를 사칭하면서 집에 급한 일이 생겼으니 애부터 먼저 밖에 내보내 달라고 말이다.

그랬는데 공범도 없이 혼자서 애를 이리저리 데리고 다니는 게 쉬운 일이 아니었다. 아이가 울면서 자기를 집에 보내 달라고 요구하는 등, 상황이 위급해지자 가해자에게도 뭔가 마가 씌였다. 그녀는 아무도 없던 숙명여대 모 건물 안에서 결국 아이를 목졸라 죽이고 말았다. 그리고는 태연하게 아이의 집으로 전화를 걸어(죽이기 전에 먼저 물어 본 연락처) 금품을 요구하였으나, 은행에서 그 돈을 인출하는 모습이 덜미를 잡히는 바람에 결국 잡혔다.

애당초 프로 범죄자들이 하는 것처럼 경찰의 추적을 피하면서 현금만을 교묘히 전달받는 꼼수 따위는 생각할 겨를도 없었고, 대놓고 서울 시내 한복판에서 은행 거래를 할 정도로 홍 순영은 범행 수법도 허술하기 짝이 없었다.

경찰에 잡히고 나서야 그녀는 자기가 얼마나 엄청난 짓을 저질렀는지 깨닫는 듯했고, 이제 멘탈이 완전히 붕괴되고 말았다. 바로 자살을 생각했다. 지하철역에서 공범을 만나기로 약속을 했다면서 경찰에게 거짓말을 하고는 지하철역 승강장으로 진입하는 열차를 향해 몸을 던졌다. 그러나 기관사가 필사적으로 열차를 세운 덕분에 머리를 약간 다치기만 하고 목숨을 건졌다. 이때 그녀가 죽어 버렸다면 아이의 시체도 못 찾게 될 뻔했다.

사용자 삽입 이미지
신 정아처럼 횡설수설과 거짓말을 거듭하던 전 현주와는 달리, 홍 순영의 최후는 더욱 비참했다. 그녀는 이제 걷잡을 수 없는 멘붕과 죄책감에 사로잡혔고, 교도소에서도, 재판 받으면서도 그냥 울부짖으면서 자기를 제발 사형시켜 달라는 말밖에 안 했다. 당시의 기록을 보면 종교인 성직자를 통한 교화 같은 것도 별 효과가 없었던 모양이다.

이때는 그렇잖아도 '범죄와의 전쟁'이 선포되었던 노 태우 정권 시절이었다. 그녀의 원대로 결국 사형 선고가 내려졌고, 그녀는 이듬해인 1991년 말에 겨우 24세의 나이로 형장의 이슬로 사라졌다. 유언도, 장기 기증도 없이 마지막 순간까지도 고개를 저으며 울기만 하면서 정말 고통스럽고 처절하고 허무하게 최후를 마쳤다.

본인은 인간으로서 그녀의 혼에(도) 동정과 연민을 느낀다.
거짓과 허영으로 가득찬 채 빗나간 탐욕을 채우려고 한 어린이의 생명을 빼앗고, 다른 단란하던 가정을 고통의 나락으로 떨어뜨린 그 인생을 동정할 생각은 절대 없다. 사형은 성경적으로도 감정적으로도 정말 정당한 인과응보이긴 했다. 성경에도 있잖은가, “욕심이 잉태하여 죄를 낳고, 죄가 완료되면 사망을 낳는다”고 말이다(약 1:15).

그러나 죄책감에 짓눌린 채 후회만 하는 건 성경이 말하는 회개가 아니고 구원을 이루지도 못한다.
저건 그냥 가룟 유다가 죽듯이 죽은 거다. 이판사판 다 끝났고 부끄러워서 세상 사람들 볼 낯이 없고 꿈이고 희망이고 없는 여건이 됐으니, 이제 살 이유가 없어서 죽겠다는 심정이다.
그런데 그렇게 죽고 나서는, 그럼 하나님은 무슨 낯으로 보려고?

영적으로 좀 날카롭게 진단하자면, 내가 보기에 홍 순영은 애를 유괴하고 살해할 때뿐만 아니라, 나중에 멘붕 상태에서 징징거리는 것도 줄곧 '마'에 씌인 상태에 가까웠다. 자유가 있을 때는 마음껏 나쁜짓을 하게 하다가, 그 자유를 빼앗기고 죄에 대한 대가를 치를 때가 됐을 때는 주체 못 할 죄책감으로 사람을 재기의 여지 없이 완전히 파멸시켜 버리는 것이 마귀의 역사이다. (아니면 죄책감을 아예 안 느끼는 진짜 인면수심 마인으로 바꾸거나.)

둘 다 이성이 마비된 상태인 건 비슷하다. 난 그녀의 “그 상태”가 참 가련하고 불쌍하다는 것이다. 뭐, 가해자의 불우한 성장 배경이 어떻고, 가해자도 사회 시스템의 피해자네 하는 개 풀 뜯어먹는 차원이 아니다. 아시겠는가?
오히려, 홍 순영 같은 일부 '서툴고 여린' 범죄자를 빌미로, 무슨 현행 사법 시스템이 너무 잔인하고 비인권적이네 하면서 사형 제도를 없애네 뭐네 하는 개드립이 일게 하는 건 마귀의 또 다른 역사이다. 이게 오늘날 영적 전투의 실상인 것이다.

민감한 얘기가 또 길어졌으니 다시 본론으로 돌아오자면,
이런 사건을 겪은 나라들은 어린이를 상대로 실시하는 안전 교육 매뉴얼이 바뀌었을 정도였다. 전혀 양아치처럼 생기지 않았고 험상궂은 아저씨도 전혀 아닌 여성조차도 얼마든지 어린이를 유괴· 살해할 수 있다는 게 입증됐기 때문이다.

3. 사형 집행의 부활을 꿈꾸며

우리나라는 잘 알다시피 김 영삼 정권의 말기인 1997년 12월 30일에, 2013년 현재 대한민국 최후의 대규모 사형이 집행되었다. 다음 대통령에게 부담을 안 주기 위해 일부러 집행한 건데 아주 잘한 결정이다.

단, 남아 있던 사형수들을 다 죽인 건 아니고, 자기 정권 전부터 남아 있던 20여 명의 사형수들만 죽였다. 세상에 대한 증오심 때문에 1991년에 여의도 광장에서 칼부림 정도가 아니라 아예 승용차를 질주해서 어린이 2명을 죽게 한 김 용제도 이때 죽은 사형수 중 하나이다.

그리고 김 영삼 정권 시절에 잡힌 지존파 멤버들은 워낙 죄질이 나빠서 그 전에 이미 다 사형이 집행되어 죽었다. 오로지 살인을 목적으로 혈연 관계가 없는 사람들이 똘똘 뭉쳐 전문적인 폭력 조직을 결성한 것은 세계의 범죄 역사를 통틀어서도 드문 사례라고 한다. 중국이 아편 전쟁 때문에 마약에 대한 역사적 트라우마가 있듯, 한국은 '반국가단체'에 대한 트라우마가 있는 듯하다(북한이라든가.. 북한이라든가..). '폭력행위등 처벌에 관한 법률'을 적용하면 사실 조폭을 결성한 것만으로도 우두머리는 최고 사형에 처해질 수 있다.

그 시절 이후로 우리나라는 한 번도 사형이 집행되지 못하고 사법 정의는 땅으로 곤두박질치고 말았다.
그에 반해 전툴루, 전땅크 시절은 비록 다른 흑역사도 많았을지언정 사법 정의에 관한 한은 천국이었다.

약간 인민재판 같은 사례이긴 하지만, 이 윤상 군 유괴· 살해 사건의 경우 아직 살인인지 감금치사인지 확실히 밝혀지지도 않은 상태에서 대통령이 자기 직위를 걸고 항소심을 묵살시켰으며, 재판부에다 압력을 넣어 1~3심에서 모두 피의자에게 사형을 때려서 확인사살을 해 버렸다. 나한테 당해 보지도 않고 말이야. 사전에 “아이를 살려 보내면 너는 살고, 아이가 죽으면 너도 죽는다. (빨랑 자수해라.)”라고 진짜 전땅크스러운 대국민 담화를 대통령이 친히 내린 상태였기 때문에, 그는 약속을 이렇게 지켰다. -_-;;;

살인인지 감금치사인지는 그리 중요하지 않았으며, 대통령의 경고를 씹었다는 괘씸죄 때문에 어차피 사형이 떨어졌다. 마치 군인의 탈영이 나중엔 탈영 자체의 공소시효는 소멸하더라도, 복귀 명령 불복종죄 때문에 처벌 대상이 되듯이 말이다.
피해자 유가족은 이것 덕분에 그나마 얼마나 큰 위로를 받았으며 전툴루에게 개인적으로 감사하게 되었을까? 이게 1983년의 일이다.

그 5공 시절에는 흔히 생각하듯 정치범이나 사상범도 아니고, 심지어 살인을 전혀 저지르지 않은 0 kill 상태에서도 사형이 떨어져서 집행된 적이 있었다.
가정집을 털고 가족들이 보는 앞에서 임산부· 여대생을 강간해 온 3인조 강도 상습범(황 인규, 최 윤성, 최 성훈)은 비록 살인 혐의는 없으나 10수 차례나 저지른 강도· 강간의 죄질이 극히 불량하다는 법무부의 코멘트와 함께 1985년 11월, 얄짤없이 사형을 당했다. 이 판결에 대해 잘못됐다고 이의가 제기된 경우는 지금까지 전혀 없다.

이렇듯, 군사정권 시절에 벌어진 일이 다 나쁘기만 했다는 건 큰 편견이며 오산이다.
내가 광주의 학살자(?.. 뭐 이 표현 자체에도 논란의 여지는 있지만)를 옹호한다고 해서 기분 나빠하는 독자가 있을지 모르겠다. 그러나 그만큼 반대편 성향의 위정자들도 만만찮게 병크를 저지른 게 많으며, 세상 한켠에는 당신과 반대의 견해를 가질 수밖에 없게 만드는 요인도 있다는 것을 독자들은 간과하지 말아야 할 것이다.

특히 우리나라의 높으신 분들이 정말로 저출산을 걱정하신다면,
이 어려운 여건 속에서 애를 낳아서 키우는 부모의 권리를 정말 존중해야 하지 않겠는가?
나라의 미래를 인질로 잡고 장난치고 짓밟고 가정을 파괴하는 범죄자들에게 생명은 생명으로 일벌백계를 내려 주길 간절히 바란다.

피해자의 유가족들은 예수도, 부처도, 공자도 아닌 평범한 일반인들이다. 세상 정부는 무슨 구원이나 내세· 영생을 논하는 조직이 아니라, 이 땅에서의 시민들 생명을 지키고, 질서과 치안을 문란케 한 자에 대한 공공의 보복을 집행할 책임이 있지 않은가?
전땅크를 비판하고 욕하고 싶으면 이런 기초적인 거 하나라도 전땅크보다 잘해 달란 말이다.

Posted by 사무엘

2013/04/05 08:32 2013/04/05 08:32
, , , ,
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/814

지금으로부터 거의 3년 전, 이 블로그가 개설된 지 얼마 되지 않았던 시절에 본인은 C++의 매우 기괴-_-한 문법인 다중 상속멤버 포인터(pointer-to-member)에 대해서 제각각 따로 글로 다룬 적이 있었다.
이제 오늘은, 그 기괴한 두 물건이 한데 합쳐지면 언어의 디자인이 얼마나 더 흉악해지는지를 보이도록 하겠다.
그 내력을 알면, C++ 이후의 객체지향 언어에서 다중 상속이 왜 봉인되어 버렸는지를 이해할 수 있을 것이다. 뭐, 이미 다 아는 분도 있겠지만 복습 차원에서.

클래스의 멤버 포인터는 그 가리키는 대상이 변수이냐 함수이냐에 따라서 내부 구조가 크게 달라진다는 말을 예전에 했었다. 함수일 때는 포인터답게 말 그대로 실행될 함수의 메모리 위치를 가리키지만, 변수일 때는 이 멤버가 this로부터 얼마나 떨어져 있는지를 나타내는 정수 오프셋에 불과하다. &POINT::x 는 0, &POINT::y는 4 같은 식.
그래서 비주얼 C++은 x64 플랫폼에서도 단순 클래스의 멤버 변수 포인터는 뜻밖에도 8바이트가 아닌 4바이트로 처리한다. UNT_PTR이 아니라 그냥 unsigned int라고 본 것이다.

그런데 다중 상속이 동반된 클래스는 '단순' 클래스라고 볼 수가 없어지며, 그런 클래스를 대상으로 동작하는 멤버 포인터는 내부 메커니즘이 굉장히 복잡해진다. 멤버 변수야 오프셋이 바뀌니까 그렇다 치지만, 멤버 함수의 포인터도 데이터 오프셋의 영향을 받는다. 비록 함수 자체는 오프셋을 타지 않고 고정된 메모리 주소이긴 하지만, 멤버 포인터가 어느 함수를 가리켜 부르느냐에 따라 그때 그때 this 포인터를 잘 보정해서 줘야 하기 때문이다.

다음 코드를 생각해 보자.
참고로, class 대신 struct를 쓴 이유는 public: 을 따로 써 주는 귀찮음을 해소하기 위해서일 뿐이다. (C#은 struct와 class의 용도가 구분되어 있는 반면, C++은 전혀 그렇지 않으므로.)

struct B {
    int valB; void functionB() { printf("functionB: %p\n", this); }
};
struct C {
    int valC; void functionC() { printf("functionC: %p\n", this); }
};

struct D: public B, public C {
    int valD; void functionD() { printf("functionD: %p\n", this); }
};

그 뒤,

D ob;
void (D::*fp)();
printf("this is %p\n", &ob);
printf("sizeof pointer-to-member is %d\n", sizeof(fp));

fp = &D::functionB; (ob.*fp)();
fp = &D::functionC; (ob.*fp)();
fp = &D::functionD; (ob.*fp)();

코드를 실행해 보면, 놀라운 결과를 볼 수 있다.
이제 fp의 크기가 포인터 하나의 크기보다 더 커졌다.
비주얼 C++ 기준으로, '포인터+int'의 합이 된다. 그래서 x86에서는 8바이트, x64에서는 12바이트.

게다가 중요한 건, functionC를 실행했을 때만 this의 값이 달라져 있다는 것이다.
이건 뭐 다중 상속의 특성상 어쩔 수 없는 면모이며, 멤버 함수를 ob.functionC()라고 직접 호출할 때는 컴파일러가 알아서 처리해 주는 기능이긴 하다.
하지만, 직접 호출이 아니라 멤버 포인터를 통한 간접 호출을 할 때는 이걸 어떻게 구현해야 할까?

결국은 멤버 함수 포인터 자체에 추가 정보가 들어갈 공간이 있어야 하고, 그 정보는 포인터에다가 함수에 대한 대입이 일어날 때 implicit하게 따로 공급되어야 한다.
다중 상속을 받은 클래스의 멤버 함수를 가리키는 포인터는 this 보정을 위한 정수 오프셋이 내부적으로 추가된다. 이제 fp는 단일 포인터 변수라기보다는 구조체처럼 바뀌었다는 뜻이다.

이 fp에다가 functionB나 functionD를 대입하면 그 멤버 함수의 주소만 대입되는 게 아니라, 숨겨진 오프셋 변수에다가도 0이 들어가며(보정할 필요가 없으므로), functionC를 대입하면 그 주소와 함께 오프셋 변수에다가도 0이 아닌 값이 같이 대입된다. 그리고 실제로 fp 호출을 할 때는 this 포인터에다가 보정이 된 값이 함수로 전달된다.

이야기는 여기서 끝이 아니다. 설상가상으로 가상 상속까지 추가된다면?
내가 클래스를 A가 아니라 B에서부터 시작한 게 이것 때문이다. 맨 앞에다가 드디어 다음 코드를 추가하고,

struct A {
    int valA;
    void functionA() { printf("functionA: %p\n", this); }
};

앞에서 썼던 B와 C도 A로부터 가상 상속을 받게 고쳐 보자.

struct B: virtual public A { ... }
struct C: virtual public A { ... }

이것도 물론 추가하고.

fp = &D::functionA; (ob.*fp)();

이렇게 해 보면..
비주얼 C++ 기준 fp의 크기는 더욱 커져서 '포인터+정수 2개' 크기가 된다. x86에서는 12바이트, x64에서는 16바이트.
다중 상속만 있을 때는 함수 말고 변수의 멤버 포인터는 크기가 변함없었던 반면, 가상 상속이 가미되면 변수 멤버 포인터도 이렇게 '크기 할증'이 발생한다. 대입 연산이나 함수 호출 때 몰래 같이 발생하는 일도 더욱 많아지며, 이 현상을 좀 유식하게 표현하면 cost가 커진다.

그 이유는 어렴풋이 유추할 수 있을 것이다. 가상 상속이라는 건 말 그대로 기반 클래스의 오프셋이 클래스의 인스턴스별로 동적으로 변할 수 있다는 뜻이다. this 포인터 보정이 뒷부분 파생 클래스의 정확한 위치를 파악하기 위해서 발생하는 일이라면, 가상 상속 보정은 앞부분 기반 클래스의 위치를 파악하는 것이 목적이다.

이런 사정으로 인해 functionA()도 원래 개체의 주소와는 다른 주소를 받으며, 이것은 functionC()가 받는 주소와는 또 다르다.
다만, pointer-to-member는 가상 함수와는 기술적으로 전혀 무관하게 동작하기 때문에, 가상 함수가 존재하는 클래스라고 해서 오버헤드가 추가되는 건 없다. 함수 멤버 포인터로 가상 함수를 가리키면, 아예 가상 함수 테이블을 참조하여 진짜 함수를 호출하는 wrapper 함수가 따로 만들어져서 그걸 가리키고 있게 된다.

요컨대 비주얼 C++은 단순 클래스, 다중 상속만 있는 클래스, 거기에다 가상 상속까지 있는 클래스라는 세 등급에 따라 멤버 포인터를 관리한다. 다만, 함수가 아닌 변수 멤버 포인터는 가상 상속 여부에 따라 두 등급으로만 나누는 듯하다. 이 정도면, 이 글을 쓰는 본인부터 이제 머리가 핑그르르 도는 것 같다.

이제 마지막으로 생각해 볼 문제가 있다. C++은 클래스의 명칭 선언만 하는 게 가능하다는 점이다.

class UnknownBase;
class UnknownDerived;

굳이 클래스의 몸체를 몰라도 이 클래스에 대한 포인터 정도는 선언이 가능하다. 그렇기 때문에 명칭 선언은 컴파일 때 헤더 파일간의 의존도를 줄이고 모듈간의 독립성을 높일 때 요긴하게 쓰이는 테크닉이다.
다만, 여러 클래스들을 명칭 선언만 하면 이들간의 상속 관계도 아직 밝혀지지 않기 때문에, 실질적인 기반 클래스와 파생 클래스 사이에 암시적인 형변환이나 static_cast, dynamic_cast 따위를 쓸 수 없다는 점도 주의해야 한다.

게다가 이렇게 명칭만 달랑 선언된 클래스에 대해서 멤버 포인터를 선언하면..
컴파일러는 이 클래스가 다중 상속이 존재하는지, 가상 상속이 존재하는지 같은 걸 알지 못한다!
그렇다고 무식하게 에러 처리하며 멤버 포인터의 선언을 거부할 수도 없는 노릇이니,
컴파일러는 가장 보수적으로 이 클래스가 어려운 요소들은 모두 갖추고 있을 거라고 생각하고 가장 덩치 크고 복잡한 등급을 선택할 수밖에 없다.

나중에 사용자가 추가 인클루드를 통해 클래스의 몸체를 선언하여, 이 클래스는 단순한 놈이라는 게 알려지더라도 한번 복잡하게 결정되어 버린 타입 구조는 다시 바뀌지 않는다.
게다가 이렇게 unknown 클래스에 대한 멤버 포인터는 단순히 '가상 상속 클래스' 등급이 아니라, 메타 정보가 추가로 붙는지 비주얼 C++에서는 함수 기준으로 x86에서 무려 16바이트를 차지하며, x64에서는 24바이트를 차지하게 된다. 포인터 둘, int 둘의 합이다.

printf("%d\n", sizeof(void (UnknownDerived::*)() ));

물론, 멤버 포인터부터가 굉장한 레어템인데, 몸체도 없이 명칭 선언만 된 클래스에 대해서 멤버 포인터를 덥석 들이대는 코딩을 우리가 실생활에서 직접 할 일은 극히 드물다. 하지만 딱 machine word와 동일한 크기를 기대했던 멤버 함수 포인터가 3~4배 크기로 갑자기 뻥튀기되고 생각도 못 했던 오버헤드가 추가되는 일은 없어야 하겠기에, 비주얼 C++은 역시 비표준 확장을 통해서 이 문제에 대한 해결책을 제시하고 있다.

그것은 바로 _single_inheritance, _multiple_inheritance, _virtual_inheritance라고 참 길게도 생긴 키워드.
클래스를 명칭 선언만 할 때

class _single_inheritance UnknownDerived;

이런 식으로 써 줌으로써 “이놈은 다중 상속 같은 귀찮은 요소가 없는 클래스이다. 따라서 얘에 대한 멤버 포인터는 추가 오프셋이 없는 제일 간단한 등급으로 만들어도 OK다”라는 힌트를 컴파일러에다 줄 수 있다.
복잡한 놈이라고 예고를 해 놓고 단순한 형태로 클래스를 선언하는 건 괜찮으나, 간단한 놈이라고 예고를 해 놓고 나중에 다중 상속이나 가상 상속을 쓰면 물론 컴파일 에러가 발생하게 된다.

아마 스타크래프트를 만든 사람도 스탑 럴커 같은 전술은 생각을 못 하지 않았을까.
저런 판타지 같은 면모는 C++을 설계한 사람이나 추후에 기능을 확장한 표준 위원회 사람들도 생각을 못 했을 가능성이 높아 보인다.
그렇기 때문에 비주얼 C++처럼 단순, 다중, 가상으로 세 등급을 나눠서 포인터에다 할증 제도를 넣고, 관련 예약어까지 추가한 건 전적으로 표준이 아니라 컴파일러 구현하기 나름이다.

아, 자세한 건 이 사이트 내용을 좀 공부하고 글을 쓰려고 했는데 도저히 다 읽을 엄두가 안 난다.
관심 있는 분들은 알아서 탐독해 보시길.
언뜻 보니, 다중 상속이 멤버 포인터보다 시기적으로 나중에 등장했다. 그래서 둘을 한꺼번에 구현하는 게 이 정도로 복잡하게 꼬인 셈이다.

Posted by 사무엘

2013/04/02 08:33 2013/04/02 08:33
,
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/813

교육자가 되기

어느 분야든 제도권의 교육자가 되기란 무지무지 힘들다.
교육자는 자율과 자기 재량이 보장되지, 방학 있지, 생업 전선에 안 뛰어들어도 되고 경기 영향도 안 받는 안정적인 수입 있지, 사고 안 치고 잘 퇴직하면 연금까지 평생 나오지..
그래서 그만큼 이 바닥은 수요보다 공급이 너무 많고 경쟁이 치열한 레드 오션이다.
제자들에게 절대적인 권위를 행사하면서 부와 명예를 보장받는 직업이 아무에게나 주어질 수 있을 리가 없다.

1. 초등학교

극소수 사립 학교에 들어가는 걸 제외하면, 사실상 오로지 교육대라는 전용 양성 기관을 거쳐야만 교사가 될 수 있기 때문에 첫 진입장벽이 높다. 소수정예 합숙 생활, 매우 저렴한 학비, 내부의 폐쇄적인 문화와 텃새 등. 거기서 필터링이 좀 되기 때문에, 교육대생 내부에서의 경쟁은 뒤의 두 분야에 '비해서'는 덜한 편이다.

다만, 언제까지나 '덜하다는' 거지 경쟁이 없다는 건 아님. 교육대 졸업생은 여타 분야와는 달리, 오로지 교사가 못 되면 정말로 사회에서 할 게 없다는 리스크가 더 크다. 어린 초등학생을 지도한다는 특성상, 전공 분야의 전문성보다는 인성· 전인교육의 비중이 더 높기 때문일 것이다. (초등학교는 담임의 비중이 매우 높으며, 영어 같은 일부 특수 과목만 빼고 한 교사가 하루 종일 자기 반의 전과목을 가르친다.)

2. 중등학교(중· 고등학교)

중등학교는 학생의 성적과 진로 지도에 대한 부담이 가장 높은 교육 등급으로, 일단은 사범대 졸업생이 지망한다. 하지만 초등학교와는 달리, 여기는 여타 학과 전공자에게도 길이 열려 있다. 따로 교육 대학원을 나오거나 다른 방법으로 교사 자격증을 취득한 뒤, 공립 학교 교사 임용에 도전할 수 있다. 그래서 진입장벽이 낮다는 특성상, 중등학교 교사 임용 시험은 정말 최악의 경쟁률을 자랑한다.

임용에서 도저히 합격을 못 하면 결국 기간제 내지 사립 중등학교, 혹은 진짜 사교육인 학원 강사를 생각하게 된다. 1순위만치 안정적이지는 못해도, 초등학교 교사 지망생보다는 선택의 폭이 넓은 편. 사립은 잘 들어가면 공립 이상으로 좋은 직장이 될 수 있긴 하나, 채용 절차가 공정하지 못할 가능성이 매우 높다는 게 함정이다.

3. 대학 교수

교사와는 달리 교수는 애들을 잘 가르치고 좋은 성적을 내게 하는 게 일차적인 목적이 아니다. 고등 교육은 학부와 대학원으로 이원화해 있으며, 교수는 평생 논문을 읽고 쓰면서 학문을 업으로 삼는 학자의 삶을 살아야 한다.
이 바닥은 박사 학위라는 희대의 진입 장벽이 있음에도 불구하고, 뽑는 숫자도 교사를 능가하는 수준으로 너무 적기 때문에 레드 오션의 진정한 종결자 분야라 할 수 있다. 지나친 학력 인플레도 한몫 했고 말이다.

책 읽은 양으로만 승부하는 인문계 지망자라면, 학원 강사나 조교로 눈물겨운 고학 생활을 수 년간 하면서 학계에 얼굴도장 찍고 지도교수에게 잘 보여야 한다.

펀드를 받고 프로젝트를 진행하는 이공계 지망자라면 돈 걱정은 인문계보다 상대적으로 덜할지 모른다. 그러나 나름 폐쇄적이고 군기도 존재하는 랩 사람들과 잘 어울려야 하며, 권한은 사장이고 하는 짓은 직속 상사 같은 지도교수에게도 잘 보여야 한다. 박사 과정 내내 논문 출판과 실적 압박에 시달리다, 졸업 후 나중에 교수 채용 때도 날고 기는 쟁쟁한 외국 박사들과 피 튀기게 경쟁해야 한다.
단, 이공계는 취업이 잘 되기 때문에, 굳이 교수가 아니어도 교수보다 더 높은 연봉 받는 대기업으로도 잘 빠진다. 비록 안정성은 교수보다 못하겠지만.

학문적 업적보다 포트폴리오가 상대적으로 더 부각되는 예체능 분야는 사회 경력 n년을 박사 학위와 동급으로 쳐서 석사 출신이 교수가 되기도 한다. 아니면 교육 대학원에서 교육학 박사를 받거나.

이공계나 예체능과는 달리, 인문계는 정말 교수가 못 되면 할 게 없는데 교수 자리가 없어서 열악하게 살고 있는 박사들이 문제이다. 그 학력으로 겨우 학원 아니면 시간 강사, 연구 교수 같은 저임금 계약직이 고작이니, 고학력 실업 문제는 어찌 해결해야 좋을꼬?


Posted by 사무엘

2013/03/30 19:29 2013/03/30 19:29
, , , ,
Response
No Trackback , a comment
RSS :
http://moogi.new21.org/tc/rss/response/812

1. 라틴 알파벳의 위엄

오늘날 세계 문자들 중에 라틴 알파벳은 인지도와 실용성 면에서 단연 절대적인 '갑'임을 부정할 수 없다. 라틴 알파벳은 다음과 같은 여러 장점과 유리한 점이 있다.

  • 어느 한 국가나 민족만의 문자가 아니다. 물론 나라의 정서법마다 알파벳을 읽는 방식에 대동소이한 차이가 있어서 혼동스러운 면모도 있지만, 어쨌든 가장 국제적이다.
  • 음소문자여서 나름 다양한 언어의 말소리에 대응하기 유리하다. 또한 풀어쓰기를 하는 구조여서 언어의 다양한 음절 구조에 대응하기에도 유리하다.
  • 글자 수가 적어서 간편하며 활용하기도 쉬운 구조이다. 가령, 세계의 문자들 중에 기계식 타자기로도 만들고 또 겨우 8*8 크기의 비트맵에 각 글자의 모양을 다 담을 수 있는 문자는 얼마 되지 않을 게다.
  • 각각의 글자들이 들쭉날쭉하고 개성이 뚜렷해서 한데 뭉쳤을 때 시각성이 뛰어나며, 기호로서의 역할도 하기 좋다.
  • 타이포그래피 관점에서는 문자 차원에서 대소문자 구분이 존재하고, 또 이탤릭체 같은 같은 방식으로도 호소력을 높일 수 있다.

물론 라틴 알파벳은 표음· 음소문자라는 취지와는 달리 현실은 시궁창으로 언문일치가 개떡이며, 구조적으로 모음 글자가 너무 부족하다는 한계도 있긴 하다.

그럼에도 불구하고 라틴 알파벳은 신경 써야 하는 글자 수가 겨우 수십 개에 불과하기 때문에, 개개의 글자가 그야말로 왕이며, 서체 디자이너는 각 글자에 대해 완전 올인 몰빵 최적화가 가능하다. 이게 개인적으로 가장 부럽게 느끼는 점이다.
가령, A~Z까지 아주 정교하게 테스트된 수제 힌팅 프로그램을 집어넣는 여유를 부리는 건 이미 20여 년 전에 트루타입 글꼴이 처음으로 도입됐을 때부터 있었던 일이다.

그리고 요즘 글꼴이라는 건, 그저 코드값별로 고정된 글자의 폭과 벡터 이미지만을 기술하는 static한 데이터의 집합이 아니다. OpenType 기술 규격 덕분에 자기 옆에 무슨 글자가 오느냐에 따라서 다른 모양을 제공할 수 있고(GSUB), 폭을 미세하게 달리할 수도 있다(커닝.. 이게 개념적으로 더 확장되어 GPOS).

알파벳 정도야 문자 개수를 제곱해 봐야 몇백~몇천 정도까지의 조합밖에 안 나오니, 그 정도는 감당 가능하다. 한글이 '가' 할 때 ㄱ과 '고'나 '강' 할 때 ㄱ의 모양이 서로 달라지는 게, 알파벳으로 치면 W 다음에 A가 올 때와 W 다음에 P가 올 때의 간격을 미세하게 달리 설정하는 것과 같다는 뜻이다. 무슨 뜻인지 아시겠는가?

이런 기술은 이런 게 반드시 있어야만 정서법 차원에서 제대로 표시가 가능한, 복잡한 외국어 문자(complex script. 태국어나 아랍어 문자 같은)를 위해서 개발된 것이다. 그런데 라틴 알파벳은 원래 그런 기술의 도움을 받아야 할 정도로 복잡한 문자가 아님에도 불구하고, 더 정교하고 아름다운 타이포그래피를 위해 서체 디자이너가 그런 최신 기술까지 적용하여 시쳇말로 가히 잉여짓을 하고 있는 것이다. 가령, 라틴 알파벳도 아주 정교하고 미려한 필기체 중에는 그렇게 다이나믹한 표시 조건이 필요한 것도 있으니 말이다.

2. 반대편 극단에 있는 한자와 가나

이렇게 소수의 유한한 글자들이 왕인 라틴 알파벳 문화권에 비해, 글자 수가 엄청 많은 CJK는 상황이 굉장히 다르다.
한자는 전세계의 문자들 중 유일하게 '열린-_- 집합'인 무지막지한 문자 시스템이다. 덕분에 가변폭-_-이라든가 글자간의 다이나믹한 상호작용 같은 개념은 전혀 존재하지 않는다. 그래도 각각의 글자 자체가 워낙 그림처럼 생겼기 때문에 그 특성을 살린 타이포그래피가 존재한다.

거기에다 일본어 정서법은 한자에다가 자기네만의 간단한 표음문자가 더해져서 상황이 또 달라진다.
일본어는 입력하기가 굉장히 복잡하고 어려운 축에 든다. 그리고 '가나'라고 불리는 고유 문자는 구조적으로 불완전하며 한글에 비해 스케일이 작고 표음 능력에 한계가 있는 게 사실이다. 일본어 정서법은 가나 전용이 현실적으로 무리이다. 한자를 쓰지 않으면 안 되는 상황이며, 한글하고는 분명 상황이 다르다.

그러나 이런 형태가 단점만 있는 건 아니다. 굵직한 의미를 강조하는 한자와, 비교적 단순한 모양인 가나가 어우러지면 그것도 또 개성이 있으며 시각성이 살아나서 읽기도 꽤 좋다. 유식한 용어를 동원하자면 function word와 content word가 딱 잘 구분돼 보인다.
또한 입력이 어려운 대신, 한번 입력된 일본어 문장은 문자 차원에서 한자, 히라가나, 가타카나 같은 적지 않은 양의 NLP 정보가 담기기 때문에, 형태소 분석이나 번역을 의외로 유리하게 만들기도 한다. 히라가나-가타카나 구분이 라틴 알파벳으로 치면 대소문자에 얼추 비슷하게 대응한다고 볼 수도 있다(완전히 같지는 않지만).

3. 한글은 멀티 패러다임

자, 이렇게 라틴 알파벳 쪽의 정황과 한자 및 일본어 정서법의 정황을 살펴보았다.
그렇다면 세종대왕이 창제한 그 우수하고 독창적인 문자라는 '한글'을 사용하는 우리는 상황이 어떨까?

한글과 관련해서 우리가 절대로 간과해서는 안 되는 점은, 한글은 단일 패러다임만으로 설명이 되지 않는 문자라는 점이다. 단적인 예로, 단일 패러다임으로 설명이 되지 않기 때문에 한글이 총 몇 자냐는 질문에 24자부터 시작해 67자, 11172자, 심지어 160여만 자 같은 사람마다 뒤죽박죽인 대답이 나오는 것이다. 각각의 숫자가 한글의 규모를 어떤 관점에서 측정한 것이겠는지는 독자 여러분이 알아서 생각해 보시라.

차라리 한자는 총 몇 자냐는 질문에 대해 “아무도 모른다”라는 대답이라도 곧바로 나오지, 한글은 대단히 특이한 경우가 아닐 수 없다. 그래서 유니코드에는 고육지책으로 한글이 자모와 글자마디가 모두 등록되어 있으며 이것은 내가 보기에 나쁜 선택이 아니다. 그러나 한글을 바라보는 패러다임의 대립은 심지어 컴퓨터 전문가 사이에서도 거의 종교적인 신념의 대립에 가깝다. 이 말이 무슨 뜻인지 잘 모르겠으면, 본인에게 개별적으로 물으면 대답해 주겠다.

한글이 이런 유별난 위치에 있는 이유는 두 말할 나위도 없이 초-중-종성을 모아서 한 글자를 이루는 구조이기 때문이다. 만약에 한글이 IPA의 지위를 노리는 범용 음성 부호를 염두에 두고 만들어졌다면, 굳이 모아쓰기를 할 필요가 없다. 로마자의 예에서 볼 수 있듯, 풀어 쓰는 게 자음이나 모음의 중첩 같은 각종 외국어들의 변화무쌍한 음운 구조의 대처에 더 유리하다. 지금의 모아쓰기 체계는 모음의 발음이 분명하고 받침이 자주 등장하는 한국어의 음운 구조에 좀 더 최적화/로컬라이즈 전략을 선택한 귀결이다.

그래서 한글은 한국어의 음절 구분이 분명하며 시각성과 개성이 더 뛰어난 문자가 되었으며, 구조적으로는 알파벳 같은 계열보다 한자 계열과 좀 더 비슷한 형태가 되었다. 이 선택은 매우 큰 장점과 개성과 자부심을 가져온 것이 사실이다. 그러나 한편으로는 우리가 기술적으로 풀어야 할 과제도 많이 만들었다. 로마자처럼 소수의 글자를 최적화하고 품질을 다듬는 데 투자될 수 있는 기술과 노력이, 한글 조합 그 자체의 문제만을 푸는 데 다 소비되어 버리게 되었다. 입력이든 출력이든 모두에서 말이다.

다시 말해, 1만 개, 1백만 개, 심지어 1억 개의 글자를 조합해서 생성할 수 있다고 하는 한글의 그런 잠재성은, 알파벳처럼 개개의 글자마다 완전 정교한 힌팅, 커닝, 이탤릭, 필기체 따위로 최적화가 가능한 면모와 기술적으로 공존할 수가 없거나, 최소한 상호 조화시키기가 대단히 매우 어렵다는 뜻이다. 그렇다면 모아쓰기라는 걸 현실에서 반드시 절대적으로 일방적인 장점이라고만 간주해도 되는 것일까?

그래서 옛날 한글 선각자 중에는 로마자의 직관적인 기계화 기술과 먼치킨 급의 타이포그래피 최적화에 너무 한이 맺힌 나머지, “우린 안 될 거야 아마”라는 비관적인 결론으로 빠져서 아예 <글자의 혁명> 급으로 한글을 풀어쓰기 형태로 마개조할 생각을 한 분까지 있었다. 다중 패러다임이 독이라고 생각했던 것 같다.

물론 그 의도는 이해하지만 그건 너무 과격하고 비현실적인 주장이다. 개개의 한글 낱자는 마치 일본어 가나만큼이나 완성도가 부족하다. 가나가 한자와 섞어 쓰라고 만들어진 문자라면, 한글 낱자는 모아 쓰라고 만들어진 문자인 것이다. 한글 낱자만으로 풀어 쓰려면 진짜로 모음 ㅡ라면 U처럼 바꾸는 식으로, 글자 자형을 좀 더 변별성 있게 고쳐야 하고 맞춤법까지 대대적으로 손을 봐야 한다. 위험 비용이 너무 클 뿐만 아니라, 풀어쓰기를 실제로 오랫동안 시행해 본 분의 증언에 따르면, 한글 풀어쓰기의 시각적 능률은 모아 쓴 한글 음절을 도저히 따라갈 수 없었다고 한다.

4. 이제는 한글 자체의 고유한 특성을 살려서 입출력 기술을 발전시켜야

결국 이 모아쓰기에 따른 다중 패러다임은 우리가 한글을 고유 문자로 사용하는 한, 우리가 언제까지나 지고 가야 하는 숙제이다. 예전에 컴퓨터의 성능이 열악하던 시절에는 한글 같은 복잡한 문자를 심을 길이 안 보여서 풀어쓰기라도 생각해야 할 것처럼 상황이 암울했다. 그보다 상황이 약간 나아졌을 때에도 겨우 일본의 자국 문자 로컬라이즈 방식을 모방한 2바이트 한글 코드에다 전/반각 문자 따위밖에 선택의 여지가 없었다.

그러나 지금은 그런 기술적인 제약이 없다. 유니코드에, 화려한 OpenType 기술까지 완비되어 있는데 뭘 더 바라겠는가? 한글은 기계화가 유리한 문자인 건 명백한 사실이다. 그러나 딱 그거 하나만 믿고, 기계화 수준이라는 게 과거의 타자기 내지 2바이트 한글 코드 시절의 기술적 수준에서 멈춘 채 발전이 정지해 있다. 그런 낙후된 기술 수준은 한글에서 제한된 일부 패러다임밖에 수용을 할 수 없다.

일례로 입력부터 먼저 살펴보면, 15년도 더 전의 윈도우 95의 마소 한글 IME의 설정 대화상자와, 지금 윈도우 8의 한글 IME의 설정 대화상자는 제공하는 기능이 차이가 거의 없다. 이거 좀 문제가 있다.
어차피 조합이 필요하고 IME 같은 계층이 필요하다면 그걸 살려서 IME 계층이 있어야만 할 수 있는 일을 추가로 많이 지원해 줘야 한다.. 이것은 내가 <날개셋> 한글 입력기를 통해 어느 정도 기술적으로 실현시켰으며, 이 분야의 발전에 대한 필요성은 내 석사 논문에다가도 충분히 언급해 놨다.

출력 쪽도 마찬가지이다. 앞서 언급했듯이 라틴 알파벳은 적당히 꼬불꼬불하고 스스로 들쭉날쭉해서 보기가 좋으며, 일본어는 한자와 가나가 어우러져서 보기 좋다. 그렇다면 한글은 단일 종류의 문자가 빽빽하게 모였을 때 어떤 미를 추구해야만 할까?
이제는 한글도 한자 중심의 획일적인 정사각형 타이포그래피를 벗어나서 커닝도 생각하고, OpenType 기술을 적극 활용한 다이나믹 글꼴이 더 많이 나와야 한다. 한글은 굳이 한자가 섞일 필요가 없는 stand-alone, self-contained 문자이기 때문이다.

한글은 각 낱자는 직선 아니면 원밖에 없어서 기하학적으로 의외로 무척 추상적이고 단순하다. 그러면서 초중종성의 개성이 아주 분명하니, 한자나 로마자와는 달리 각종 로고타입이나 픽토그래픽으로 형상화하기가 좀 까다로운 면모가 있다(여전히 그림이나 아이콘 같아 보이지 않고, 글자처럼 보임). 그러나 그런 식으로 모아 쓰는 한글의 특성 때문에 오로지 한글에만 적용할 수 있는 글꼴이 나올 수도 있다. 모아쓰기를 그저 거추장스럽게 한글 자모를 정사각형에다가 예쁘게 끼워 넣는 overhead, burden으로만 받아들이지 말고 좀 더 창조적으로 활용할 수는 없을까? 이것이 내가 현재 고민하고 있는 분야이다.

요컨대,

  • 한글을 굳이 여타 문자와 더 비슷하게 만들어서 단점을 상쇄하려 하기보다는, 한글의 개성과 장점을 더 살린다. “넌 그걸 할 수 있냐? 난 그걸 못 하는 대신이 완전히 새로운 이걸 할 수 있다. 놀랍지?”를 지향한다는 뜻이다.
  • 한국어의 특성은 배제하고 오로지 한글의 형태 자체만을 생각한다.

이것이 내가 전통적으로 생각해 온 연구 방향이다. 입력기도 그렇고 글꼴도 그렇다. 더 이상의 자세한 설명은 생략한다. 연구 결과물이 다 나온 뒤에 빵! 터뜨려야 하는 것이기 때문에 당분간은 비밀이다. 그렇다고 해서 그렇게 심하게 거창한 건 아니고.. <날개셋> 한글 입력기도 겨우 1.x~2.x 시절에는 아이디어만 새로웠지, 기술적으로는 그다지 거창하지 않았으며 허접함 그 자체였었다.

이런 식으로 입력과 출력 모두에서 한글의 기술적 가능성을 한 걸음 확장하는 시도를 골고루 달성하고 나면, 그것이 대중적으로 성공하든, 아니면 너무 과격하거나 시장성이 없어서 실패하든 난 대한민국이라는 나라에서 태어난 보람을 느낄 것이고 죽어도 여한이 없을 것 같다.

Posted by 사무엘

2013/03/27 19:38 2013/03/27 19:38
,
Response
No Trackback , 13 Comments
RSS :
http://moogi.new21.org/tc/rss/response/811

자유에 대한 생각

우리나라 내지 이에 준하는 여타 자유 민주주의 국가들이 국민에게 보장하는 자유는 여러 종류가 있다. 우리나라의 경우 헌법 제2장에서 신체의 자유, 거주와 이전의 자유, 직업 선택의 자유, 사생활과 비밀의 자유, 양심의 자유, 종교의 자유, 집회와 결사의 자유 등등이 명시되어 있다. 그리고 모든 자유에는 책임이 뒤따르며, 남의 자유를 침해하거나 범죄· 이적 행위· 반역을 조장하거나, 다른 자유 이념과 모순을 일으키는 자유는 자유로 인정받을 수 없다고 우리는 아주 어릴 적부터 사회· 도덕 시간에 배운다.

이런 자유들은 당연한 것 같지만 당연한 게 아니다. 선조들이 피흘려 쟁취한 소중하고 고귀한 이념이다(대표적으로 6· 25!). 또한 이것은 충분한 경제력과 심지어 과학 기술이 뒷받침 되어야만 실현 가능하다. 옛날에 먹고 살기가 어렵던 시절에는 사회 보장 제도가 제대로 돌아갈 수 없었으며, 뭐 하나 잘못 사고 쳤다간 집안이 쫄딱 망하고 처자식이나 자기 자신을 노예로 팔아야만 수습할 수 있었다. 거기에다 누가 무슨 짓을 할지 상대방을 믿을 수도 없는 긴급한 상황이라면 어떻게 모든 자유가 보장될 수 있겠는가?

그래서 우리나라는 명목상 미국의 사회· 정치 제도를 벤치마킹한 자유 민주주의 국가로 시작했으나, 휴전선 너머 북한의 집요한 방해 공작과 비열한 해코지로 인해, 완전히 이상적인 자유를 실현하는 데는 애로사항이 있었다. 완전 폐쇄적인 선군정치 최적화 병영 국가와, 사회 시스템이 다 개방되어 있는 자유로운 국가가, 서로 인구와 경제력이 비슷하고 다른 변수가 없다면 어디에서 어디로 간첩을 침투시키고 유언비어 퍼뜨리고 심리전을 전개하기가 쉬울 것이며, 무력 충돌이 벌어졌을 때 누가 이길 가능성이 더 높을까?

이 때문에 우리나라는 전통적으로 북한과 관련해서는 불가피하게 기본권을 법으로 크게 제한할 수밖에 없었다. 우리나라에 21세기에까지 국가 보안법 같은 구시대 악법(?)이 존재하는 이유는, 21세기에까지 우리나라 체제를 부정하고 김씨 부자에게 이로운 짓을 하는 구시대 악인이 아직도 존재하고 있으며 법에 저촉되어 잡히고 있기도 하기 때문이다.

이것은 독일이 아무리 자유 민주주의 국가라 해도 나치 및 히틀러와 관련된 매체 표현은 무조건적으로 금지하며, 누가 길거리에서 팔 뻗쳐서 “하일 히틀러!”만 외쳐도 잡아 가는 것과 동일한 맥락이다. 정상적인 독일 국민 중에 그걸 보고 무슨 국민 기본권 침해라고 징징대는 사람이 과연 있을까?

자, 여기서 표현이라는 말이 나왔다. 우리나라 헌법에는 '표현의 자유'라는 말이 문자적으로 등장하지는 않지만, 18조와 21, 22조는 개념적으로 표현의 자유의 범주에 든다고 여겨진다.

본인은 크리스천으로서 표현의 자유라는 게, 사상과 종교의 자유에 동급으로 매우 중요한 자유라고 생각한다. 표현의 자유는 크리스천에게 거리 설교 같은 복음 전파를 법적으로 보장하는 자유이기 때문이다. 기독교 신앙은 남에게 영향을 끼치고 역사하는 신앙이다. 표현의 자유를 배제하고서 사상과 종교의 자유만으로 크리스천 신앙을 다 논하는 것은 불가능하며, 둘을 서로 떼어서 생각할 수 없다.

그리고 본인은, 우리나라는 표현의 자유를 포함한 기본권의 보장이 이미 넘치도록 충분히 아주 잘 돼 있다고 생각한다. 그래서 우리나라의 체제에 대해 고맙게 여긴다. 지금까지 온라인이나 오프라인으로 내가 믿는 프로파간다를 주변에 알리거나 교회에서 거리 설교를 하면서 공권력으로부터 구금· 체포의 위협을 느낀 적이 없다. 심지어 정치인들 비판도 아무 문제 없이 했다.

이 명박 정권 때부터 분야별로 온라인 검열이 강화된다면서 사람들이 굉장히 불안해했다. 그러나 난 별로 동요하지 않았다. 난 일단 신앙과 관련된 자유가 침해받지 않는 것만으로도 굉장한 자유감을 느끼며, 그것만 보장해 준다면 심지어 독재 정권이라 해도 별로 거부하지 않는다. 오히려 자유가 너무 많아서 그걸 오· 남용 하는 부류들이 더 문제라고 난 생각한다.

북한 관련 표현이야 나라에서 하지 말라면 좀 참고 안 하면 되고, 음란물· 성인물이야 어차피 나하고는 아무 상관 없는 일이니.. 나라에서 그런 규제를 가하는 사정을 오히려 이해한다. 그런 규제가 무슨 북한 같은 수준의 검열로 이어질 거라고 생각하지도 않는다. 언론들이 너무 오버를 하고 네티즌들을 막 선동했다.

글쎄, 난 이런 점에서는 “나만 괜찮으면 된다”는 식의 어찌 보면 좀 이기적인 생각을 하고 있기도 하다. 그런데 난 그게 성경적으로 나쁘다고 생각하지 않는다.

예를 들어, 계몽주의 사상가 볼테르는 이런 유명한 말을 남겼다고 한다.

“난 당신의 사상에 동의하지 않습니다. 그러나 당신이 그 사상 때문에 박해를 받는다면 나도 당신의 말할 권리를 위해 끝까지 같이 싸우겠습니다.”


그래서 특히 진보 성향이 강한 사람들이 이 말을 자기 블로그에다가도 많이 걸어 놓고, 많이 떠받드는 것 같다.

그런데, 난 양심적으로 저렇게는 못 하겠다.
나더러 그저 소수이고 박해받는다는(?) 이유만으로 병역 거부하는 인간들, 동성애자들, 낙태 합법화, 사형 폐지론자들의 말할 권리를 위해 같이 싸우자고? 못 한다. 물론 그들은 어차피 요즘 대한민국에서 박해를 받고 있는 것도 아니고.

크리스천으로서 그런 사람들을 괴롭히고 왕따 시키고 해코지하는 것에야 물론 동참하지 않는다.
하지만 혼자 죄의 가시밭길을 가다가 혼자 망하든지 말든지 마음대로 해라.
그들과 얘기를 나눌 기회가 있으면 그들에게 복음을 전하고 그들의 오류를 지적하면 지적했지,
그들의 표현의 자유의 보장을 위해 싸운다거나 기도해 준다거나(?) 하는 일은 난 할 수 없다. 난 볼테르 같은 대인배가 아니다. 아니, 난 하나님보다 더 대인배여야 할 필요를 느끼지 않는다.

저렇게 '행동하는 양심'의 감성을 자극하는 작품으로 <나치가 그들을 덮쳤을 때>라는 시도 유명하다.
물론, 정치적 무관심을 풍자하는 건전한 메시지가 핵심이며, 평소에 선을 많이 행해 놔야 내가 위급할 때 나도 도움을 받을 수 있다는 교훈 정도는 나도 잘 안다.

그러나..
공산주의자, 사회민주당원, 노조, 유대인?
그 무엇이 됐든 아무튼 내가 신념적으로 동조하지 않는 그룹이 먼저 쓸려 나갈 때, 내가 굳이 먼저 그들을 위해 같이 나서야 할 필요는.. 솔직히 난 못 느끼겠다.
그들이 남아 있다고 해서 내가 위기에 처했을 때 나의 '신앙'을 방어해 주는 데 기여할 거라고도 생각하지 않고 말이다. 오히려 크리스천은 그런 상황에서 마 10:17-20 같은 말씀을 더 떠올려야 하지 않겠는가?

글쎄, 배가 침몰한다거나 화재가 났다거나 해서 사상이고 종교고 나발이고 없이 다같이 죽게 됐을 때야 인간으로서 서로 도와야 한다. 그거야 맞는 말이다. 그러나 그 사상과 종교 자체만이 사람을 가르는 요인이라면 얘기가 달라진다는 뜻이다.

자라면서 이런 내 생각이 앞으로 또 바뀌게 될지는 모르겠다. 크리스천 중에서도 교리에 명백히 어긋나지 않는 범위 하에서 세상사의 참여에 굉장히 호의적이며, 각종 사회 운동이나 심지어 파업· 데모에 대해서도 더 열린(?) 생각을 갖고 있는 사람도 있다. 사실 집회와 결사의 자유 자체가 헌법에 있기도 하니 말이다. 이에 대한 이견과 논쟁은 이 세상이 어차피 제도적으로 성경대로 손쉽게 살 수 있는 형태로 되어 있지 않은 이상, 언제까지나 존재할 것 같다.

끝으로, 자유 얘기가 나왔으니 말인데 미국 얘기를 좀 하고 글을 맺겠다.
한쪽에서는, 미국이 점점 법이 바뀌어 개인의 자유가 제한되고 반기독교 성향으로 가고 있다고 엄청 걱정한다.
거리설교를 했다간 잡혀 가고, 학교에 성경의 개인적인 반입이 금지되고, 교회나 신학교계에서도 이제 동성애를 당당히 반대했다간 사회적으로 매장당하게 생겼다고 걱정한다. 특히 흑인 대통령이 부임하면서 그런 추세가 더욱 심해졌다고 그런다.

그러나 크리스천 중에서도 좌성향이 더 강한 다른 한쪽에서는, 저런 것보다는, 여전히 미국의 메이저 교회들이 저지르는 병크와 비리, 그리고 특히 정치 종교 결탁 행위만을 비판하느라 바쁘다. 그런 것들이 나라에서의 크리스천들의 자유를 억압하는 빌미를 주고 있다고 말이다.

양 극단 중에서 진실은 어디에 있는지 모르겠다. 미국은 모든 면모를 균형 있게 이해하기가 힘든 나라인 한편으로, 대형 교회들의 폐단은 한국이나 미국이나 별 차이 없다는 걸 느낀다.

Posted by 사무엘

2013/03/25 08:38 2013/03/25 08:38
, , ,
Response
No Trackback , 4 Comments
RSS :
http://moogi.new21.org/tc/rss/response/810

C/C++의 const 이야기

1.

C/C++에서 const라는 키워드는 어떤 변수를 선언할 때 타입과 함께 지정해 줄 수 있는 modifier 속성이다. 이와 비슷한 위상인 키워드로 volatile도 있다.

이 const의 큰 의미와 용도는 C와 C++에서 모두 동일하다. 바로, 한번 값이 정해지고 나면 그 뒤로 값이 또 바뀔 수 없다는 걸 뜻한다. 비슷한 용도로 쓰이는 매크로 상수나 enum과는 달리, const 개체는 엄연히 상수 역할을 하는 '변수'이기 때문에 L-value의 특성도 껍데기나마 지니며, 자기 주소를 & 연산자로 얻을 수 있다는 특징이 있다. (자기 주소가 있는데 왜 대입을 못 하니 ㄲㄲㄲ)

그런데 const라는 의미를 언어 차원에서 실현하는 방식이 C는 다소 느슨한 편이다.
C 언어도 const 변수에다가 대놓고 대입 연산자를 들이대는 시도 정도는 컴파일러가 에러로 대응하며 막아 준다. 그러나 강제로 const 속성을 없애는 형변환+포인터 연산 같은 것까지 저지하지는 못한다.

이는 마치, C/C++ 코드에서 변수를 초기화하지 않고 사용하는 걸 간단한 지역 변수 정도는 컴파일러가 알아서 발견하여 경고로 처리해 주지만, 복잡한 배열이나 포인터, 구조체의 경우를 일일이 체크하지는 못하는 것과 비슷한 맥락. 그래서

const int MARK = 100;
const int *p = &MARK;

printf("%d %d\n", MARK, *p);
*const_cast<int *>(&MARK) = 50;
printf("%d %d\n", MARK, *p); //이것이 문제.

이런 코드를 돌려 주면 C에서는 MARK가 처음에는 100이다가 나중에는 50이 되어 버린다! 이런 이유로 인해 C에서 const int는 껍데기만 const이지 case 문의 상수로 쓰이지도 못한다. 아, C 언어는 const_cast라는 연산자가 없으니, 그냥 *((int *)&MARK) = 50; 이라고 해야겠지만 말이다.

허나, C++은 이 정책이 바뀌어서 const를 다루는 방식이 좀 더 엄밀해졌다. 사실, 객체 지향 언어이다 보니 상수값을 취급하는 방식이 더 정확하고 엄밀해져야만 하는 게 마땅하다. 무작정 C 같은 '고수준 어셈블리' 패러다임만 추구해서는 곤란할 터이다.

C++은 MARK 변수가 차지하는 메모리에 들어있는 값과 상관없이 소스 코드에서 MARK가 그대로 쓰인 곳은 언제나 100을 대응시켜 준다. 다시 말해 위의 경우 100과 50이 출력된다. MARK와 *(&MARK)의 값이 달라지는 한이 있더라도 MARK는 언어 차원에서 처음 선언해 준 값이 그대로 유지되며, 진짜 매크로 상수처럼 쓰일 수 있다는 뜻이다. 신기하지 않은가? C와 C++ 사이의 교묘한 차이 중 하나이다. C/C++ 프로그래머라면 이 정도는 이미 아는 분이 많을 것이다.

2.

C/C++은 잘 알다시피 '선언 따로, 정의 따로'라는 좀 원시적이라면 원시적인 디자인 철학을 따르는 언어이다. 그래서 헤더에 들어간 선언은 그 선언을 사용하는 모든 번역 단위들이 include를 “매번” 해 줘야 하고, 그 선언에 대한 정의는 아무 번역 단위에다가 “한 번만” 써 주면 링크 때 알아서 말 그대로 '연결'이 된다. 그렇다, 걔네들은 원래 그런 언어이다.

자바나 C#은 클래스의 선언과 정의가 일심동체이고 그 클래스가 곧 번역 단위이다. 뭐, C++도 클래스를 선언하면서 멤버 함수의 몸체까지 헤더 파일 안에다 같이 써 주는 게 불가능하지는 않지만, 그건 간단한 인라인 함수를 만들 때에나 제한적으로 쓰이는 관행이다. 아니면 어차피 모든 클래스의 몸체가 헤더에 들어가야만 하는 템플릿일 때 정도.

자, 이런 이중적인 구조로 인해 C++은 static 멤버 변수의 정의조차도 클래스의 선언과 동시에 할 수가 없다.
여러 번역 단위에서 매번 인클루드되는 '선언부'에다가 한 번만 등장해야 하는 '정의부'가 동시에 들어갈 수는 없기 때문이다.
자바나 C#은 클래스 안에다가 static int MAX = 100; 같은 문장을 아무렇지도 않게 넣을 수 있으나, C++은 굳이 static int MAX;int CFoo::MAX = 100; 을 분리해서 써 줘야 한다.

그럼, C++의 클래스에서 멤버를 선언할 때 대입 연산자가 들어갈 일이란 오로지 순수 가상 함수를 선언할 때 쓰이는 = 0밖에 없는 걸까? (자바와 C#은 순수 가상 함수는 오히려 pure이나 abstract 같은 키워드를 따로 써서 표현함!)

놀랍게도 그렇지는 않다.
딱 하나 예외적으로, static const라는 속성을 지닌 간단한 '정수 계열'의 멤버는 클래스 안에다 선언과 함께 초기화를 하는 게 가능하다. 즉, 클래스 안에다가 static const int MAX = 100; 정도는 C++도 허용해 준다는 뜻이다.

물론 제약이 몹시 심하다.
static과 const 중 속성이 하나라도 빠져서는 안 된다. 그리고 배열이나 구조체의 초기화는 어림도 없다. static const WCHAR NAME[] = L"foo"; 같은 거 안 된다.

쉽게 말해 정수 정도면, 심벌이 있는 곳의 메모리 주소를 참고하는 게 아니라 심벌의 값 자체를 매번 집어넣어 주는 게 어차피 이득이니까 예외적으로 클래스 내부에서의 정의와 초기화가 허용되는 셈이다. 그러니 static const 정수는 그냥 메모리 주소를 얻는 게 가능한 enum 수준에 불과하다.

정수 계열은 심지어 __int64도 허용되지만 포인터는 허용되지 않는다. 그리고 부동소수점도 안 된다. static const double PI = 3.141592; 는 안 된다는 뜻이다. 이건 현재 GNU 계열 컴파일러에서만 지원하는 extension일 뿐, 표준은 아니다.

3.

한 소스 파일에다가 const 속성을 가진 커다란 정수 테이블 배열을 전역변수 형태로 만들었다. 그건 난수표가 될 수도 있고 time-critical한 실시간 계산 프로그램(게임이라든가)에서는 삼각함수나 로그값 테이블이 될 수도 있고 문자 코드 변환 테이블이 될 수도 있다.

그런데 다른 번역 단위에서는 그 테이블의 명칭을 extern으로 선언해 놓고 참고하여 사용했는데, 링크할 때는 그 명칭을 찾을 수 없다고 에러가 나는 것이었다. 본인은 그 이유를 알 수 없었다. 경험적으로 const 속성을 제거하면 문제를 피해 갈 수 있긴 했으나, 값을 변경하지 않는 상수 테이블을 일반 배열로 취급할 수도 없는 노릇이었다.

링크가 되지 않던 이유를 난 한참 뒤에야 알게 됐다.
C가 아닌 C++에서는 static이나 extern 명시가 없이 const로 선언된 전역변수는 기본적으로 extern이 아니라 static 속성이 부여된다. 그러니 그 번역 단위 내부에서만 쓸 수 있지, 외부로 명칭이 노출되지 않으며, 따라서 링크 에러가 난다.

왜 그렇게 정책이 바뀌었냐 하면 const 개체에 대해서는 이 글의 1번 항목에서 명시한 것과 같은 무결성을 보장하기 위해서인 듯하다.
심벌이 가리키는 메모리 주소는 값이 언제 바뀌어 있을지 모르니, const 개체의 값은 매 번역 단위마다 컴파일러가 소스 코드로부터 읽어들여서 확인하기 위해서이다.

이 조치를 무시하고 const 개체의 값을 다른 번역 단위에서도 사용하려면 extern을 명시적으로 지정해 줘야 한다.

extern const TYPE TABLE = ... 라고 바로 써 줘도 되고, external const TYPE TABLE; 이라고 먼저 선언만 한 뒤에 나중에 const TYPE TABLE = ... 을 쓰면 TABLE은 여느 전역변수와 마찬가지로 다른 번역 단위에서 참조가 가능한 extern 변수가 된다.

4.

Windows 환경에서 개발을 하다 보면 지금 설치되어 있는 운영체제의 SDK에 기본 내장되어 있지 않은 GUID를 수동으로 추가해서 사용해야 할 때가 있다.

GUID는 코드가 아니라 128비트짜리 난수가 들어있는 구조체에 불과하지만, 엄연히 const 전역변수들의 집합이기 때문에 선언부와 정의부가 따로 있다. 그리고 주요 GUID의 실제 값들은 플랫폼 SDK의 라이브러리 디렉터리에 있는 uuid.lib에 들어있다. kernel32, user32, gdi32만큼이나 딱히 우리가 지정을 안 해도 자동으로 링크되는 기본 라이브러리이기 때문에, 파일의 존재감을 모르는 분도 많을 것이다.

그런데 이놈의 GUID 하나 좀 쓰자고 헤더 파일과 소스/라이브러리 파일을 다 구비해 줘야 하는 걸까? 여간 번거로운 일이 아닐 수 없다. 귀찮다고 헤더 파일에다가 GUID 값을 몸체(정의)를 다 써 주면, 이론적으로는 그 헤더를 인클루드하여 사용하는 모든 번역 단위에 동일한 GUID의 몸체들이 obj 파일 내부에 중복 기재될 위험이 있기 때문이다.

결국 이 문제는 MS 컴파일러의 경우 자기만의 언어 확장을 만듦으로써 우격다짐으로 해결했다. DLL 심벌을 만들거나 사용할 때 __declspec(dllexport/dllimport)를 사용하는 것처럼 __declspec(selectany)라는 속성도 있다. 이것이 지정된 전역 변수는 여러 object에서 중첩 기재된 심벌이라도 링크 때 딱 한 몸체만 임의 선택된다.

여러 소스 코드에서 공통으로 쓰이는 GUID를 새로 추가하고 싶으면 #include <initguid.h>를 해 준 뒤, DEFINE_GUID 매크로로 새 GUID의 명칭과 값을 써 주면 된다. 이 매크로는 내부적으로 selectany 지정자를 사용한다.

결국 이것은 전역 변수 선언계의 #pragma once나 마찬가지이다. 중복 인클루드 방지에 이어 심벌 몸체의 중복 링크 방지 마크이다. 이게 다 C/C++에는 간편히 끌어다 쓰는 패키지 개념이 없이, 원시적인 헤더/라이브러리에만 의존하느라 컴파일러 제조사가 부득이 추가한 꼼수인 셈이다.

내가 늘 느끼는 거지만..
C++ 님 좀 짱이다. 10년이 넘게 파 왔지만 아직도 지금까지 몰랐던 사실들이 계속 발견된다.

Posted by 사무엘

2013/03/22 08:29 2013/03/22 08:29
, ,
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/809

철도/지하철역과 주차장

지하철은 역에 접근하는 여러 교통수단들과 어떤 방식으로 연계를 하고 있을까?

가장 먼저 도보는 trivial, self-explanatory이다. 설명이 더 필요하지 않다.
버스는 교통 카드를 이용할 경우 잘 알다시피 환승 할인이 된다(30분 이내에 환승시. 그리고 최대 5회까지). 외국에서도 서울의 대중교통 요금 시스템을 배우러 올 정도로 시스템이 합리적으로 잘 바뀌었다.

그리고 자전거가 있다. 저탄소 녹색 성장이라는 구호 아래 나라에서 나름 권장은 많이 한다. 역 주변에 자전거 주차대를 많이 설치해 놓았으며, 일부 역은 전동차에다 자전거 휴대도 가능하게 계단에 경사로를 설치하기도 했다.
하지만 우리나라는 도로에서 자전거를 몰기는 위험한 곳이 많고, 자전거를 휴대하여 승차하는 것도 마냥 쉽지만은 않다는 한계가 있다. 자전거는 스마트폰만큼이나 도난에도 취약한 편이고 말이다. (공공 자전거 주차대에는 CCTV 정도는 장착해 둬야 할 듯.)

허나, 지하철이 대중교통으로서 진짜로 자가용의 수요를 흡수하려면 버스나 자전거 같은 것뿐만 아니라 역설적으로 승용차와의 연계도 고려해야 하지 않을까 하는 생각이 든다. 딴 게 필요한 게 아니라 주차 시설 말이다. 버스 이용자가 지하철로 이동하는 것보다는 자가용 이용자가 지하철로 전향하는 게 훨씬 더 성공적인 현상이지 않은가? 교회로 치면 불신자가 교회로 새로 유입되어야지, 한 교회의 기존 신자를 다른 교회로 옮기기만 하는 제로썸 게임은 성장에 한계가 있단 말이다.

서울 중심부보다는 변두리 외곽의 역들이 이런 식으로 승용차를 맞이할 채비를 더욱 갖출 필요가 있다. 대중교통이 열악한 경기도 외곽에서 서울 근교의 전철역까지는 승용차를 타고, 거기서 서울 도심까지는 지하철을 타는 식의 통근 패턴이 지금보다 더 활발하게 정착되어야 한다.

그 주차장은 기본적으로 유료이지만 지하철 환승객에게는 주차료를 아주 크게 깎아 주는 식으로 운영되어야 할 것이다. 굳이 좁아 터진 서울 시내까지 스트레스 받으면서 차를 직접 끌고 가느니, 그냥 여기에 세워 놓고 주차료+지하철비가 시간과 비용면에서 훨씬 더 수지가 맞게끔 장점이 와 닿을 수 있어야 한다.

복정 역은 외곽+2개 노선 환승+주차장이라는 세 변수를 두루 갖춘 좋은 사례이다. 주변이 허허벌판이다 보니 주차장은 그냥 평지에서 운영되고 있다.
그러나 건물의 주차장은 보통 지하에 있는 편이고 지하철은 승강장도 지하에 있는데, 두 시설 다 지하에다 넣는 게 승객의 동선면에서 더욱 유리할 것이다. 이미 주차장 없이 완공되어 버린 기존역들은 어쩔 수 없고.

그러니 주차장이 갖춰진 지하철역은 사람이 드나드는 출입구뿐만 아니라 자동차가 드나드는 출입구도 어딘가에 생기게 된다. 개화산(5호선), 잠실(8호선. 2호선 말고), 동묘앞(1호선)처럼 인근에 지하철 관련 건물이 따로 있는 역들은 바로 그 건물의 지하에 주차장을 갖추는 것을 적극 검토할 필요가 있다.

이렇게 지하 승강장 + 지하 주차장이 갖춰진 역은 내가 알기로 공항 철도 서울 역, 그리고 신분당선의 판교 역 정도이다. 특히 판교의 경우 아직 지어지지 않은 건물의 내부에 있는 역으로 형태가 변모할 예정이니 지하 주차장이 미리 건설되는 게 당연한 이치이다. 상업 시설이 같이 갖춰져 있는 민자 역사들 역시 비슷한 이유로 인해 주차장이 있어야만 할 것이다.

내 경험상 성남의 8호선 수진 역은 출입구 바로 옆에 노상 공영 주차장이 있어서 본인 역시 몇 번 편리하게 이용한 적이 있다. 관리 요원이 퇴근한 저녁과 심야 시간대에는 무료 개방이기도 해서 더욱 좋았다.
다만, 도심 지하철이 아닌 광역전철들은 사정이 좀 낫다. 특히 지상 고가역들은 고가 아래가 주차장으로 개방되어 있는 경우가 많다. 경춘선 갈매 역이 좋은 예. 물론 무료이다.

중앙선 전철역들도 대체로 역 주변에 무료 주차장이 있다. 최근에는 서울 강북 최동단에 있는 양원 역의 주차장을 유용하게 이용했다. 장애인 차량 주차 구역만 안 건드리면 된다. 물론, 무료이기 때문에 자리가 언제나 있다는 보장은 못 한다. 운빨이 작용해야 된다.

공항 철도도 역마다 주차 시설을 갖추고 있다. 유료이더라도 요금이 공항 주차장보다 무척 저렴하기 때문에, 영종도를 내 기름값과 톨비를 들여서 자가용으로 직접 힘들게 건너느니, 차라리 차를 역에다 두고 공항까지는 열차로 가는 게 낫겠다는 생각을 절로 들게 만든다. 이 전략에 대해서는 교통 평론가 겸 철덕인 한 우진 님께서 정리해 놓은 게 있으니 참고하면 되겠다.

영종도 공항 화물 터미널 역의 근처에는 소규모 '무료 주차장'도 있다고 한다. 하지만 무료인 만큼 관리인도 없고, 차량 파손 및 도난 우려도 감수해야 한다고.

이렇듯, 현재 전철역들의 주차 편의는 역마다 케바케인 편이다. 그러나 서로 다른 교통수단들이 힘을 합쳐서 파이의 크기를 키우고 시너지 효과를 내려면 철도역들도 주차에 대한 배려를 좀 더 해야 할 것이다. 특히 서울 2기 지하철들은 내부가 깊어서 공간이 많아서 사업 아이템이랍시고 물품 보관 서비스 같은 것까지 한다는데, 자전거나 자동차 주차에 대한 배려도 생각해야 하지 않을까?

Posted by 사무엘

2013/03/19 19:36 2013/03/19 19:36
, , ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/808

윤곽선 글꼴과 아이콘 이야기

1. 윤곽선 글꼴의 기술 디테일

옛날에 인쇄가 물리적인 활자로 행해졌고 컴퓨터에서도 비트맵 글꼴이 대세이던 시절에는, '폰트 한 벌'이라 하면 여기에는 서체뿐만 아니라 고정된 크기라는 개념까지 포함되어 범위가 더욱 제한적이었다.
진짜 말 그대로 활자 한 벌이다. 그 폰트가 제공하는 서너 종류의 크기로만 글자를 쓸 수 있는 것이다. 더욱이 컴퓨터용 서체의 경우 화면용과 인쇄용이 따로 있기도 했고 말이다. 한 서체를 나타내는 그런 각 크기별 폰트들을 모두 통틀어서 자족(font family)이라고 불렀다.

오늘날처럼 트루타입, 오픈타입 같은 베지어 곡선 기반의 윤곽선 폰트 기술이 보편화된 관점에서 보면, 다양한 크기를 얻는 건 너무 당연하고 하나도 특별할 게 없는 특성이지 않은가. 과거의 관행은 상상조차 하기 어려운 것 같다.
허나 과거에는 오히려 한 폰트가 혼자서 다양한 크기의 font family의 역할을 다하는 게 보통일이 아니었다. 그래서 글꼴 대화상자를 보면 트루타입 글꼴을 선택했을 경우 “이 글꼴은 트루타입 글꼴로, 화면과 프린터에서 동일한 글꼴이 사용됩니다”가 떴었다.

윤곽선 기술을 통해 크기 문제가 해결되고서야 영문 서체의 경우, font family는 다양한 크기의 집합이 아니라 bold나 italic 같은 변형의 총칭을 일컫는 개념으로 변모했다. 트루타입 글꼴이 도입되기 전엔 bold/italic은 그냥 원글꼴을 산술 연산으로 변형해서 구현했을 뿐, 별도의 글꼴로 만든다는 걸 생각하기 어려웠다. 조합 가짓수가 감당을 못 할 정도로 너무 많아지니 말이다.

인간이 쓰는 글자는 글자 하나하나가 생각보다 꽤 정교한 벡터 드로잉이다. 수많은 윤곽선 글꼴 자형을 화면에다 래스터라이즈 하려면 비트맵 글꼴만 상대하면 될 때보다 훨씬 더 많은 계산량과 메모리가 필요하다. 쉽게 말해 게임에서 2D 스프라이트가 3D 폴리곤으로 바뀌는 것과 비슷하다.

(지금이 아무리 컴퓨터의 성능이 좋아져도 3D 폴리곤으로 옛날의 스타크래프트 같은 수십· 수백 마리의 저글링 개떼 블러드를 구현하는 건 좀 찰진 맛이 안 난다. ㄲㄲㄲㄲㄲㄲ 또한 둠 2에서 둠 3으로 넘어가면서 가장 먼저 사라진 게, 예전 같은 광활한 개방된 맵에서 쏟아져 나오던 몬스터 개떼들이지 않던가. 뭐 어쨌든..)

그래서 자동차에 변속기가 필수인 것처럼 윤곽선 글꼴을 찍는 시스템은 운영체제든 무슨 프로그램이든간에 글꼴 캐시(font cache)가 반드시 있어야 한다. 쉽게 말해 자주 쓰이는 윤곽선 글꼴은 래스터라이즈된 비트맵 결과를 미리 저장해 놓고 재사용하라는 소리다.

제아무리 강한 엔진이라도 3~4단 기어에서 바로 출발 가능한 자동차는 없듯, 제아무리 날고 기는 고성능 폰트 엔진이라도 글꼴 캐시 없이 윤곽선 글꼴을 비트맵과 별 차이 없는 속도로 찍을 수는 없다.
폰트 캐시는 각종 운영체제나 소프트웨어가 잡아먹는 메모리에서 생각보다 많은 비중을 차지하고 있다. PC의 성능이 시원찮던 1990년대 중반에는 운영체제의 한글판과 영문판의 요구 시스템 사양의 차이를 만들 정도였다.

2. 힌팅

윤곽선 글꼴을 찍는 시스템은 이것만으로도 굉장히 복잡해지는데 또 하나 간과할 수 없는 것은, 바로 저해상도에서 심각하게 보기 안 좋아지는 품질 문제였다.
수백~수천 픽셀의 EM 크기에서 만들어진 매끄러운 윤곽선 패스를 수~수십 픽셀대로 축소하여 래스터라이즈하다 보면 획이 빠지거나 뭉개지거나 굵기가 뒤죽박죽이 되어 버린다. 컴퓨터의 래스터 디스플레이는 연속적인 실수가 아니라 유한한 정수 개의 픽셀로 구성되어 있으니 말이다.

요즘 유행하는 서브픽셀(ClearType)이라든가 그레이스케일은 한 픽셀에 담을 수 있는 정보량 자체를 흑백보다 더 늘려서 글자를 좀 더 부드럽게 보이게 하는 anti-aliasing 방법이다. 그러나 그 전에는 monochrome 디스플레이에서도 최대한 글자가 예쁘게 래스터라이즈 되게 하려면...

일단, 싱거운 결론이지만 이것은 원론적으로 100% 완전한 해결이 불가능한 문제이다.
래스터라이저를 아무리 귀신같이 잘 만든다 해도 이 획과 저 획이 어느 크기로 scale했을 때 간격이 같고 굵기가 같아야 할 기준을 스스로 찾을 수는 없다. 그 기준 자체가 아주 모호하고 인위적이기 때문이다.

결국, 서양에서 만들어 낸 것은 '힌팅'이라고 불리는 기술이다.
트루타입 폰트의 경우 이 힌팅이 특허로 등록되어 있었을 정도로 고급 핵심 기술이었다.
폰트 래스터라이저의 동작 알고리즘을 다 안다고 가정하고, 특정 크기에서 특정 글자는 윤곽점을 빼거나 추가하거나 위치를 옮겨서 인위적으로 이런 식으로 래스터라이즈되게끔(= 사람 눈에 보기 좋게) 윤곽선을 변조한다.

쉽게 말해 부가 정보를 덧붙인다는 뜻이다. 그래서 명칭도 힌트, 힌팅이다. 이건 100% 자동화를 할 수 없으며 장인의 정교한 수작업이 동원해야만 넣을 수 있다.
Times New Roman 같은 서체를 6~11포인트 크기로 anti-aliasing이 없이 보면 정말 하나하나 수작업으로 비트맵을 만든 게 아닌가 하는 생각이 들 정도로 모양이 예쁜 걸 볼 수 있는데, 그건 내장 비트맵이 아니라 힌팅만으로 주어진 윤곽선을 변형하여 만들어 낸 결과물이다. 래스터라이저의 범용적인 알고리즘만으로 만들 수 있는 결과물이 결코 아니다!

오늘날은 픽셀 자체를 anti-alias하는 기술이 발달하여 예전보다는 힌팅의 필요성이 줄어들었지만, 그래도 힌팅을 해 주면 윤곽선의 제어점이 래스터라이즈 기준 지점에 더 가까이 옮겨지기 때문에 뿌옇게 찍힐 것이 더 깔끔하고 배경과 글자 사이가 더 높은 채도로 찍히는 긍정적인 효과를 얻을 수 있다.

다만, 화면의 해상도까지 예전의 도트 프린터 수준으로까지 올라간다면 힌팅은 정말로 할 필요가 없고 그냥 anti-aliasing만으로 충분한 지경이 될 수 있다. 힌팅은 마치 옛날의 256색 팔레트 제어 기술만큼이나 legacy로 전락하는 날이 올지도 모른다.

그리고 한글이나 한자처럼 문자 집합의 크기가 커서 일일이 수제 힌팅을 도저히 줄 수 없는 문자는.. 애시당초 크기별로 내장 비트맵을 일일이 만들어 넣는 게 속 편하다. 뭐 요즘은 그 관행도 '맑은 고딕'을 시작으로 서서히 변하고 있긴 하지만 말이다.

3. 아이콘과 아이콘 패밀리

글꼴이 비트맵에서 윤곽선으로 넘어가면서 겪은 변화와 비슷한 맥락의 변화를 겪고 있는 곳이 또 있으니, 그건 바로 아이콘이 아닌가 싶다.

원래 응용 프로그램의 아이콘은 32*32 16컬러 크기만 있었다. 그러던 것이 Windows 95 이래로 16*16이 활발히 쓰이기 시작해서 메뉴나 작업 표시줄 같은 데서 좋은 인상을 주려면 오히려 16*16을 심혈을 기울여 잘 만들어야 하는 지경이 되었다. 아이콘 하나 때문에 Windows API에는 윈도우 클래스 등록용으로 WNDCLASSEX라는 구조체가 새로 만들어졌고 RegisterClassEx 함수가 도입되었다.

그러다 아이콘의 색깔은 256색 이상으로 늘고, 윈도우 XP에서부터는 트루컬러 정도가 아니라 알파 채널이 들어간 32비트 색상 아이콘이 등장했다. 덕분에 아이콘 하나도 크기가 수만 바이트로 늘고 프로그래머가 대충 발로 만들 수 없는 물건이 되어 버렸다. Visual Studio IDE는 최신 2012버전까지도 32비트 아이콘은 내용을 볼 수만 있지 고칠 수는 없다. 전용 그래픽 에디터가 필요해졌다.

그리고 윈도우 비스타부터는 32*32보다도 더 큰 48*48이 표준 크기로 또 추가되고, 아예 크기가 세 자리 수로 진입한 png 이미지가 아이콘 안에 들어가는 경지가 되었다. 이젠 아이콘 이미지도 예전 관행처럼 압축 없이 저장했다간 크기가 너무 커지기 때문이다.
XP까지만 해도 수만 바이트에 불과하던 간단한 메모장 프로그램이(notepad.exe) 별로 기능이 추가된 것도 없는데 비스타 이후부터 크기가 세 배로 뻥튀기 된 건 전적으로 아이콘 이미지 때문이다.

이렇게 아이콘의 크기가 커졌음에도 불구하고 아이콘은 역시나 본문 글자만큼이나 16*16의 작은 크기에서도 자신의 본분에 충실해야 한다. 이 점에서 아이콘은 글꼴과도 비슷한 구석이 있다. 단지, 크기뿐만 아니라 색상까지 고려해야 한다는 차이가 있을 뿐.

요즘은 굳이 16색까지 갖출 필요는 거의 없어졌지만, 프로그램의 한 아이콘은 똑같은 컨셉이더라도 자고로 최소한 256색과 32비트 트루컬러, 그리고 16, 32, 48과 심지어 경우에 따라서는 24나 40 같은 그 중간 크기까지 따로따로 갖추고 있어야 한다. 옛날에 크기별로 비트맵 글꼴을 따로 만들던 거랑 정확히 같은 맥락이니, 그야말로 font family에 착안하여 icon family라는 말을 만들어야 할 판이다.

그리고 작은 크기일 때는 수제 도트 노가다 말고는 정말 답이 없다. 큰 이미지를 무식하게 축소시켰다가는 품질이 그야말로 개판이 되기 때문. 아이콘에 무슨 힌팅 같은 게 있는 것도 아니니 말이다.
오늘날 아이콘은 점점 벡터 이미지처럼 되고 있는 면모가 있지만, 그렇다고 도트 노가다가 필요하지 않은 것도 아니니 참 오묘한 존재가 되어 간다는 생각이 든다.

language bar에 표시되는 각종 IME 아이콘들의 경우, 담겨 있는 정보는 단색이더라도 무조건 32비트 알파 채널 이미지로 만들지 않으면 운영체제가 아이콘을 화면에 제대로 표시해 주지를 않는다(Vista 이상 기준). 아이콘 출력을 재래식 GDI가 아니라 GDI+ 같은 다른 계층으로 하는 것 같다.

Posted by 사무엘

2013/03/16 19:31 2013/03/16 19:31
, ,
Response
No Trackback , 3 Comments
RSS :
http://moogi.new21.org/tc/rss/response/807

MFC와 View 오브젝트 이야기

1. 들어가는 말: MFC에 대한 큰 그림

MFC는 Windows API를 단순히 C++ 클래스 형태로 재포장만 한 게 아닌 독창적인 기능이 다음과 같이 최소한 세 가지 정도는 있다.

  • 가상 함수가 아니라 멤버 함수 포인터 테이블을 이용하여 메시지 핸들러를 연결시킨 메시지 맵. MFC 프로그래머 치고 BEGIN/END_MESSAGE_MAP()을 본 사람이 없다면 간첩일 것이다.
  • 운영체제가 제공하는 핸들 자료형들과 C++ 개체를 딱 일대일로 연결시키고, 특히 MFC가 자체적으로 생성하지 않은 핸들이라도 임시로 C++ 개체를 생성해서 연결했다가 나중에 idle time 때 자동으로 소멸을 시켜 주는 각종 handle map 관리자들. 절묘하다.
  • 20년도 더 전의 MFC 1.0 시절부터 있었던 특유의 document-view 아키텍처. 상당히 잘 만든 디자인이다.

양념으로 CPoint, CRect, CString 같은 클래스들도 편리한 물건이긴 하지만, 그건 너무 간단한 거니까 패스.

사실, MFC는 Windows API를 객체지향적으로 재해석하고 포장한 수준은 그리 높지 않다. 본디 API가 prototype이 구리게 설계되었으면, MFC도 해당 클래스의 멤버 함수도 똑같이 구린 prototype을 답습하고 내부 디테일을 그대로 노출했다.

이와 관련하여 내가 늘 드는 예가 하나 있다. 당시 경쟁작 라이브러리이던 볼랜드의 OWL은 radio button과 check button을 별도의 클래스로 분리했다. 그러나 MFC는 그렇게 하지 않았다. 운영체제 내부에서 둘은 똑같은 버튼 윈도우이고 스타일값만 다를 뿐이기 때문이다. 그러니 MFC로는 동일한 CButton이다. 그리고 CStatic도 마찬가지.
아마 기존 응용 프로그램의 포팅을 용이하게 하려고 의도적으로 이런 식으로 설계한 것 같긴 하지만, 이것 때문에 MFC를 비판하는 프로그래머도 물론 적지 않았던 게 사실이다.

그러나 인간이 하루 하루 숨만 쉬고 똥만 만드는 기계가 아니듯, MFC는 단순한 API 포장 껍데기가 아니라 다른 곳에서 더 수준 높은 존재감을 보여준다. 오늘 이 글에서는 document-view 아키텍처 쪽으로 얘기를 좀 해 보겠다.

2. view가 일반적인 윈도우와 다른 점

MFC는 뭔가 문서를 생성하여 작업하고 불러오거나 저장하는 일을 하는 업무용 프로그램을 만드는 일에 딱 최적화되어 있다. 그렇기 때문에 MFC AppWizard가 FM대로 생성해 주는 기본 코드는 아주 간단한 화면 데모 프로그램만 만들기에는 구조가 필요 이상으로 복잡하고 거추장스러워 보인다.
그냥 프레임 윈도우의 클라이언트 영역에다 바로 그림을 그려도 충분할 텐데 굳이 그 내부에 View라는 윈도우를 또 만들었다. 그리고 View는 Document 계층과 분리돼 있기 때문에, 화면에 그릴 컨텐츠는 따로 얻어 와야 한다.

이런 계층 구분은 소스 코드가 몇십~몇백만 줄에 달하는 전문적인 대형 소프트웨어를 개발할 걸 염두에 두고 장기적인 안목에서 해 놓은 것이다.
먼저, View와 Document를 구분해 놓은 덕분에, 동일한 Document를 여러 View가 자신만의 다양한 설정과 방법으로 화면에 동시에 표시하는 게 가능하다. 텍스트 에디터의 경우, 한 문서의 여러 지점을 여러 창에다 늘어놓고 수시로 왔다 갔다 하면서 편집할 수 있다. 한 창에서 텍스트를 고치면 수정분이 다른 창에도 다같이 반영되는 것이 백미.

일례로, MS 워드는 기본, 웹, 읽기, 인쇄, 개요 등 같은 문서를 완전히 다른 방식으로 렌더링하는 모드가 존재하지 않던가(물론, MS 워드가 MFC를 써서 개발됐다는 얘기는 아님). 게다가 이 중에 실제로 위지윅이 지원되고 장치 독립적인 레이아웃이 사용되는 모드는 인쇄 모드뿐이다. 인쇄를 제외한 다른 모드들은 인쇄 모드보다 문서를 훨씬 덜 정교하게 대충 렌더링하는 셈이다.

이렇듯, view는 그 자체만으로 독립성이 충분한 특성을 가진 계층임을 알 수 있다. view는 프레임 윈도우와도 분리되어 있는 덕분에, 한 프레임 윈도우 내부에 splitter를 통해 하위 view 윈도우가 여러 개 생성될 수도 있다.
CWnd의 파생 클래스인 CView는 윈도우 중에서도 바로 저런 용도로 쓰이는 윈도우를 나타내는 클래스이며, 부모 클래스보다 더 특화된 것은 크게 두 가지이다. 하나는 CDocument와의 연계이고 다른 하나는 화면 출력뿐만 아니라 인쇄와 관련된 기능이다.

SDI형 프로그램에서는 view 윈도우 자체는 계속 생성되어 있고 딸린 document만 수시로 바뀌기 때문에, document를 처음 출력할 때 view가 추가적인 초기화를 하라고 OnInitalUpdate라는 유용한 가상 함수가 호출된다. 그리고 화면 표시와 프린터 출력을 한꺼번에 하라고 WM_PAINT (OnPaint) 대신 OnDraw라는 가상 함수가 호출된다. 하지만 프린터 출력이 화면 출력과 기능면에서 같을 수는 없으니 CDC::IsPrinting이라든가 OnPrepareDC 같은 추가적인 함수도 갖고 있다.

그러고 보니 MFC의 view 클래스는 운영체제에 진짜 존재하는 '유사품' 메시지인 WM_PRINT 및 WM_PRINTCLIENT와는 어떻게 연계하여 동작하는지 모르겠다. 화면의 invalidate 영역과 긴밀하게 얽혀서 BeginPaint와 EndPaint 함수 호출을 동반해야 하는 WM_PAINT와는 달리, PRINT 메시지는 invalidate 영역과는 무관하게 그냥 창 내용 전체를 주어진 DC에다가 그리면 된다는 차이가 존재한다. 거의 쓰일 일이 없을 것 같은 메시지이지만, AnimateWindow 함수가 창 전환 효과를 위해 창 내용 이미지를 미리 내부 버퍼에다 저장해 놓을 때 꽤 유용하게 쓰인다.

3. CView의 파생 클래스들

MFC에는 CView에서 파생된 또 다른 클래스들이 있다. 유명한 파생 클래스 중 하나인 CCtrlView는 MFC가 자체 등록하는 클래스 말고 임의의 클래스에 속하는 윈도우를 그대로 view로 쓰게 해 준다.
그래서 운영체제의 시스템 컨트롤을 view로 사용하는 CTreeView, CListView, CEditView, CRichEditView 등등은 다 CCtrlView의 자식들이다.

  • 프로그램의 클라이언트 영역에다 CTreeView와 CListView를 splitter로 나란히 배열하면 '탐색기' 내지 레지스트리 편집기 같은 외형의 프로그램을 금세 만들 수 있다.
  • <날개셋> 편집기가 MFC를 써서 개발되던 버전 2.x 시절에는 문서 창을 CCtrlView로부터 상속받아 만들었다.

CCtrlView 말고 CView의 또 다른 메이저 파생 클래스로는 CScrollView가 있다. 얘는 이름에서 유추할 수 있듯, view에다가 스크롤과 관련된 기본 구현들이 들어있다. 텍스트 에디터 같은 줄 단위 묶음 스크롤 말고, 픽셀 단위로 컨텐츠의 스크롤이 필요한 일반 워드 프로세서, 그래픽 에디터 같은 프로그램의 view를 만들 때 매우 유용하다. 마우스 휠과 자동 스크롤 모드(휠 클릭) 처리도 다 기본 구현돼 있다.

인쇄 미리 보기 기능은 온몸으로 scroll view를 써 달라고 외치는 기능이나 다름없으며, 실제로 MFC가 내부적으로 구현해 놓은 '인쇄 미리 보기' view인 CPreviewView 클래스도 CScrollView의 자식이다.
단, 요즘은 Ctrl+휠을 굴렸을 때 확대/축소 기능도 구현하는 게 대세인데 배율까지 관리하는 건 이 클래스의 관할이 아닌 듯하다. 그건 사용자가 직접 구현해야 한다.

그럼 스크롤 가능한 view로는 오로지 자체 윈도우만 설정할 수 있느냐 하면 그렇지는 않다. CFormView는 대화상자를 view 형태로 집어넣은 클래스인데 그냥 CView가 아니라 CScrollView의 파생 클래스이다. 워낙 설정할 게 많아서 환경설정 대화상자 자체가 세로로 쭈욱 스크롤되는 프로그램은 여러분의 기억에 낯설지 않을 것이다.

옛날에 윈도우 3.x 시절의 PIF 편집기처럼 클라이언트 영역에 대화상자 스타일로 각종 설정을 입력 받는 게 많은 프로그램을 만들 때 CFormView는 대단히 편리하다. 대화상자는 여느 윈도우들과는 달리, 자식으로 추가된 컨트롤들에 대해 tab 키 순환과 Alt+단축키 처리가 메시지 처리 차원에서 추가되어 있다.

4. CScrollView 다루기

처음에는 CView로부터 상속받은 view를 만들어서 프로그램을 열심히 만들고 있다가, 뒤늦게 view에다가 스크롤 기능을 추가해야 할 필요가 생기는 경우가 종종 있다.
이미 수많은 프로그래밍 블로그에 해당 테크닉이 올라와 있듯, 이것은 대부분의 경우 base class를 CView에서 CScrollView로 문자적으로 일괄 치환하고 몇몇 추가적인 코드만 작성하면 금세 구현할 수 있다.

클래스 이름을 치환한 뒤 가장 먼저 해야 할 일은 스크롤의 기준이 될 이 view의 실제 크기를 SetScrollSizes 함수로 지정해 주는 것이다. OnInitialUpdate 타이밍 때 하면 된다. 안 해 주면 디버그 버전의 경우 아예 assertion failure가 난다.

여기까지만 하면 반은 먹고 들어간다. OnDraw 함수의 경우, 전달되는 pDC가 아예 스크롤 기준대로 좌표 이동이 되어 있다! 즉, 내부적으로 (30, 50) 위치에다가 점을 찍는 경우, 현재 스크롤 시작점이 (10, 20)으로 잡혀 있으면 화면상으로 이 위치만치 뺀 (20, 30)에 점이 찍힌다는 뜻이다. 내가 수동으로 스크롤 좌표 보정을 할 필요가 없다. 아, 이 얼마나 편리한가! invalid 영역의 좌표도 화면 기준이 아닌 내부 기준으로 다 이동된 채로 전달된다.

그러니 CView 시절에 짜 놓은 그리기 코드를 어지간하면 수정 없이 CScrollView에다 곧바로 써먹을 수 있다. 다만, 최적화만 좀 신경 써 주면 된다. 당장 화면에 표시되는 영역은 수백 픽셀에 불과한데 수천 픽셀짜리의 전체 그림을 몽땅 불필요하게 계산해서 그리는 루틴을 OnDraw에다 때려박지 않도록 주의해야 한다.
이때 유용한 함수는 RectVisible이다. 이 영역이 invalidate되었기 때문에 반드시 그려 줘야 하는지의 여부를 알 수 있다.

그 다음으로 신경을 좀 써야 하는 부분은 마우스 클릭이다.
마우스 좌표는 화면 기준으로 오지 내부 기준으로 오지는 않으므로, 내부 개체에 대한 hit test를 하려면 마우스 좌표에다가 GetScrollPosition(현재 스크롤 위치) 함수의 값을 더하면 된다.
그리고 화살표 키로 무슨 아이템을 골랐다면, 그 아이템의 영역이 지금의 화면 범위를 벗어났을 경우 스크롤을 시켜 줘야 한다. 수동 스크롤은 ScrollToPosition 함수로 하면 된다.

화면의 일부 영역을 다시 그리도록 invalidate하는 것도 스크롤 위치 반영이 아닌 그냥 지금 화면 기준의 좌표를 지정하면 된다. 그러면 OnDraw 함수에서는 스크롤 위치가 반영된 내부 좌표 기준으로 refresh 위치가 전달된다.

끝으로, 마우스로 어떤 개체나 텍스트를 눌러서 끌든, 혹은 단순 selection rectangle을 만들든 그 상태로 포인터가 화면 밖으로 나갔을 때, 타이머를 이용한 자동 스크롤도 구현해야 할 것이다. 이 역시 자동화하기에는 customization의 폭이 너무 넓기 때문에 MFC가 알아서 해 주는 건 없다. 알아서 구현할 것. 이 정도면 이제 스크롤 기능을 그럭저럭 넣었다고 볼 수 있을 것이다.

이 정도면 어지간한 개발 이슈들은 다 나온 것 같다.
참, 혹시 재래식 GDI API가 아니라 GDI+를 쓰고 있는 프로젝트라면 CScrollView로 갈아타는 걸 신중히 해야 할 것 같다. GDI+는 MFC가 맞춰 놓은 GDI 방식의 기본 스크롤 좌표를 무시하고 DC의 상태를 난장판으로 만들어 버리기 때문이다. GDI+는 재래식 GDI보다 느리지만 곡선의 안티앨리어싱과 알파 블렌딩이 뛰어나니 아무래도 종종 사용되게 마련인데..

간단한 해결책 중 하나는, GDI+ 그래픽은 CreateCompatibleDC / CreateCompatibleBitmap을 이용한 메모리 DC에다가 따로 그리고, 본디 화면에다가는 그 결과를 Bitblt로 뿌리기만 하는 것이다. 그렇게 하면 아무 문제가 발생하지 않고, 심지어는 속도도 내 체감상으로는 더 빨라지는 것 같다.

Posted by 사무엘

2013/03/13 19:34 2013/03/13 19:34
, , ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/806

« Previous : 1 : ... 140 : 141 : 142 : 143 : 144 : 145 : 146 : 147 : 148 : ... 214 : Next »

블로그 이미지

그런즉 이제 애호박, 단호박, 늙은호박 이 셋은 항상 있으나, 그 중에 제일은 늙은호박이니라.

- 사무엘

Archives

Authors

  1. 사무엘

Calendar

«   2024/04   »
  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        

Site Stats

Total hits:
2664752
Today:
1927
Yesterday:
1553