« Previous : 1 : ... 23 : 24 : 25 : 26 : 27 : 28 : 29 : 30 : 31 : Next »

본인은 예전에 쓴 이 글에서 언급된 노트북을 개인용 컴퓨터로 아주 만족스럽게 잘 쓰고 있다. 이제 2년이 경과했지만, 예전 노트북과는 달리 이번 4대 노트북은 잔고장이 발생한 적이 전혀 없고 심지어 아직까지 운영체제를 재설치한 적도 없다.

http://moogi.new21.org/tc/141

그런데 지금으로부터 두세 주 남짓 전부터는 컴퓨터에 뭔가 이상 징후가 느껴졌다.
바로 컴퓨터의 속도가 체감상으로는 평소의 절반 이하 수준으로 곤두박질친 것이다.

창의 크기를 바꾸거나 인터넷 화면을 스크롤할 때의 반응이 매우 둔해지고, 컴파일도 엄청 느려졌다.
Aero를 꺼도 화면이 버벅대는 게 변함없었기 때문에 이건 그래픽 카드 문제가 아니며, 디스크 I/O 쪽의 병목 현상 역시 아니었다. 전적으로 CPU가 느려진 것이 확실했다. 심하게 버벅댈 때는 마우스 포인터까지 버벅대며 움직였다.
더구나 AC 전원을 연결해도 속도가 여전히 느렸기 때문에 전원 절약과 관련된 CPU 감속 역시 아니었다.

본인은 평소에 컴퓨터 관리를 얼마나 결벽증에 가깝게 하고 지내는데 메모리 부족이나 악성 코드 때문도 아니고..
이 노트북은 물론 예전 노트북에서도 이런 식의 이상 증세를 경험한 적은 없었기 때문에 처음엔 좀 당황했다.

이 문제는 의외로 굉장히 쉽게 해결됐다. 그것 때문일 수도 있다고 예상은 했지만 정말로 전적으로 그것 때문일 줄은 몰랐다.

바로... 냉각팬에 잔뜩 낀 먼지 때문이었다. 그것만 제거해 주자 컴퓨터는 거짓말처럼 본디 속도로 되돌아왔다!
팬이 잘 안 돌아가고 열이 못 빠져나가고 있으면 컴퓨터가 안전을 위해 스스로 알아서 감속 운행(?)을 해 왔던 것이다. 굳이 절전을 위한 감속뿐만 아니라 말이다. 먼지를 제거하기 전이나 후나 밖에서 느껴지는 컴퓨터 소음 내지 팬 바람은 별 차이가 없는데, 컴퓨터의 성능이 이렇게 확 달라지다니 참 뜻밖이고 의미 있는 경험을 했다. (날씨가 너무 덥고 레일이 너무 뜨거우면 알아서 감속을 하는 KTX처럼?? ㅋ)

10년이 넘게 노트북을 써 왔지만 이런 일은 처음이었다. 하긴, 2년이 넘도록 A/S 센터를 단 한 번도 찾지 않을 정도로 컴퓨터가 건강했기 때문에, 내부 청소를 할 일도 지금까지 없었다. 이렇게 한번 먼지를 제거해 줬으니 앞으로 또 이 노트북이 몇 년간 쌩쌩하게 돌아가 주기를 기대한다.

하긴, 다른 가전제품에서도 비슷한 경험을 한 적이 있다. 멀쩡하던 에어컨이 갑자기 전혀 동작하지 않고, 아무리 온도를 낮춰도 더운 바람밖에 나오지 않는 것이었다. “올여름엔 지내는 데 당분간 애로사항이 잔뜩 꽃피겠구나! A/S 센터 연락처가 어디더라?” 이러고 있었는데 문제의 원인은 정말 어처구니없는 곳에서 찾아낼 수 있었다.

에어컨으로부터 나오는 더운 바람이 빠져나갈 통로가 막혀 있었던 것이다. ㅜ.ㅜ
겨울에는 보온을 위해 닫아 두지만 한여름에는 응당 개방해 놓아야 한다.
컴퓨터든 에어컨이든 열이 잘 빠져나가게 해 줘야 한다는 교훈을 얻었다. 이건 자동차에도 동일하게 적용되는 원칙일 것이다.

Posted by 사무엘

2010/08/05 09:09 2010/08/05 09:09
Response
No Trackback , 6 Comments
RSS :
http://moogi.new21.org/tc/rss/response/339

오늘날 쓰이고 있는 컴퓨터라는 기계의 이론적 근간을 마련한 사람으로는 앨런 튜링(영국)이라든가 폰 노이만(헝가리->미국) 같은 불세출의 천재 수학자가 있다. 그런데 영국에서는 튜링보다 먼저, 범용적인 계산 기계라는 개념을 떠올렸던 천재가 있었으니 바로 찰스 배비지(1792-1871)이다. 전산학도라면 배비지의 해석 기관에 대해 들어 보지 못한 사람이 없을 것이다.

그는 시대를 너무 앞서 간 괴짜 덕후였으며, 재정 부족과 당대의 기계 제작 기술의 부족 때문에 그의 꿈이 당장 완전히 실현되지는 못했었다.
참고로 창조 과학회에서는 배비지가 독실한 크리스천 과학자였다고 띄우고 있다. ㅋㅋ 링크를 소개한다. (그 반면 튜링은 동성애자인 데다 자살로 생을 마감했으니, 기독교 진영에서는 별로 좋아할 구석이 없겠다)
http://www.kacr.or.kr/library/itemview.asp?no=243

그런데, 이 시대에 영국에서는 배비지의 계보를 이을 천재 수학자가 또 태어났는데, 이번엔 여자였다. 그녀의 이름은 에이다(Ada Lovelace; 1815-1852). 인류 역사상 최초의 프로그래머라고 일컬어지는 먼치킨 엄친딸 공순이이다. 이 두 사람은 사진기가 발명되기 거의 직전의 시기를 살았던 사람인지라 사진은 전해지지 않으며, 초상화나 스케치로 그려진 모습만 전해 내려온다. 하지만 에이다는 상당한 미녀였다고 한다. Lovelace라는 성은 백작 작위를 얻은 남편의 성에서 딴 것.

에이다는 ‘자고 일어나 보니 유명해졌더라’ 같은 어록으로 유명한 시인 바이런의 외동딸이었다. 아버지가 희대의 바람둥이었다고 하는데 딸 역시 머리가 비범했고 나중에는 도박에 빠졌다고 하니 평범한 인생을 살지는 못했으며, 그러다 자궁암 때문에 30 중반의 나이로 요절하여 세상을 짧고 굵게 살다 갔다.

그녀는 남들이 도무지 이해를 못 하던 찰스 배비지의 해석 기관의 원리를 간파하고 그 기계의 무한한 가능성을 알아차린 당대의 극소수 덕후 중 하나였다. 오늘날 절차형 프로그래밍 언어의 기본 골격이라 할 수 있는 루프, 조건문, 서브루틴 같은 개념을 떠올렸다. 그것을 처리하는 기계를 만들고 그 틀을 바탕으로 프로그램을 짜서 돌리면, 기계로 음악도 작곡하고 그림도 그리게 할 수 있다고 상상했다. 무려 19세기에 말이다!

배비지 역시 에이다의 재능과 글빨에 큰 감명을 받았다고 한다. 그녀는 기계가 숫자를 처리하기 위해서는 10진법이 아닌 2진법이 유리하다는 발상을 했으며, 베르누이의 수를 구하는 ‘프로그램’을 해석 기계를 기반으로 실제로 작성하기도 했다. 그래서 최초의 프로그래머이다. ㅎㄷㄷ;;; (베르누이는 유체 역학에서 배우는 베르누이의 원리를 발견한 그 과학자 겸 수학자이다. H2O가 뭔지 정도야 '문과 출신'도 알겠지만, 베르누이의 수가 뭔지는 어지간한 이과 출신도 들어 보지 못했을 것이다.)
http://www.bernoulli.org/

그로부터 100년 남짓한 시간이 지나고 전자식 컴퓨터가 실제로 발명되었을 때, 한 절차형 프로그래밍 언어는 바로 이 여성 프로그래머를 기려, 그녀의 이름을 따서 Ada라고 명명되었다. 프로그래밍 언어의 이름에다가 전설적인 프로그래머의 이름을 붙였으니 매우 고무적인 현상이라 하지 않을 수 없다.

Ada 언어는 1983년에 첫 제정되었으며, 이는 시기적으로 C++의 탄생 시기와 일치한다. C++의 고안자가 스웨덴 사람인 반면, Ada의 초창기 핵심 고안자는 프랑스 사람이었다. Ada는 당시 난립하던 프로그래밍 언어들의 통합을 목적으로 미국 국방성으로부터 강력한 후원을 받으며 만들어졌다. 그래서 그쪽 바닥--무기를 가동하는 전산 시스템 같은--에서는 지금까지도 표준 언어로 쓰이고 있다고 한다.

본인은 에이다 언어에 대해서 잘은 모르지만, 이 언어의 문법은 일단 파스칼과 얼추 비슷하다. 암호스러운 기호들보다는, 타이핑을 더 하더라도 깔끔하게 영어 단어 표기를 선호한다. 패키지 단위로 빌드가 이루어지는 것도 C/C++보다는 파스칼 방식이다. (참고로 베이직 언어의 경우, MS의 퀵베이직은 include에 컴파일/링크까지 C언어의 빌드 모델을 그대로 이어받은 반면, 파워베이직은 파스칼과 비슷한 방식을 쓰고 있다는 것이 흥미로움.)

Ada에 대해서도 예제 코드를 보면 이해가 빠를 것이다. 미리 말하지만 C/C++의 사고방식보다는 파스칼의 관점에서 보면 굉장한 동질감을 느낄 수 있다. 심지어 대소문자 구분이 없는 언어라는 것까지도 똑같다. 비록 요즘 C++의 영향을 받은 자바나 C# 같은 언어들의 추세는 대소문자 구분이지만 말이다. 더 자세한 것은 http://www.adahome.com/ 참고.
아니, 그러고 보니 Ada는 수학자의 이름을 따서 지어진 것까지 파스칼과 일치하는구나.

Ada는 기존 언어들의 통합을 목적으로 만들어진 만큼, 이것저것 여러 언어 요소들을 집어넣느라 표현의 자유가 굉장히 넓으며, 제공되는 언어 요소가 많다.
무슨 말이냐 하면, 가령 다차원 배열(a[2,5])과 배열의 배열(a[2][5])을 구분하여 모두 표현할 수 있고,
단축연산이 지원되는 논리 연산자와(and also나 or else), 그렇지 않은 연산자(그냥 and나 or)가 모두 제공된다.

파스칼처럼 0..100 같은 식의 subrange도 당연히 지원되고, 똑같은 정수형이라도 int 같은 기본 자료형과 완전히 호환되는 단순 alias/typedef인지, 아니면 int와 호환되지 않는 별개의 타입인지도 지정 가능하다. 타입 하나는 C/C++과는 비교도 할 수 없이 정교하고 엄격하게 만들 수 있어서 좋다.
함수 안에다 함수도 당연히 만들 수 있고, while이나 for 같은 loop 자체에다가 label 이름을 붙여서 다중 loop을 goto문 없이 바로 탈출하는 것이 가능하다.

이런 것들만 있는 것도 아니고, 어쨌든 Ada는 처음 발표되었을 때는 문법이 필요 이상으로 너무 복잡하고 컴파일러로 다 구현하는 데 난감하다는 불만도 있었다고 한다. Quicksort의 고안자께서도 그렇게 불만을 제기한 사람 중 하나였다고 함. 하지만 오늘날은 C++도 가히 복잡성 면에서는 타의 추종을 불허하는 경지에 도달한 것 역시 사실이다.

그다지 여성향을 느낄 수 없는 것 같은 전산학 분야에도 이런 사연이 있는 여성의 이름을 딴 프로그래밍 언어가 존재한다는 것이 흥미롭다. 여성 프로그래머 모에~ 이다. 하하. =_=;;
참고로 성경에는 Ada와 가장 비슷한 이름으로 유대 왕국의 왕 Asa가 있으나, 물론 성별부터가 다르다.

에이다가 살아 있던 19세기에 우리나라는 뭘 하고 있었는가? 딱 흥선대원군 시절이다. 그런데 본인은 그 시절 조선의 역사에 대해서 좋은 기억이 도무지 없다. 19세기 하면 홍 경래의 난, 전 봉준 동학 운동, 게다가 명성황후 시해 등... 나라는 점점 탐관오리 부정부패와 외세의 침략에 휘말려 막장으로 치닫다가 이내 일제에게 주권을 빼앗기고 만다. 그런데 그 시절에 영국은 저런 상상을 초월하는 학문적 성과가 속속들이 발표되고 있었으니... 오옷 역시 킹 제임스 성경과 철도를 만들어 낸 나라! 괜히 전세계를 호령한 선진국이 아니다.

게다가 전자기학의 대부인 마이클 패러데이(1791-1867), 제임스 맥스웰(1831-1879), 그리고 나중엔 찰스 다윈(1809-1882)이 전부 동시대를 살았던 영국의 과학자들이다! 이 정도면 충격과 공포가 아닐까? 맥스웰도 마찬가지이지만 패러데이는 다윈의 진화론을 단호하게 반박하고 부정한 것으로 유명한 사람인데( http://www.kacr.or.kr/library/itemview.asp?no=644&orderby_1=subject 참고), 에이다로부터 연애편지를 받고서 사랑의 힘으로 더욱 분발(?)하여 전자기력  관련 실험을 성공적으로 마칠 수 있었다 ‘카더라’. 그러고 보니 패러데이는 찰스 배비지와 거의 동갑이니 흠좀무.

끝으로, 잘 알려지지 않은 사실인데, 에이다 부인은 암 치료 과정에서, 당시 통용되던 bleeding (또는 bloodletting) 시술 중에 사망했다. 쉽게 말해 피를 빼내는 작업이다. 옛날 사람들은 병에 걸리면 환자의 혈액에 독소가 차기 때문에 그걸 제거하면 병이 나으리라고 믿었던 모양이다. 마치 체했을 때 손가락 끝을 따는 것처럼? 그런데 그렇게 바늘로 찔러서 몇 방울 따는 것과는 비교도 못 할 정도로 피를 많이 퍼내다가 오히려 환자를 탈수와 쇼크로 인해 죽게 한 경우도 많았다고.

미국의 초대 대통령이며 세계 최초로 자기 임기만 마치고 권좌에서 물러난 지도자인 조지 워싱턴도 bleeding 중에 죽었다. 문헌을 찾아보니 그는 은퇴 후 노후로 인한 폐렴· 천식· 독감 합병증에 걸렸는데, 의사가 치료랍시고 허약한 노인의 피를 5 pint... 거의 2리터가 넘게 빼냈던 것이다. 성인 인체의 전체 혈액이 5리터 남짓이라 하며 헌혈만 해도 아주 건장한 성인 남자를 대상으로 많이 채혈할 때가 3~400ml 정도 하는데, 그의 5배가 넘는 양을 뽑아냈으니 환자의 명을 재촉하는 행위밖에 더 되었겠는가? 그 당시는 혈액에 면역 시스템이 있다는 걸 모르던 시절이었고, 육체의 생명이 피에 있다는 걸(레 17:11) 실감을 못 했었다.

http://gwpapers.virginia.edu/articles/wallenborn.html
http://www.av1611.org/amazing.html

Posted by 사무엘

2010/07/29 08:36 2010/07/29 08:36
, , ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/333

C 언어는 다른 언어가 언어 차원에서 기본으로 제공해 주는 상식적인 기능이 없고, 대신 별도의 함수 호출에 의존하는 형태인 게 몇 가지 있다. 거듭제곱 연산이 대표적인 예이고, 문자열 타입도 언어가 자체 제공하지 않는다. 사실은 동적(힙) 메모리를 할당하는 기능 자체가 아예 없다.

그 이유는 간단하다. 저런 기능들은 컴퓨터 CPU 명령 차원에서 직관적으로 구현 가능하지 않기 때문이다. 그래서 연산자가 그렇게도 많다는 C 언어는 거듭제곱 연산자가 없으며 pow라는 함수를 호출해야 한다. (그나마 파스칼은 그런 함수조차도 없기 때문에, exp와 log 함수 조합으로 임의의 수의 거듭제곱을 얻어내야 한다.)

메모리 할당도 마찬가지이다. 메모리 관리는 CPU뿐만이 아니라 해당 운영체제/플랫폼이 담당하는 비중도 크기 때문에, 작은 언어인 C가 언어 차원에서 자체 제공하지는 않는 것이다. malloc, free, realloc 같은 함수를 써야 한다. 그러면 윈도우 운영체제의 C 라이브러리는 내부적으로 또 HeapCreate, HeapAlloc 같은 더 저수준의 윈도우 API를 이용해서 그런 메모리 관리 기능을 구현해 준다.

그런데 C++에서는 드디어 동적 메모리 할당과 해제 기능이 언어 차원에서 연산자로 추가되었다. 바로 new와 delete 연산자이다. 그때까지 영단어로 이루어진 연산자는 sizeof가 고작이던 것이 새로 추가되었으며, 그 후로 *_cast라든가 typeid 등 여러 영단어 연산자가 C++에 추가되었다. 메모리 할당이라면 몰라도 개체의 생성과 소멸에 따른 생성자와 소멸자 함수 호출은 언어 차원에서 책임져 줘야 하는 영역이기 때문에 별도의 연산자가 생긴 것이다.

연산자가 추가된 덕분에 일단 type casting이나 sizeof 계산을 할 필요가 없게 된 것은 좋다.

pData = new DATA[nCount];
pData = (DATA *)malloc(sizeof(DATA)*nCount);

물론 번거로운 문법 정도야 C 시절에도 매크로로 대체 가능했겠지만 말이다.

#define NEW_C(T, N)  (T *)malloc(sizeof(T)*(N))

그러나 new 연산자는 malloc 함수처럼 범용적인 void* 포인터를 되돌리는 건 지원하지 않으며, 해당 타입의 배수가 아닌 크기의 메모리도 할당할 수 없다. 그렇기 때문에 가변 길이 구조체 같은 메모리를 할당하는 건 오히려 더 불편할 수 있다.
또한 할당 아니면 해제만 지원되지 C 함수처럼 realloc 기능도 없다. C++의 메모리 연산자는 오로지 개체의 생성과 소멸에만 초점을 둔 것이다. 그렇기 때문에 이것이 기존 C의 메모리 관리 함수를 완전히 대체하지는 못할 것으로 보인다.

new 연산자로 데이터 타입을 지정한 뒤에는 new DATA[100] 처럼 배열 첨자가 올 수 있고, 아니면 new Object(x, y)처럼 해당 개체의 생성자 함수에다 넘겨 줄 인자가 올 수도 있다. 두 문법 중 오로지 하나만 허용된다.
그러므로 생성될 때 생성자 함수 인자 전달이 필요한 개체는 배열로 만들 수 없다. 그러나 인자가 필요한 생성자 함수가 존재한다 할지라도, 전부 default argument가 있어서 대체가 가능하다면 배열을 만들 수 있다.

1. new operator vs operator new

이 new 연산자(new operator)는 내부적으로 operator new라는 함수를 호출하는 형태로 구현되어 있으며, 이 특수한 함수는 나름 오버로딩이 가능하다! (delete도 마찬가지) 비록 개체를 생성하여 생성자 함수를 호출한다는 기본 기능은 C++의 특성상 불변이지만, 이 연산자가 하는 일 중 메모리를 할당하고 해제하는 계층은 customize가 된다는 뜻이다.

void *operator new(size_t size);
void operator delete(void *ptr);

operator new 함수는 첫째 인자는 무조건 포인터 크기와 같은 부호 없는 정수형이어야 한다. 부호 있는 정수형도 허용되지 않는다. 그리고 리턴값은 void *이어야 한다.
한편 delete 함수는 첫째 인자는 무조건 void *이어야 하고, 함수의 리턴값은 void여야 한다. 일단 기본적인 생김새는 malloc, free와 완전히 일치한다는 뜻.

당연한 말이지만 이 함수만 단독 호출이 가능하다.
malloc(100)을 쓸 곳에 그냥 operator new(100) 이라고만 써도 된다. 그러면 어차피 new char[100]과 비슷한 효과가 나게 된다. C++ 언어는 이 함수들의 기본 구현을 라이브러리 차원에서 제공하고 있다. 만약 기본 C/C++ 라이브러리를 사용하지 않으면서 new/delete 연산자도 쓰고 싶다면 내가 직접 이들 함수를 구현해 줘야 한다.

거기에다 나만의 인자를 추가한 operator new/delete를 만들 수 있다. 예를 들어, C/C++ 라이브러리가 사용하는 프로세스 기본 힙이 아닌 다른 곳에다가 메모리를 할당하고 싶다면 이렇게 코드를 써 주면 된다.

void *operator new(size_t size, HANDLE hHeap)
{ return HeapAlloc(hHeap, 0, size); }

HANDLE hMyHeap = HeapCreate( ... );
Object *pt = new(hMyHeap) Object( ... );

new 바로 옆에다가 전달해 주는 인자는 operator new의 둘째 이후의 인자로 전달된다. delete도 비슷한 방식으로 오버로딩 가능하다. 놀랍지 않은지?

모든 개체과 기본 자료형에서 통용되는 global scope의 operator new/delete가 있는 반면, 특정 클래스에서만 통용되는 new/delete 함수를 만들 수도 있다. 함수 프로토타입은 동일하다. 이 new/delete 함수는 굳이 static을 지정해 주지 않더라도 언제나 static으로만 선언되기 때문에, 클래스 내부에 있더라도 가상 함수 지정이나 this 포인터는 지원되지 않는다. 또한 생성자· 소멸자· 대입 연산자 등과는 달리, 파생 클래스로 상속도 된다.

2. new operator vs new[] operator

그런데, 더욱 충공깽한 사실은 new와 new[] (delete도 delete[])가 구분되어 있다는 것. 이런 구분이 언제 필요하냐 하면 소멸자 함수가 존재하는 개체의 배열을 선언할 때이다. (물론 기본 자료형이 아니라 개체를 배열로 만드는 경우는 드물지만 말이다.)
우리가 요청하는 메모리의 크기와 실제로 운영체제로부터 할당되는 메모리의 크기는 여러 가지 요인으로 인해 일치하지 않는 경우가 있으며 후자가 전자보다 대체로 더 크게 잡힌다.

배열을 delete로 해제할 때는 여기에 있던 배열 각 원소들에 대해서도 소멸자 함수를 일일이 호출해 줘야 하는데, 원래 여기에 개체가 정확하게 몇 개 있었는지를 메모리 블록만 봐서는 알 수 없게 되는 것이다.
그래서 1980년대에 C++이 처음 등장했을 때는 delete 연산자에다가 배열의 개수까지 지정을 해 줘야 했다.

int *arr = new int[nCount];
Object *ptr = new Object[nCount];
(....)
delete arr; //기본 자료형은 그냥 이렇게 지워도 무방
delete[nCount] ptr; //이놈은 흠좀무

C++은 그렇잖아도 garbage collector도 없어서 불편해 죽겠는데 배열의 원소 개수까지 프로그래머가 관리해야 한다니, 이게 말이나 되는 소리인가?

프로그래머의 원성이 빗발친 덕분에 시스템이 바뀌었다. 배열의 원소 개수는 C++이 메모리를 할당하면서 내부적으로 알아서 관리하도록 바뀌고 원소 개수를 생략 가능해졌다. 그러나 그래도 이게 배열이라는 힌트는 알아서 줘야 한다. 배열일 때와 그렇지 않을 때 C++이 메모리를 관리하고 인식하는 방식은 여전히 서로 약간 다르기 때문이다.

delete arr; delete[] ptr; 를 해도 괜찮다는 소리이지 delete arr; delete ptr; 처럼 구분이 완전히 사라진 건 아니다.

그래서 operator new/delete를 오버로드했다면 operator new[]/delete[]의 오버로드도 지원된다. 둘은 인자의 의미나 하는 일의 차이는 전혀 없다. 단지 new[]의 경우, 연산자의 리턴값 포인터에다가 곧바로 개체가 저장되지는 않는다는 차이가 존재할 뿐이다. 배열 원소 개수가 앞부분에 먼저 저장되고 그 뒤의 공간부터가 쓰인다.

자바와 C#에서 볼 수 있듯, 요즘 대세는 개체는 무조건 new로 선언하는 것이다. 그게 언어 문법까지 더 명료하게 만들어 주는 효과까지 있다. 그러나 C++은 기본 자료형이든 개체든 스택과 힙에 모두 선언 가능하고, 심지어 함수 전달도 둘 다 call by name이나 reference 방식이 모두 가능하다.

일반적으로 컴파일러들은 C++의 operator new/delete도 내부적으로는 C의 malloc/free로 구현한다. 기능이 완전히 동일한데 둘의 동작 방식이 달라야 할 이유가 전혀 없기 때문이다. 그러나 원칙대로라면 malloc으로 할당한 포인터를 delete로 해제한다거나, new로 할당한 메모리를 free로 해제하는 것은 허용되지 않는 비추 행동이다. 그렇게 섞어 쓰지는 않는 게 좋겠다.

Posted by 사무엘

2010/07/28 08:27 2010/07/28 08:27
, ,
Response
No Trackback , 6 Comments
RSS :
http://moogi.new21.org/tc/rss/response/332

C++에는 namespace라는 엄청난 키워드가 존재한다.
namespace는 소스 코드에 존재하는 수많은 명칭(심볼)들로 하여금 이들이 통용되는 구획을 강제로 구분해 준다. (명칭의 decoration도 달라지기 때문에, 링크 때도 동명의 심볼들이 서로 구분 가능함)
방대한 프로그램을 짜고 특히 남이 만든 여러 라이브러리들을 한데 뭉뚱그려 관리하다 보면 함수나 전역 변수 이름, 심지어 매크로 같은 게 겹쳐서 링크 시 충돌이 있을 수 있다. 이때 namespace는 그런 문제에 대한 근본적인 해결책이 되어 준다.

C++은 C에 비해 scope이라는 개념이 더욱 발달했다.
여기서 말하는 scope이란, 단순히 전역 변수냐 지역 변수냐 하는 생명 주기 차원이 아니라, 어떤 심볼이 언어의 문맥 차원에서 인식되고 접근이 허용되는 범위를 일컫는다.
가령, C++ 클래스 내부에 있는 static 변수는 생명 주기로 말하자면야 C의 전역 변수와 다를 바가 없다. 그러나 단순 전역 변수와는 확연하게 다른 scope을 지니고 있다. 그래서 :: 같은 연산자도 생겼다.

예전에는, 특히 C 시절에는 global이라는 기본 namespace 하나밖에 없는 것과 마찬가지였지만 C++에서는 나만의 namespace를 정의할 수 있고, 심지어 이중 삼중으로 namespace 안에 또 namespace를 만들 수도 있다. 심볼들의 입체적인 관리와 구별이 가능해진 것이다.
사실 namespace는 90년대에 나중에 추가된 키워드로, 도스 시절의 볼랜드 C++ 같은 컴파일러에서는 지원도 되지 않는다. (MFC 역시 namespace는커녕 템플릿조차 없던 시절부터 만들어져 온 클래스 라이브러리인지라, namespace를 사용한 흔적이 없음)

그런데, namespace가 하는 일은 클래스가 하는 일과 좀 중복이 있어 보인다.
클래스도 그 자체가 이미 자신만의 새로운 scope을 만들어 낸 것이기 때문이다.
클래스 내부에 public으로 선언된 static 변수 내지 함수하고,
namespace 내부에 존재하는 전역 변수 내지 함수는 언뜻 보기에 위상이 완전히 똑같다.

밖에서는 클래스::이름, 또는 namespace::이름 이렇게 ::을 써서 호칭하는 것마저 동일하다.
클래스도 안에 클래스 내지 구조체가 중첩해서 존재할 수 있으며, 심지어 클래스 내부에서만 통용되는 enum이나 typedef를 선언하는 것도 가능하다.
그럼 도대체 namespace만의 특징은 무엇이 있을까? 아래의 코드를 생각해 보자.

namespace NS {
   class A {};
   void f( A *&, int ) {}
}

//void f(NS::A *&, int) {} //이게 뭘까?

class CS {
public:
   class A {};
   static void g( A *&, int ) {}
};

이렇게만 보면 NS라는 namespace에 소속된 클래스 A와 전역 함수 f,
그리고 CS라는 클래스에 소속된 클래스 A와 전역 함수 g는 서로 그게 그거 같고 정말 차이를 발견할 수 없어 보인다.
다만, class나 struct와는 달리 namespace는 뭔가 인스턴스화하는 자료형을 만드는 것이 아니기 때문에, 닫는 중괄호 뒤에 세미콜론을 붙일 필요가 없다. 뭐, 그 정도 차이는 존재한다.

이들 각 심볼을 외부에서 접근하는 방법도 완전히 동일하다. 아래 코드를 보라.

NS::A *pfm = NULL;
NS::f(pfm, 0); //하지만 바로 f(pfm, 0)만 해도 된다. 이유는 나중에 설명

CS::A *qfm = NULL;
CS::g(qfm, 0);

그런데, namespace는 클래스에 없는 부가 기능이 좀 있다.

첫째, 바로 ADL(Argument dependent name lookup)이라는 기법이다.
C++ 컴파일러는 함수의 argument의 타입으로부터 함수의 소속 scope를 자동 추론하는 기능이 있다.
namespace NS에 속해 있는 f를 호출할 때 굳이 NS::를 할 필요가 없다.
왜냐하면 f가 받는 함수 인자 중에 이미 NS에 소속된 자료형이 존재하기 때문에, 컴파일러는 이 f를 먼저 global scope에서 살펴봐서 없으면 NS namespace 안에서도 찾아보게 된다.

함수의 인자를 이용하여 함수를 추정한다는 점에서는 함수 오버로딩의 확장판이라고 볼 수도 있겠다.
사실, 위의 소스에서 주석을 쳐 놓은 global scope의 f 함수까지 정의한다면 컴파일러는 어느 f 함수를 선택해야 할지 모호하다면서 에러를 낸다.
이런 기능은 클래스에는 존재하지 않는다. g 함수를 호출할 때는 매번 CS::g를 해 줘야 한다.

둘째, using 키워드이다.
반복되는 타이핑을 좀 줄이고 싶어하는 건 프로그래머들의 공통된 희망 사항이다.
타입 선언을 좀더 간편하게 하기 위해서 C/C++에는 typedef라는 키워드가 있고, 베이직이나 파스칼에는 구조체 참조를 좀더 간편하게 하려고 With 같은 키워드가 있다.

그와 마찬가지로 C++에는 여타 namespace에 있는 명칭을 매번 :: 연산자 없이도 바로 참조 가능하도록 using namespace 선언을 제공한다. using namespace std; 처럼 말이다.
using namespace NS를 한번 해 주면, 그 뒤부터는 NS::A *pfm 마저도 A *pfm로 축약 가능해진다.
using의 용법으로는 또 다른 것도 있는데, 설명서를 읽어 봐도 잘 모르겠다. 정말 무진장 복잡하고 저런 걸 언제 어디서 써먹으면 될지 영 감이 안 잡힌다. =_=;;
다만, namespace가 아니라 클래스에 의해 만들어진 scope에 대해서는 그런 것 역시 지원되지 않는다.

셋째, namespace p = FS; 처럼, namespace에다 별명(alias)을 붙여 쓰는 것도 가능하다. 길고 복잡한 다단계 namespace를 손쉽게 축약하는 방법이다. 저런 문법도 있다니, 가히 충격과 공포.

끝으로, 이름 없는 namespace는 마치 C 시절의 static 전역변수/함수처럼, 해당 번역 단위(소스 코드; translation unit) 바깥으로 함수나 변수 심볼이 노출되지 않게 하는 역할을 한다는 것도 알아 두면 좋다.
이 정도 되면 namespace는 C++ 언어에서는 단순히 클래스 이상으로 자신만의 역할이 있다고도 볼 수 있겠다.

가장 먼저 언급한 ADL에 대해서는 비판은 있다. namespace에다가 일종의 예외 규정을 만드는 것이나 마찬가지이기 때문에 C++ 문법을 더욱 복잡하게 하고 컴파일러 만들기도 난해한 언어로-_- 만드는 데 일조했기 때문이다. 그러나 프로그래밍의 편의를 위해서 ADL은 어쩔 수 없이 꼭 필요하기도 하다. 이게 없으면 다른 namespace에 소속되어 있는 클래스의 오트젝트에 대해서는 연산자 오버로딩조차도 제대로 못 하게 되는 경우가 생길 수도 있기 때문이다.

참고로 자바나 C#처럼 C++보다 나중에 등장한 본격 객체 지향 언어들은 C++처럼 global scope이라는 게 존재하지 않는다. 전역 함수나 전역 변수라는 게 애초부터 존재하지 않으며 모든 심볼들은 무조건 클래스에다 소속되어 있어야 한다. 또한 이런 언어들은 C++ 같은 텍스트 include라든가 링크라는 개념이 없으며, 클래스가 곧 패키지요 namespace의 형태로 구조가 잘 짜여 있다. 그래서 C++처럼 namespace를 별도로 갖고 있지는 않다.

Posted by 사무엘

2010/07/07 08:44 2010/07/07 08:44
Response
No Trackback , 5 Comments
RSS :
http://moogi.new21.org/tc/rss/response/313

※ CreateFont

받는 인자가 무려 14개나 되어, Win32 API 전체를 통틀어 손꼽힐 정도로 받아들이는 인자가 많다. MSDN 레퍼런스 없이는 함부로 꺼내 쓰지도 못한다.
그렇다고 해서 딱히 포인터를 전달한다거나, 결과값을 받는다거나 하는 것도 아니고 인자들은 다 단순한 int 아니면 문자열들이다. 이 값들을 LOGFONT라는 구조체에다 담아서 한꺼번에 전달하는 CreateFontIndirect라는 함수가 별도로 있기도 하다.

하지만 실제로 쓰이는 인자는 글꼴 이름과 크기(가중치 100), 그리고 아주 가끔 빌드/이탤릭 여부(가중치 20) 정도가 진짜 전부이다.
전문적인 워드/그래픽 프로그램이 아니고서야 장평값이 다르다거나 기울여진 상태로 글자를 찍을 일이 있기나 할까? 운영체제가 기본으로 지정한 퀄리티(안티앨리어싱 여부) 말고 다른 걸 지정할 일도 거의 없기는 마찬가지. 좀더 간단한 형태의 함수가 있었으면 좋겠다. MFC의 CFont::CreatePointFont()처럼 말이다.

※ CreateProcess

인자 개수 10개. 프로그램을 실행하는, 좀더 정확히 말하면 프로세스를 생성해 주는 가장 저수준 함수이다.
실생활에서는 파일 이름과 매개변수(가중치 100)만 있으면 거의 다 통용되고, 거기에다 초기 시작 디렉터리와 윈도우 초기 배치 방식을 나타내는 SW_* 값 정도만 있으면 될 것이다(가중치 20).

하지만 이 함수 역시 사용하기는 무진장 까다롭다. 파일 경로를 나타내는 버퍼는 read only여서는 안 되고 write 가능한 영역에 있어야 한다. 운영체제가 이 문자열을 그대로 strtok 스타일로 tokenize를 했다가 다시 원래 형태로 되돌려 주기 때문이다. null 문자를 잠시 삽입하므로 이는 버퍼를 수정하는 행위임.
디버거 붙일지 여부, 각종 보안 설정, 환경 변수 값, 심지어 멀티모니터 환경에서 프로그램을 초기에 띄울 모니터 위치 등등.. 별별 정보를 다 지정 가능하기 때문이다.

게다가 리턴값으로는 새로 생긴 프로세스 및 스레드의 식별자와 핸들 같은 것도 돌아오는데, 그 핸들은 이 함수를 호출한 프로세스가 알아서 Close 해 줘야 된다. 여간 손이 많이 가는 게 아니다.
MS는 프로그램을 실행할 때 16비트 시절의 잔재인 WinExec 함수를 사용하지 말고 이 함수를 사용할 것을 권하나, WinExec의 간편함(달랑 명령줄과 윈도우 배치 방식만 넘겨주면 됨!)의 유혹을 뿌리치기는 쉽지 않을 것이다. 사실 WinExec도 이제는 내부적으로 CreateProcess를 호출하는 방식으로 실행된다.

※ CreateFile

인자 개수 7개. C 표준 함수에 있는 fopen처럼 파일 이름과 용도(많지도 않다. 읽기 아니면 신규 생성)만 있었으면 좋겠지만, 역시 운영체제 API이다 보니 보안 관련, 파일 공유 여부 같은 잡다한 정보들이 엄청 많이 들어간다. 파일을 그저 문서의 열기/저장 기능 구현 용도로나 쓴다면 대부분의 세부 옵션들은 필요하지 없으며, 그냥 디폴트만 넘겨 주면 된다.
사실 이 함수는 디스크 상의 파일뿐만 아니라 파일의 형태로 표현 가능한 각종 운영체제 오브젝트들을 생성할 수도 있는 상당히 다재다능한 녀석이다. 피타고라스는 세상만물이 수라고 말했다면, 유닉스에서는 모든 게 파일이라고 한다. 윈도우 운영체제도 그런 철학이 아주 없지는 않다.

※ GetOpenFileName

우리에게 친근한 파일 열기 대화상자를 꺼내 주는 함수. 이제 얘는 인자로 일일이 입력 정보를 받는 방식을 애초부터 포기하고, 대신 크고 아름다운 구조체만을 받는 형태이다. C++로는 응당 클래스를 만들어 쓴다. MFC의 CFileDialog처럼.

하지만 실제로 자주 쓰이는 정보는 열기/저장 여부, 시작 디렉터리, 파일 포맷 필터 말고 있나? (가중치 100)
거기에다가 아주 가끔씩 파일 복수 선택 여부라든가 '읽기 전용으로 열기' 같은 자잘한 옵션만.. (가중치 30)
이렇게 굳이 구조체 만들 필요 없이 간단하게 실행이 끝나는 함수도 좀 있었으면 좋겠다는 생각을 꽤 자주 하곤 했다.

※ TaskDialogIndirect

너무나 클래식한 API인 MessageBox 함수를 대체하는 새로운 UI 대화상자 함수로, 윈도우 비스타에서 처음으로 추가된 걸로 잘 알려져 있다.
다른 건 '메시지 박스' 그대로 쓰면 되는데 "다음부터 이 메시지 표시하지 않음" 체크 같은 딱.. 2% 아쉬운 면모만 보충하고 싶을 때... 이 함수가 가히 구세주이다. 참고로 이 함수를 쓰려면 공용 컨트롤 6.0 매니페스트가 필수라는 걸 잊지 말자.

물론 얘는 TaskDialog와 TaskDialogIndirect로 아예 두 버전으로 나뉘어 있고, 쓸데없이 괜히 함수 프로토타입만 복잡한 게 아니라 진짜로 사용자가 실제로 쓸 수 있는 기능이 엄청 많은 게 맞다. 최근에 만들어진 만큼 API를 비교적 잘 설계했다는 생각이 든다. 하지만 정확하게 기억은 안 나는데 TaskDialog에는 정작 내가 꼭 써야 하는 customize 기능이 빠져 있어서 어쩔 수 없이 훨씬 더 복잡한 TaskDialogIndirect 함수만 써야 했던 것 같다. 그건 아쉬운 점.

대화상자의 제목, 큰 제목, 본문, 각 버튼의 모양, 링크, 라디오 상자 등등등...
이건 구조체가 아니라 아예 대화상자 생성 스크립트(XML 기반)를 받게 해도 이상할 게 없어 보인다.
최신 MFC에는 이 task dialog를 감싸는 클래스가 "당연히" 추가되어 있으며, 각종 상업용 GUI 패키지들에는 XP 이하 운영체제에다가도 task dialog를 자체 구현한 솔루션이 있기도 하다.

Posted by 사무엘

2010/06/29 08:53 2010/06/29 08:53
,
Response
No Trackback , 3 Comments
RSS :
http://moogi.new21.org/tc/rss/response/306

C++의 typename 키워드

C++ typename 키워드의 용법은 크게 두 가지이다. 그러나 주된 목적은 동일하다. 다음에 나오는 명칭이 변수도 될 수 있고 변수의 타입 이름이 될 수도 있는 문맥일 때, 이것이 명백하게 후자임을 알려 주는 것이다.

먼저, typename은 잘 알다시피 템플릿 인자를 선언할 때 class 대신 쓸 수 있다.

template<class T> void Swap(T& a, T&b );
template<typename T> void Swap(T& a, T&b );

위의 두 줄은 의미상 완전히 동일하다.
템플릿이 C++ 언어에 처음으로 추가되었던 당시에는 typename이라는 키워드가 없었고 템플릿 인자를 선언할 때에도 class를 썼던 것이다.
그러나 이것은 의미상으로 문제가 있었다. 아래의 예를 보자.

template <class T, int N>
class MyClass {
public:
    T data[N];
};

MyClass<int, 20> obj;

템플릿 인자로는 잘 알다시피 자료형 이름 내지 정수 숫자가 올 수 있다.
그런데 int N에 해당하는 템플릿의 인자로는 마치 일반 함수의 인자처럼 int 값에 해당하는 20이 쓰였다. 그런데, class T에 해당하는 첫째 인자는 그럼 T라는 클래스에 속하는 개체가 쓰인단 말인가?

전혀 그렇지 않다. 여기서는 진짜로 특정 type에 속하는 20 같은 값이 아니라, type 자체가 인자로 와야 하기 때문이다.
그래서 의미상 완전히 다르다는 걸 표현하기 위해 typename이라는 키워드를 class 대신 사용할 수 있게 되었다. 매우 바람직한 조치이다. class라는 키워드는 이제 진짜로 새로운 클래스를 선언할 때만 쓰도록 하자.

그리고 다음 용법이 개념상으로 진짜 중요하다. scope resolution과 관계가 있다.
A가 클래스 이름일 때 A::B라는 표현을 썼다면, C++의 특성상 B는 A의 멤버 변수일 수도 있고, A 클래스 내부에 선언된 다른 타입(클래스, 구조체 따위)의 이름일 수도 있다. 그 클래스 내부에 무엇이 선언돼 있냐에 따라서 해석이 달라진다. 처리가 어렵다.

그런데 설상가상으로 A 자체가 실제로 무슨 타입이 들어올지 모르는 템플릿 클래스의 인자라면? B에 대한 해석은 그야말로 귀에 걸면 귀걸이, 코에 걸면 코걸이가 될 수밖에 없어진다. A의 실체가 무엇이건 B의 정체는 컴파일 시점 때 다 결정되어야 하는데 말이다.

그럴 때 typename A::B를 써 주면, B는 A가 무엇이건 상관없이 변수가 아니라 말 그대로 type 이름으로 처리되어야 함을 컴파일러에게 알려 준다. 이 키워드는 절대적인 모호성 해결보다는, C++의 문법 해석의 복잡성을 좀 줄이고 컴파일러 개발을 더 수월하게 만들려는 목적이 더 크다고 보면 정확하다.
자, 이번에도 이해를 돕기 위해 예제 코드를 보자.

template<typename T>
class MyClass {
public:
    struct MYSTRUCT {
    };
    static MYSTRUCT dat;

    typename T::COMP pp;
};

struct SAMPLE {
    struct COMP {
    };
};

MyClass<SAMPLE> obj;

바로 이런 식으로 MyClass가, 자신의 템플릿 인자 T가 내부적으로 또 갖고 있는 COMP라는 구조체를 이용하기 위해서는 pp를 저렇게 typename을 줘서 선언해야 한다. COMP를 자료형 이름임을 명확하게 해 줄 필요가 있다.

비슷한 이유로 인해,

template<typename T>
typename MyClass<T>::MYSTRUCT MyClass<T>::dat;

템플릿 클래스 내부에 있든 구조체 형태로 된 static 멤버를 밖에서 또 정의해 줄 때, typename을 넣어 줘야 한다.
똑같이 MyClass<T>::로 시작하는 명칭이지만 typename이 선행된 MYSTRUCT는 자료형이고, dat는 멤버 변수로 인식되는 근거가 여기에 있는 것이다.
늘 생각하는 것이지만 C++의 세계는 참으로 심오하다. -_-;;

Posted by 사무엘

2010/06/28 08:56 2010/06/28 08:56
,
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/305

프로그램의 권한

1.
병특 회사에서 근무하던 시절의 일이다.
그때 본인은 본업을 넘어-_-, ActiveX 컨트롤을 만들어 관리하던 적이 있었다.
(사용자들이 아무리 ActiveX 욕하고 우리나라가 무슨 MS 공화국이네 뭐네 하면서 까도, 일선 개발자들은 위에서 까라면 깔 수밖에 없다. 더구나 본인은 국방부 시계가 돌아가던 중! ^^;;; )
ActiveX는 내부에 플래시 UI를 하나 만들어서 플래시는 웹 상에서 사용자와 소통을 하고, ActiveX는 플래시와 소통을 하면서 플래시만으로 하기 힘든 네이티브 코드를 수행했다. 내가 왕년에 저런 일까지 했다니..

그런데 문제가 있었다. 정확하게는 기억이 안 나지만, ActiveX든 플래시든.. 뭔가 로컬에 있는 파일을 참조하는 건 아무 문제가 없었는데 웹에 있는 놈을 가져오는 건 아무 이유 없이 도무지 되지 않고 작동을 거부하는 것이었다.
먼 옛날 일이 됐으니 망정이지, 이것 때문에 당시 회사에 환멸을 느낄 정도로 좌절하고 삽질했었다. -_-;;

문제의 원인은 보안이었다. 그 당시 갓 출시된 플래시 7이던가 8이던가.. 하필 그때부터 보안 정책이 딱 바뀌어 플래시의 액션스크립트는 아무 웹에서나 정보를 덥석 가져오지 못하게 되었다.
그 반면, 내 로컬 컴퓨터의 ....\flash player\#security\flashplayertrust 이런 디렉터리에다가 configuration file을 만들어서 접근을 허용하는 웹 주소를 먼저 적어 줘야 하고, 플래시는 허용된 웹으로만 접근할 수 있다.
자세한 정보: http://kb2.adobe.com/cps/116/1165eb90.html

어쨌든 이것 때문에.. 가장 권한이 많고 강력한 ActiveX가 DllRegisterServer를 통해 등록될 때 저 flashplayertrust에다가 우리 플래시에 대한 정보를 덩달아 등록해 주고, 등록 해제될 때 그 정보를 삭제하도록 함으로써 문제는 일단락되었던 걸로 기억한다. ActiveX는 네이티브 코드인 관계로 파일, 레지스트리, 웹 등에 다 접근이 되고, 심지어 Win32 API를 직접 호출해서 뭐든 다 할 수 있으니.. 사기 유닛이다.

물론 오늘날은 다른 웹 표준과 RIA 기술도 풍부한데 저런 무식한 방법을 쓰는 건 곤란하다.
참고로 플래시에 전설의 flv 동영상이 추가된 게 그 무렵부터일 것이다. 유튜브가 그때 막 태동했으니 말이다. 플래시가 벡터 드로잉 애니메이션뿐만 아니라 일반 비디오 플레이어 분야도 섭렵하기 시작했으며, 덕분에 이제 인터넷으로 동영상 볼 때 ActiveX 설치를 요구하는 사이트는 개념 없다는 소리를 듣기 시작하게 됐다.

2.
안드로이드 어플 만들면서도 비슷한 경험을 했다. 아놔 다른 프로그램에서는 잘 되는 환경설정 변경이 왜 도대체 안 되고, 기껏 되더라도 왜 내가 바꾼 환경설정이 다른 곳에 도무지 적용이 안 되는지.. 함수 호출 결과는 성공인데.. 그 뒤 결과는 그냥 씹히고 있던 것이다.
하루를 삽질하고 났더니 원인은 역시 매니페스트 파일에다가 android.permission.WRITE_SETTINGS , android.permission.CHANGE_CONFIGURATION 요 따위 퍼미션 요청을 안 해 놨기 때문이었다.

3.
그동안 유닉스 계열 OS에 비해 권한이나 보안 같은 관념이 너무 약하던 윈도우도, 비스타부터는 그쪽으로 좀더 엄격해졌다.
잘 알다시피 사용자 계정 컨트롤(UAC)라는 게 추가됐으며, 프로그램을 관리자 권한으로 실행할 때와 그렇지 않을 때에 허용되는 권한의 차이가 매우 커졌다. 가령, 관리자 권한이 아니면 '내 문서' 말고 다른 디렉터리에다가는 파일을 제대로 만들지도 못한다.

그리고 이 프로그램이 요구하는 권한을 명시하는 게 가능해졌다.
아무 권한에서나 실행 가능한지, 무조건 관리자 모드에서 실행돼야 하는지 하는 걸 말이다.
지정은 EXE 내부의 매니페스트 XML에다가 하면 된다. 그 개념은 이미 윈도우 XP에서 시스템 DLL의 로딩 방식을 제어하기 위해 도입된 바 있으므로 새삼스러울 게 없다.
비주얼 C++ 2008부터는 링커 옵션에 이걸 바로 지정해 주는 게 추가됐다. 그 이전 버전에서는 사용자가 직접 xml 파일을 손으로 써서 링크해 주면 된다.

스크린 키보드처럼 장애인 Accessbility용 프로그램은 의외로 높은 보안 수준이 필요하다.
내가 받은 입력에 대한 결과를 시스템 모든 프로그램에다가 끼쳐야(키보드 입력 흉내) 하기 때문에
이런 프로그램은 별도의 인증을 거쳐야 운영체제가 그 정도의 권한을 허락하게 되어 있다.
그런 인증을 거치지 않은 "<날개셋> 입력 패드"는, 사용자가 직접 관리자 권한으로 실행해 주지 않으면,
자기보다 권한 등급이 높은 프로그램에다가는 글자 입력을 전달해 줄 수 없다.

글을 맺는 소감:
삽질해야 하는 게 싫다. -_-;;
지금 유닉스 명령어 익히느라 땀 뻘뻘 흘리는 걸 보면, 옛날에 지금보다 영어도 훨씬 더 못 하던 시절에 도스 명령은 어째 알아서 외웠는지 궁금하다.
지금 이놈의 안드로이드 때문에 삽질하는 걸 보면, 옛날에 윈도우 API는 어째 공부했는지 내가 생각해도 나 자신을 이해할 수 없다.
그때는 삽질을 삽질이라고 여기지 않고 전적으로 재미로 했기 때문에 프로그래밍에 재미를 붙일 수 있었던 것 같다.

Posted by 사무엘

2010/06/22 08:55 2010/06/22 08:55
, , ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/300

C++의 템플릿은 두말할 나위도 없이 매우 강력하고 유용한 개념이다.
C++에다가 제네릭/메타 프로그래밍--프로그램을 만드는 프로그램.. 즉 더욱 추상화된 기법--의 가능성을 무한히 열어 줬기 때문이다.

swap, min, max 같은 것부터 시작해서
옛날에는 문법적으로 매우 불완전하기 짝이 없는 매크로를 쓰고 위험한 typecasting을 해야 했던 것을, 템플릿 덕분에 언어의 정식 문법으로 아주 깔끔하고 type-safe하게 구현할 수 있게 된 게 많다.

그리고 static 배열의 크기(원소 개수)를 되돌리는 매크로인 ARRAYSIZE도 생각해 보자.
C 시절에는 sizeof(x)/sizeof(x[0]) 와 같은 식으로 구현했다. 물론 이 값들은 모두 컴파일 시간 때 이미 결정이 되기 때문에 실제로 나눗셈 연산이 일어나지는 않는다.

하지만 템플릿을 이용하면...
template<typename T, size_t N> char (*__GetArraySize(T (&n)[N]))[N];
#define ARRAYSIZE(x)   sizeof(*__GetArraySize(x))

자 이게 무슨 의미인지 이해가 가시는가?
무슨 타입인지는 모르겠지만 어쨌든 N개짜리 배열의 참조자를 받아서 N개짜리 char형 배열의 포인터를 되돌리는 함수를 템플릿으로 선언한다.
그 후, N의 함수의 리턴값을 역참조한 배열의 크기를 sizeof로 구하면... 당연히 char형 배열의 크기는 본디 배열의 원소 개수와 일치할 수밖에 없게 된다. 함수의 몸체를 정의할 필요조차 없다! 마치 &( ((DATA*)NULL)->member ) 가 에러를 일으키지 않고 멤버 오프셋을 되돌려 주는 것과 같은 이치이다.

템플릿이 아예 배열의 참조자를 받게 함으로써 좋은 점은, 이 매크로에다가 static 배열이 아니라 단순 포인터를 집어넣으면 컴파일 에러가 발생하게 된다는 것이다. 단순 sizeof(A)/sizeof(A[0])보다 타입 safety도 보장되고 훨씬 더 좋다.
(), *, [] 등이 뒤섞인 복잡 난해한 C/C++ type string 읽는 법에 대해서는 이전에 별도로 글로 다룬 바 있다.

template<typename T, size_t N> size_t ARRAYSIZE(T (&n)[N]) { return N; }

물론 위와 같이 코드를 쓰면 더 간단하고 알아보기도 쉽게 동일한 효과를 이룰 수 있지만, 최적화를 하지 않은 디버그 빌드는 불필요한 함수 호출이 계속 일어나는 문제가 있다. 배열의 크기 정도는 컴파일 타임 때 모든 계산이 딱 일어나게 하는 방법을 쓰는 게 더 좋을 것이다.

이렇게 템플릿은 매우 편리한 개념이긴 하나, 한계도 분명 있다.
템플릿은 동일한 패턴을 지닌 여러 다른 코드들을 찍어내는 ‘틀’과 같다. 하지만 이 틀 자체는 한 번만 정의해 놓고 링크 타임 때 여러 오브젝트 파일 사이를 자유자재로 넘나들게 할 수는 없다. 최적화처럼 기술적으로 여러 난관이 있기 때문이다. 템플릿 인자로 들어온 타입이 int일 때, double일 때, 심지어 개당 100바이트가 넘는 구조체일 때 이들에 대한 각종 비교나 대입 연산과 최적화 방식과 코드 생성 방식은 완전히 천차만별이 될 수밖에 없다.

이런 이유로 인해 템플릿의 정의 효과는 오로지 한 소스 코드, 한 translation unit 안에서만 유효하며, 템플릿 클래스나 함수는 모든 소스 코드에 헤더 파일의 형태로 매번 include되어야 한다. 몸체까지(body; definition; implementation) 죄다 말이다. 일반적인 클래스의 함수처럼 선언 따로, 정의 따로일 수가 없다. 사실은 템플릿 코드에 대한 에러 체킹 자체도 템플릿이 인자가 들어와서 어떤 형태로든 실현(realize)이 됐을 때에야 할 수 있다.

그러니 템플릿으로 구축된 각종 함수와 클래스는 소스 코드가 노출될 수밖에 없으며, 그 소스 코드를 고치면 템플릿을 include하는 모든 소스 파일들이 재컴파일되어야 하는 등 프로그래밍 상의 한계가 결코 만만한 수준이 아니다. 하지만 이 한계는 C++ 언어의 컴파일/링크 모델이라든가 기존 컴파일러들의 오브젝트 파일 포맷 내지 컴파일러/링커의 동작 방식이 근본적으로 바뀌지 않는 한 극복되기 쉽지 않을 것이다.

이런 구조적인 불편을 해소하고자 C++ 표준 위원회가 제안한 것은 export 키워드이다.
흔히 import/export하면 윈도우 프로그래밍 세상에서는 DLL 심볼을 내놓거나 가져오는 개념을 떠올리는데, 이 문맥에서는 그런 건 아니다. 한 translation unit에 존재하는 템플릿 함수 구현체를 다른 translation unit이 그 경계를 초월하여 링크 타임 때 가져다 쓸 수 있게 하는 흠좀무한 개념이다. 즉, 템플릿 몸체에 대한 export를 뜻한다.

헤더 파일에다가는

export template<typename T> void Swap(T& a, T& b);

이라고 해 놓고 모처의 cpp 파일 한 군데에다가는

export template<typename T> void Swap(T& a, T& b)
{
    T c(a); a=b, b=c;
}

이런 식으로 써 놓음으로써,
Swap 함수는 export라고 마크가 되어 있으니 자기 translation unit에서만 쓰지 말고, 다른 단위에서도 필요하면 링크 때 가져다 쓸 수 있게 한다는 게 당초 의도였던 모양이다. (인터넷 검색을 해 보니)
템플릿으로 들어간 클래스 멤버 함수에 대해서도 마찬가지이다. export가 없으면 저 함수 body는 마치 static 변수/함수처럼 그 소스 파일 내부에서만 유효하고 다른 소스 파일에서는 링크 에러가 나게 될 것이다.

그러나 이것을 본 컴파일러 개발사들은 '이뭐병, 이딴 걸 무슨 얼어죽을 표준안이라고 내놓냐' 하는 반응이었고..
비주얼 C++, gcc 등 유수의 컴파일러들은 이 키워드의 구현을 포기/거부하고 말았다.
비주얼 C++의 경우 도움말에 Nonstandard Behavior로 자기는 이 키워드를 지원하지 않는다고 당당히 명시까지 되어 있다. 2010은 모르겠고 2008까지도 마찬가지임.
하지만 마이너급 컴파일러 중엔 export를 구현 안 한 놈이 없는 건 아니라고 한다. 흠좀무.

컴파일해 놓은 코드를 짜깁기만 하는 게 링크인데, 저걸 구현하려면 링크에다가 다시 컴파일을 하고 재링크(?) 과정을 집어넣어야 한다. C/C++의 정상적인 빌드 루트와는 정면으로 모순되는 과정을 요구하는 것이다. 그래서 컴파일러 개발사들이 떡실신한 것이다.

어쨌든 이런 이유로 인해서 export는 C++의 흑역사 키워드로 전락해 있다.
옛날에 MS는 링크 과정을 최대한 간단하게 만들려고 COFF 방식 obj 파일과 PE 방식 exe 파일을 채택했다고 하던데, 하지만 요즘은 워낙 translation unit을 넘나드는 링크 타임의 코드 생성과 전역 최적화 같은 기술이 대세가 돼 있다 보니, export 키워드의 의도가 옛날만치 그저 병맛나게 들리지만은 않을 것 같은 생각이 들기도 한다.

마치 유명무실하던 auto가 리모델링되고 서울 지하철 5호선 마곡 역이 13년만에 부활했듯이 export 키워드도 의도 자체는 좋은데.. 언젠가 부활할 날이 올 수 있지 않을까? 하지만 C++의 차세대 표준인 C++0x에서는 export를 아예 빼 버리고 백지화하자는 제안까지 나온 상태이니, 과연 지못미이다.

Posted by 사무엘

2010/06/12 09:27 2010/06/12 09:27
, ,
Response
A trackback , 11 Comments
RSS :
http://moogi.new21.org/tc/rss/response/293

C/C++의 type string은 간단한 건 간단하지만 복잡한 건 한없이 복잡하다. C/C++ 프로그래밍 경력 10년이 넘는 본인조차 아직 그런 쪽에는 능숙하지 않으며, 좀 복잡한 type 선언을 해야 하면 옛날에 짜 놓은 코드를 복사해서 가져온다. -_-

복잡한 게 뭔지를 물으신다면, 이런 것을 말한다. 특정 함수의 포인터, 배열의 포인터를 되돌리는 함수의 포인터, 포인터의 참조자, C++ 멤버 포인터 등등... 생각만 해도 머리가 뱅뱅 돌지 않는지?

C/C++에서 뭔가 명칭을 선언하는 건 아래와 같이 일면 단순하다. 간단한 것, 상식적인 것부터 살펴보자.

type p;

이렇게 써 주면 p라는 명칭은 type이라는 타입으로 선언된다. p는 변수가 될 수도 있고 함수도 될 수도 있고 포인터나 배열 변수가 될 수도 있다. C++은 함수 내부의 아무 위치에서나 변수를 선언할 수 있으나, 함수 안에서 또 함수를 선언할 수는 없다. nested 함수라는 개념이 존재하지 않는 것이다.

type a, b, c;

처럼 콤마를 써서 여러 명칭을 동일 type으로 동시에 선언할 수도 있다.
type에는 int, float 같은 built-in type이 들어갈 수 있고, 사용자가 예전에 정의한 구조체· 공용체나 클래스가 들어갈 수도 있다.

C에서는 구조체· 공용체의 명칭 앞에 struct나 union 키워드를 생략할 수 없으며 생략하려면 typedef를 별도로 만들어야 하는 부조리가 있었으나, C++에서는 그런 한계가 없어졌다. type이 템플릿인 경우, 템플릿을 실제로 만들어 내는 argument도 < >에다 둘러싸서 넣어 줘야 하며, 타입 명칭이 다른 scope에 존재할 경우 :: 연산자도 써 줘야 한다. std::vector<int>처럼.

type 명칭에는 이 변수의 성격을 규정하는 modifier 키워드도 선택사항으로 들어갈 수 있다. 이런 예로는 const, volatile, register 같은 키워드가 있다.

type에 대한 설명은 여기까지로 하고, 그럼 p(명칭)에 대해 알아보자.
명칭은 한 번에 여러 개를 동시에 선언할 수 있고, 또 원한다면 p=1처럼 =을 써서 선언과 동시에 초기화도 가능하다. C++의 경우, 아예 ()을 써서 생성자 함수 호출을 바로 시키는 것도 가능하며 built-in type에 대해서도 생성자 함수 호출하듯 값을 초기화할 수 있다. 즉,

int *a=NULL, b=7; /* C style */
int *a(NULL), b(7); //C++ style

C에서는 위의 문장만 허용되는 반면 C++은 아래의 문장도 허용된다는 뜻이다.

자, 그럼 이제 진짜 복잡한 부분으로 들어가 보겠다.
C/C++의 문법이 판타지 같은 이유는, 분명 명칭의 type과 관련된 modifier들이 type 부분에 확실하게 구분되어 있는 게 아니라 name 부분으로 개별 적용되는 것도 있기 때문이다. 그렇기 때문에 C/C++은

int *a, b;

라고 선언하면 *라는 modifier는 a에만 적용되어 a만 int형에 대한 포인터가 되고 b는 일반 int가 되는 것이다. 그런데 D라는 언어는 그렇지 않아서 위와 같이 선언하면 a와 b의 타입이 모두 int*가 된다.

이런 식으로 개별적으로 적용되는 modifier로는 다음과 같은 것이 있다. 이런 것들이 막 섞이면 사람 머리 터지게 만든다. ^^;;

*p : p가 포인터임을 뜻한다. 변수의 왼쪽에 붙으며, 오른쪽에서 왼쪽으로 해석한다. *가 여러 개 붙으면 2중, 3중 포인터가 될 수 있다. (pointer to)
&p : C++에서 추가된 문법이며, p가 참조자임을 뜻한다. 쓰임이 포인터보다 훨씬 제한적이기 때문에 다중으로 붙을 수 없다. 용법은 *와 동일. (reference to)

int *&p;

라고 하면 우에서 좌로 & → * 순으로 해석되어 p는 포인터의 참조자가 된다(a reference to a pointer to integer). 반대로 참조자를 가리키는 포인터라든가 참조자를 또 가리키는 참조자라는 개념은 C++에 없기 때문에, &*나 && 같은 문법은 틀렸다. 포인터의 문법을 간소화하려고 만든 게 참조자인데 이는 상식적으로 당연한 얘기. 하지만 이중 포인터의 참조자인 **&은 있을 수 있다. 이 정도면 *와 &의 관계는 충분히 설명됐을 것이다.
다음,

p() : 어떤 명칭 바로 오른쪽에 ()가 붙었다면 이는 그 명칭이 함수임을 뜻한다. 쉽다.

p[n] : 그 명칭이 배열임을 뜻한다. 첨자가 들어있어야 하는 게 원칙이지만, 함수 argument라든가 일부 1차원적인 문맥에서는 첨자가 생략되어서 포인터와 별 차이 없는 용법이 되기도 한다. 영어로는 array of에 해당. []가 오른쪽 끝에 계속 붙으면 다차원 배열을 만들 수 있다.

그렇다면 명칭의 왼쪽에 포인터가, 오른쪽에 ()나 []가 다 붙어 있으면 어떻게 해석해야 할까?
일단 오른쪽 것부터 해석한다. 그 후 오른쪽 끝에 도달하면 왼쪽으로 간다. 그래서

int *a[10];

은 []이 먼저 해석되어 array of / pointer to / int가 되고, 따라서 ‘int *가 10개 있는 배열’이 된다.
이 순서를 바꾸기 위해서 또 괄호가 사용된다. 함수를 뜻하는 ()와는 쓰이는 문맥이 다르며, 의미도 다르다. 이걸 아는 게 중요하다.

int (*a)[10];

은 *이 먼저 해석된 후 오른쪽의 배열로 넘어가서 pointer to array[10] of int가 되고, 따라서 배열의 포인터가 된다. 사실, C/C++의 type string은 일종의 영어 어순을 따르고 있는 셈이다. 이걸 알면 쉽다. 꼭 기억하자.

int func(int x);
int (*funcptr)(int x) = func;

명칭 다음에 곧바로 ()가 나오면 함수 선언이 되나, 이름이 괄호로 둘러싸여서 *가 먼저 해석되므로 funcptr은 pointer to function, 즉 함수의 포인터가 되고, 자신과 prototype이 완전히 같은 func라는 함수를 가리킬 수 있게 되는 것이다.

닫는 괄호를 만나면 아직 해석되지 않았던 왼쪽으로 이동하고, 그러다가 여는 괄호를 만나면 다시 닫는 괄호 바깥의 오른쪽으로 가면서 완전히 바깥에 도달할 때까지 이 과정을 반복하면 된다.
따라서 명칭 뒤에 붙는 (), *, [] 같은 게 아무리 복잡하더라도, 명칭의 좌우에 가장 가까이 붙어 있는 놈이 뭔지만 보면, 얘가 포인터인지 함수인지 배열인지 정도는 바로 알 수 있다.

double ( *varr( double (*)[3] ) )[3];

위는 배열의 포인터와 함수의 포인터가 모두 동원된 예이다. 슬슬 머리가 아파질 것이다. varr의 좌우로 *와 ()가 있는데, 이때 오른쪽으로 먼저 간다. 그래서 varr은 함수가 되고 왼쪽의 *는 함수의 리턴값과 관계가 있게 된다. 그렇다. 이놈은 double 형 배열의 포인터를 인자로 받는 함수인데, 이 함수의 리턴값 역시 double 형 배열의 포인터라는 뜻이다.

double (* (*pfnFunc)( double (*)[3] ) )[3] = varr;

그리고 저 varr을 가리키는 함수의 포인터는.. varr만 (*pfnFunc)라고 또 감싸 주면 만들 수 있다. ^^;; 포인터를 되돌리는 함수의 포인터인 것이다.

int *(*(*fp1)(int))[10];

굉장히 변태-_-스러운 예제인데, 별표를 맨 왼쪽에 있는 것부터 [1], [2], [3]으로 번호를 매기자면,
fp1은 int 형을 인자로 받고, 원소 개수가 10인 int 포인터[1]의 배열에 대한 포인터[2]를 되돌리는 함수의 포인터[3]이다.

pointer to *
function (int)
returning pointer to *
array [10] of int*

이제 진짜 궁극의 변태 같은 예를 들면,

char *(*(**foo[2][8])())[10];

array [2][8] of
pointer to **
function ()
returning pointer to *
array [10] of char*

다시 말해 char*가 10개 들어있는 배열의 포인터를 되돌리는 함수의 2중 포인터를 담고 있는 2차원 배열이라는 소리이다. ^^;;

그럼 마지막으로, 또 하나의 기괴한 C++ 문법을 소개하면서 글을 맺겠다. 그것은 바로 멤버 포인터라는 특이한 포인터이다.

class CMyObject {
public:
 int x,y,z;
 void foo() {}
 void bar() {}
};

CMyObject obj;
int CMyObject::*pVal = &CMyObject::x;
void (CMyObject::*pFunc)() = &CMyObject::foo;

obj.*pVal = 10;
(obj.*pFunc)();

위의 코드에서 볼 수 있듯 pVal은 int형인 x, y, z중 한 멤버 변수를 가리킬 수 있고, pFunc는 자신과 prototype이 같은 foo()와 bar() 중 하나를 가리킬 수 있다.
일반적인 C++ 클래스의 non-static 멤버들은 멤버 포인터로 하여금 자신을 가리키게 할 때 "&클래스::멤버"와 같은 식으로 주소를 얻을 수 있다. 이때 어느 토큰 하나도 생략할 수 없다. 심지어 자기 클래스 멤버 함수 내부에서라도 자기 클래스 이름을 반드시 명시해야 한다.

멤버 포인터를 나타내는 ::*은 ::와 *가 합쳐진 것이다. 그러나 멤버 포인터를 실제로 사용하는 연산자인 .* 또는 ->* 는 완전히 한 토큰으로, 사이를 띄울 수 없다. 또한 멤버 포인터 함수를 선언하고 호출할 때는 반드시 괄호가 필요하다. 이걸 하지 않으면 오른쪽의 함수 호출 ()가 먼저 해석되어서 개체와 멤버 포인터가 먼저 연결되지 못하기 때문이라 한다.
마치 파스칼 언어에서 우선순위 처리의 특이점 때문에 (a=1) and (b>5)처럼 각 항을 괄호로 싸 줘야 하는 것과 비슷한 맥락이라 하겠다.

그나저나 C++은 :: . -> 이렇게 세 연산자가 모두 따로 존재하는 언어라는 게 특이하다. 자바나 C#은 . 하나가 이들 기능을 모두 수행한다.

Posted by 사무엘

2010/05/29 15:20 2010/05/29 15:20
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/279

파일 대화상자

1. 파일 대화상자라는 개념

윈도우 운영체제.. 사실 이뿐만이 아니라 플랫폼을 불문하고 현대적인 GUI 프레임워크들은
불러오기/저장하기(파일 선택) 기능을 공통 기능으로 제공한다.

어떤 형태로든 사용자가 작업하는 문서(데이터)를 파일로 읽고 쓰는 소프트웨어치고 이 기능을 안 쓰는 녀석은 없으므로, 이건 가히 필수 공통 기능이라 하기에 손색이 없다. 도스 시절에는 불러오기/저장하기 UI도 프로그래머가 제각각으로 직접 구현해야 했으니 얼마나 번거로웠는지 모른다. 이거 하나만 운영체제가 고수준 API를 제공해 줌으로써 응용 프로그램이 직접 FindFirstFile, FindNextFile 같은 파일 탐색 함수를 호출해야 할 일은 상당수 없어졌다.

물론 이것 말고도 색깔을 찍는 대화상자, 인쇄 대화상자 같은 것도 공통 대화상자에 속하며 운영체제가 제공해 주는 게 있다. 그리고 필요한 경우, 대화상자의 일부 요소나 동작 방식을 프로그래머가 자기 입맛에 맞게 customize하는 테크닉도 응당 제공된다.

2. 윈도우 운영체제의 파일 대화상자의 역사

윈도우 3.x의 파일 대화상자는 파일 목록과 디렉터리 목록이 리스트 박스의 형태로 좌우로 나란히 나오고, 그 아래에는 저장하거나 열려는 파일의 format 그리고 드라이브 목록이 콤보 박스의 형태로 나란히 나와 있었다. 드라이브-디렉터리-파일이라는 클래식한 형태에 충실한 디자인이라 하겠다. 가정에서는 네트웍이 아직 많이 생소하던 시절이었기 때문에, 네트웍 드라이브에 접근하기 위해서는 별도의 버튼을 눌러야 했다.

사용자 삽입 이미지

그러던 것이 윈도우 95/NT4에 와서는 크게 바뀌었다. 사실 윈도우 95부터는 쉘의 디자인의 근간이 싹 바뀌어 오늘날까지 이어져 오고 있다. 드라이브를 제공하는 장치간의 구분이 완화되었고, 가장 최상위 계층인 바탕 화면 아래로 내 컴퓨터, 휴지통 등등이 따르는 컨셉이 이때 도입된 것이다. 파일 리스트는 구닥다리 리스트 박스 대신, 소위 공용 컨트롤이라고 불리는 리스트뷰 컨트롤 기반으로 바뀌고 디렉터리와 드라이브는 폴더라는 개념으로 바뀌었다. 그리고 탐색기가 제공하는 쉘 기능(파일 복사, 붙여넣기, 삭제, 개명, 각종 우클릭 메뉴)은 파일 대화상자 내부에서도 그대로 쓸 수 있게 된 것 역시 큰 변화이다.

사용자 삽입 이미지

윈도우 98부터는 파일 대화상자의 크기 조절이 가능해졌다. 상당히 바람직한 변화이다.

윈도우 2000/ME부터는 파일 리스트 왼쪽에 바탕 화면, 내 문서 등 주요 폴더로 곧바로 이동하는 아이콘이 추가되었다(일명 favorite bar). 그리고 아마 이 무렵부터, 파일이나 디렉터리 이름의 처음 몇 자를 입력하면 자동 완성 후보가 뜨는 아주 편리한 기능이 생겼다.

이 구조가 윈도우 XP까지 이어지다가 비스타/7부터는 또 파일 대화상자가 싹 바뀌었다. 변화의 양상에 대해 한 마디로 요약하자면, 탐색기와의 경계가 더욱 모호해지고 좀더 "웹처럼"(webby) 바뀌었다. 웹 페이지 탐색하는 기분으로 내 컴퓨터의 폴더를 탐색하게 되었는데, 이는 IE4부터 MS에서 부르짖은 캐치프레이즈이기도 하다. 물론 탐색기와 IE의 완전 통합은 보안상의 이유로 인해 IE7부터는 좀 지양되었지만 말이다.

얼마나 바뀌었냐 하면, 파일 대화상자에도 웹브라우저처럼 "뒤로, 앞으로" 버튼이 생겼고, 검색란이 생겼다. 자주 쓰는 폴더 목록은 마치 인터넷 즐겨찾기처럼 뜨기 시작한 것이다. 그런 디자인이야 디자이너의 취향 나름이라 하지만, 기본 크기가 좀더 큼직해지고 마우스로 두세 단계의 폴더를 바로 건너뛰어 상위 단계로 갈 수 있어서 디렉터리 변경이 좀더 편해진 게 매우 좋다.

3. 과거 호환성

이렇게 운영체제가 버전업되면서 파일 대화상자의 디자인은 몇 차례 변화를 겪었다.
응용 프로그램이 아무 조작을 안 가하고 기계적으로 MSDN에 명시된 input과 output만 FM대로 잘 활용한다면, 파일 대화상자의 디테일은 응용 프로그램의 본질적인 동작에 전혀 영향을 끼치지 않는다. (당연한 말이지만)

그렇기 때문에 윈도우 비스타 이전에 개발된 프로그램이라 할지라도, 파일 대화상자 API만 곱게 쓴다면 비스타에서 실행했을 때 최신 디자인의 파일 대화상자가 뜬다. 이게 무슨 공용 컨트롤 매니페스트도 아니고, 최신 대화상자를 쓰기 위해서 응용 프로그램이 뭔가 조치를 취해야 할 필요는 없다.

그럼에도 불구하고 운영체제들은 옛날 버전 파일 대화상자들의 디자인도 갖고는 있다.
응용 프로그램이 파일 대화상자에다가 자신만의 컨트롤을 추가하고, 동작 방식을 제어하고 특정 컨트롤을 서브클래싱까지 하는 경우, 그 파일 대화상자의 디자인이 최신 운영체제에서 바뀌어 버린다면 동작 방식에 심각한 문제가 생길 수 있기 때문이다.

이미지 프로그램의 경우 열기 대화상자에다가는 그림 preview를 추가하기도 하고, 저장 대화상자에다가는 JPG로 저장할 경우 화질을 지정하는 추가 옵션을 대화상자에다 덧붙이곤 했다.
압축 유틸리티나 파일 변환 프로그램은 아예 파일 열기 대화상자에다가 자기네 동작 옵션을 한 보따리 가득 집어넣어 그걸로 프로그램 메인 화면을 만들기도 했다는 걸 알 필요가 있다.

최신 디자인을 적용할 수 없는 경우, 운영체제는 여전히 윈도우 2000이나 심지어 98(favorite bar까지 없어진) 스타일로 파일 대화상자를 출력해 준다.
특히 MFC를 써서 프로그램을 개발하는 경우 이 점을 매우 신경써야 한다. MFC는 프로그래머가 원하는 타이밍에 손쉽게 이벤트를 날려 주기 위해서.. 쉽게 말해 개발자의 편의를 위해서 윈도우에다가 온갖 보이지 않는 훅킹과 서브클래싱을 남발하는데, 이런 이유로 인해서 구형 버전의 비주얼 C++로 CFileDialog 클래스를 쓰면, 아무리 내가 customize를 하는 게 없어도 최신 운영체제에서 파일 대화상자가 최신 디자인으로 나오지 않는다!

까놓고 말해 본인이 개발한 <날개셋> 편집기의 열기 대화상자와, <날개셋> 타자연습의 연습글 추가 대화상자를 윈도우 비스타/7에서 살펴보기 바란다. 차이가 느껴질 것이다.
이런 이유로 인해, 최신 운영체제의 GUI 혜택을 입으려면 새로운 기능을 쓰는 게 없어도 개발툴도 최신으로 써야 하는 경우가 있다. 싫으면 MFC 클래스 쓰지 말고, 불편하더라도 GetOpenFileName처럼 윈도우 API를 직접 호출하든가 말이다. ^^

4. 파일 대화상자의 중복 개발

그런데 MS 오피스 제품들은 전통적으로 운영체제의 표준 API를 쓰지 않고, 무려 자기네가 따로 자체 개발한 파일 대화상자를 사용해 왔다. 가령, 운영체제의 표준 파일 대화상자는 윈도우 2000/ME대에 가서야 favorite bar가 추가된 반면, MS 오피스의 대화상자는 꽤 일찍부터.. 최소한 오피스 97 시절부터 그런 걸 갖추고 있었다.

이런 식으로 MS 내부에서 운영체제 GUI가 오피스 GUI를 뒤쫓아가는 양상은 일상적인 모습 같다. 하다못해 윈도우 3.1 시절에도 오피스가 자체 구현했던 도구모음줄(toolbar), 상황선(status bar), 은빛 3D 효과 대화상자, 프로퍼티 시트와 위저드 같은 게 95로 넘어가면서 운영체제 표준 GUI로 나중에야 편입되었다. 그러던 관행이 세월이 흘러 윈도우 7에서는 리본 인터페이스가 워드패드와 그림판에까지 적용되어 있다.

오피스 제품 중에서도 무려 1980년대부터 개발되어 온 워드와 엑셀은, 겉모습은 별 차이 안 나지만 사실 일반 대화상자들마저도 윈도우 운영체제의 표준 대화상자가 아니며 마치 아래아한글처럼 자체 구현 대화상자이다! 걔네들이 처음부터 아주 크로스 플랫폼으로 개발되어서 그런 건 아니고, 애초에 개발 컨셉이, 빈약한 운영체제의 기본 컴포넌트에 의존하지 않고 자기네 컴포넌트로 UI를 자체 구상하겠다는 방향이었던 것 같다. 그 부자 기업이 하고 싶은 걸 뭘 못 하겠는가?

90년대 이후부터 개발된 파워포인트나 액세스는 그런 수준까지는 아니지만 열기/저장 대화상자라든가 About 대화상자만은 여전히 MS 오피스 공용 라이브러리의 것을 사용해 왔다.
개발툴인 비주얼 스튜디오도 닷넷급부터는 MS 오피스의 GUI 엔진을 이어받고 있는 중이다. 순수 MFC만으로 MS 오피스 짝퉁 GUI을 구현했던 비주얼 C++ 5와 6의 계보는 흑역사가 된 지 오래.

그런데, 그러던 정책이 이제는 바뀌었다.
윈도우 비스타와 동일한 timeline에 개발된 제품인 비주얼 스튜디오 2008과 MS 오피스 2007부터는
놀랍게도 운영체제의 표준 파일 대화상자를 사용하며, 앞으로도 계속 그런 구도가 유지될 것이다!
비주얼 스튜디오 2005와 MS 오피스 2003은 그렇지 않다는 소리.

다만, 이건 비스타 이상에서 실행됐을 때에 한해서이다. 윈도우 XP에서 돌아가는 비주얼 스튜디오 2008이나 MS 오피스 2007은 여전히 자체 대화상자를 사용한다. -_-;; 이것도 무지 신기한 점임.

비스타부터는 이제 운영체제의 파일 대화상자도 기능이 매우 향상되었고 customize하는 수단도 충분히 깔끔해졌기 때문에 굳이 운영체제 따로, 오피스 따로 노선을 갈 필요를 느끼지 않게 되어 그렇게 바뀐 것 같다.
비스타에서는 비주얼 스튜디오 2005의 옛날식 파일 대화상자가 오히려 더 촌스럽게 느껴진다. ^^;;

Posted by 사무엘

2010/05/13 07:34 2010/05/13 07:34
, ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/267

« Previous : 1 : ... 23 : 24 : 25 : 26 : 27 : 28 : 29 : 30 : 31 : 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:
2676562
Today:
1130
Yesterday:
2124