네트워크나 파일 같은 외부 입출력을 열고 닫는 작업은 실패할 가능성이 워낙 높기 때문에 프로그램 작성에서 에러 처리가 거의 무조건적인 필수이다.

하지만 메모리 할당은 그렇지 않다. 수~수십 MB 정도 공간을 요청하거나 재할당하는 것쯤은 요즘 컴에서는 실패할 가능성이 0에 수렴한다. malloc의 결과값이 NULL인지 일일이 체크하는 코드는 요즘 거의 찾을 수 없다. C++의 new 연산자도 예전에는 실패 시 NULL 리턴이었지만 지금은 예외를 던지는 형태로 디자인이 바뀐 지 오래다. 그거 일일이 NULL 체크를 하는 건 너무 남사스럽고 성가시고 민망하기 때문이다.

메모리 할당을 위해 어지간해서는 C++의 깔끔한 new와 delete를 쓴다지만, C++ 연산자에는 메모리의 재할당 기능이 없기 때문에 이를 위해서는 여전히 malloc/realloc/free 쌍이 쓰인다. 그리고 좀 원시적인 테크닉이긴 하지만 가변 길이 구조체의 메모리 할당을 위해서도 크기 지정이 자유로운 C 스타일의 메모리 함수가 필요하다. 아니면 operator new 함수를 직접 호출하든가 말이다.

그런데 realloc은 실행이 실패했을 때의 상태가 꽤 복잡하다. 보통 ptr=realloc(ptr, newsize) 같은 형태로 활용을 하는데, 재할당이 실패했다고 생각해 보자. 이때는 realloc은 재할당을 할 수 없어서 NULL을 되돌린다. 이는 분명 비정상적인 오류 상황이고 프로그램이 그에 대한 별도의 대비를 하긴 해야 하지만, 그렇더라도 ptr이 원래 가리키던 메모리는 아무 이상이 없다. 그런데 ptr에다가 무턱대고 마치 malloc의 리턴값처럼 NULL을 대입해 버리면 ptr은 소실되고 메모리 leak이 발생하게 된다.

그러니 실행이 실패하더라도 메모리 leak은 발생하지 않게 하려면

ptr_tmp=realloc(ptr, newsize);
if(ptr_tmp) ptr=ptr_tmp; //성공
else { } //실패

번거롭지만 이렇게 임시 포인터 변수를 하나 추가로 둬서 실행이 성공했을 때에만 포인터의 실제값을 반영하게 해야 안전하다. 본인은 이 점을 한 번도 생각을 안 하고 있었는데 비주얼 C++ 2012에서부터 추가된 코드 정적 분석기가 지적을 해 주는 걸 보고서야 “아하!”하고 무릎을 쳤다.

이런 것을 생각하면 realloc의 실패야말로 리턴값보다는 예외 처리로 알려 주는 게 더 편리하겠다는 생각이 든다.
절차형으로 실행되는 컴퓨터 프로그램에서는, 당연한 말이지만 n+1단계 명령은 그 앞의 1~n단계의 모든 명령들이 성공적으로 차곡차곡 잘 실행됐다는 전제하에서만 실행 가능하다. 중간에 뭔가 탈이 났다면 더 진행을 할 수 없으며 어디까지 앞뒤로 되돌아가면 되는지를 컴퓨터가 스스로 판단할 수 없다. 컴퓨터에게는 인간 같은 유도리가 존재하지 않는다. 그렇기 때문에 그런 정보가 없다면 그 프로그램은 전체가 강제 종료되는 것밖에 답이 없다.

자동차 운전을 하는 사람이라면 단순히 핸들과 페달과 변속기를 조작하는 것 말고도 사고가 났을 때의 대처 요령과 보험사 연락처 같은 것도 숙지하고 있어야 하듯, 컴퓨터 프로그램도 마찬가지이다. 중간에 탈이 나도 최대한 부드럽게 수습하고, 피치 못할 상황에서  프로그램이 죽더라도 최소한 지금 작성 중인 문서를 저장이라도 한 뒤에 죽는 그 로직 자체도 프로그래밍이 돼 있어야 한다. 그것이 바로 예외 처리라는 분기 제어에 해당한다.
아울러, 숙달된 프로그래머라면 예외 처리를 구현하는 데 드는 추가 오버헤드와 비용을 숙지해 둘 필요도 있다. 수많은 객체들의 생명 주기를 관리하면서 여러 함수들을 한꺼번에 이탈하는 것도 그냥 될 리는 없으니 말이다.

C/C++은 애초에 운영체제/하드웨어 차원에서의 crash는 있어도 언어 차원에서의 예외 처리라는 게 아예 존재하지 않던 언어이다 보니 이쪽의 지원이 다른 언어들보다 상대적으로 미비하다. C++에 try/catch 키워드는 한참 뒤에 등장했으며 언어 자체는 이 예외 구문을 전혀 사용하지 않는다. 이걸 사용하는 건 라이브러리 계층에서이다. 그리고 예외 처리용 객체를 날려 줄 때조차도 new로 메모리를 할당했다면 해제를 수동으로 해 줘야 하니 불편한 점이 아닐 수 없다.

다시 본론인 realloc 얘기로 돌아온다.
저런 예외 처리도 오버헤드가 크니 싫고 리턴값만으로 모든 책임을 회피하고 싶다면, realloc 함수의 프로토타입을 차라리 이렇게 설계했으면 더 편했을지도 모른다.

bool realloc(void **pptr, size_t newsize);

void **라니 참 COM스러워 보이지만(CoCreateInstance, IUnknown::QueryInterface ㅋㅋ), C++이라면 템플릿 함수로 이걸 감싸서 지저분함을 한결 예방할 수 있을 것이다.

if(realloc((void **)&ptr, newsize)) { /* 성공 */ }
else { /* 실패 */ }
free(ptr);

내가 무엇을 의도하는지는 딱 보면 알 수 있을 것이다. 기존 메모리를 가리키고 있는 포인터의 주소를 받아서, 재할당이 성공하면 그 포인터가 가리키는 값을 그대로 바꿔 버리고 true를 되돌리는 것이다. 어차피 지금 realloc 함수는 ptr=realloc(ptr, newsize)라고 ptr이 함수 인자(input) 겸 리턴값(output) 형태로 동시에 쓰이고 있으며, 재할당이 성공했다면 예전 주소는 보관하고 있을 필요가 전혀 없으니 말이다.

실패했다면 ptr은 *ptr이든 **ptr이든 아무 변화가 없고 리턴값만 false가 된다. free(ptr)을 해 주는 한 어떤 경우든 메모리 leak 걱정은 안 해도 된다. realloc 함수가 이렇게 만들어지는 게 더 낫지 않았나 싶은 생각이 든다.
뭐, realloc이 결코 실패하지 않는다고 가정하고 프로그램이 막무가내로 동작한다면, 차라리 NULL 포인터 일대를 액세스하다가 확실하게 죽는 게 기존 메모리를 범위를 초과하여 건드리다가 죽는 것보다는 더 안전할지도 모르겠지만 말이다.

끝으로 하나 더. fopen에서 접근 모드와(read/write 등) 데이터 처리 모드(바이너리/텍스트) 인자는 들어올 수 있는 조합이 뻔하고 상수 명칭 조합으로 처리해도 하등 이상할 게 없을 텐데, 왜 하필 더 파싱도 어렵게 문자열을 쓰고 있는지도 이유를 모르겠다. 딱히 확장의 여지가 있어 보이지도 않는데 굳이 _open 같은 저수준 함수와 형태를 달리할 이유가 없다. 이런 것들이 C 라이브러리에 대해서 궁금한 점이다.

Posted by 사무엘

2015/04/14 08:25 2015/04/14 08:25
,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1083

이건 예전에 썼던 disabled 윈도우 관련 글에서 추가로 다뤘어야 했는데 그때 빠뜨렸던 내용이다.
그 글에서 설명했듯, Windows는 '고전 테마'에서 메뉴, 버튼, static 컨트롤이 disable된 형태는 그냥 회색으로만 표시하는 게 아니라 흰색 윤곽 위에다가 회색 윤곽을 덧씌워서 일종의 '엠보싱' 효과를 줘서 표시한다.

사용자 삽입 이미지

이것들은 은색(밝은 회색, 일명 3D 배경색)이 배경이라는 공통점이 있다. 시스템 컬러 번호는 COLOR_3DFACE. 사용자가 딱히 변경을 할 수 없는 고정된 문자들이다.

그 반면, 리스트 박스나 에디트 컨트롤처럼 흰 배경(COLOR_WINDOW)에 표시되는 문자들은 사용자의 선택이나 입력에 의해 내용이 바뀔 수가 있다. 이런 것은 disable됐을 때 배경이 회색으로 변하고 문자는 딱히 엠보싱 처리가 되지 않는다. 각각의 경우에 비주얼이 나름 원칙이 있게 설계된 셈이다.

그런데 먼 옛날에 Windows 9x 시절에 안전 모드 부팅을 해 본 분들은 뭔가 이상한 낌새를 느끼지 않으셨나 모르겠다.
안전 모드에서는 일체의 외부 디바이스 드라이버를 불러들이지 않는 관계로 그래픽조차도 완전 미개한 VGA 640*480 16색으로로 돌아가 버린다.

회색과 파랑의 기본색 팔레트가 다른 톤으로 바뀌는데, 이건 그렇다 치더라도
안전 모드에서는 disable UI가 엠보싱이 아니라 그냥 짙은 회색 단색으로 간소화되어 출력된다. 그래서 느낌이 더욱 달라진다.

사용자 삽입 이미지

엠보싱을 표현하는 데는 흰색과 회색 단 두 색만 있으면 된다. 색깔 표현의 한계 때문에 엠보싱을 포기한 건 결코 아니다.
결정적으로 NT 커널 기반인 Windows 2000은 똑같이 VGA 16색인 안전 모드에서도 동일하게 엠보싱 처리를 해 준다. 어찌 된 일일까?

사용자 삽입 이미지

the old new thing 블로그에 관련 설명이 있다.
시스템 정보를 얻는 API 함수 중 하나인 GetSystemMetrics를 보면 SM_SLOWMACHINE이라는 아이템이 있는데,
얘의 리턴값이 true일 정도로 열악한 환경에서는 운영체제 셸은 disable UI에 엠보싱을 포기하고 그냥 회색 단색을 출력한다고 한다.

이 플래그는 컴이 486이 안 되고 램이 6MB도 안 되는.. 그야말로 윈도 3.1만 간신히 돌리던 극도의 똥컴에서나 켜지는 정도였다. 윈도 95가 당시 사용자의 컴퓨터 환경을 감안하여 명목상으로는 386+램 4MB 이상을 기준으로 설계되었다는 걸 생각해 보자. 물론 한글판은 한글 입출력 오버헤드 때문에 그런 사양에서는 어림도 없으며, 최하 486에 램 8MB 이상은 기본으로 갖춰져야 했다.

그리고 컴퓨터가 아니면 그래픽 카드가 완전 구릴 때에도 이 플래그가 설정되었다.
이를테면 마우스 포인터 깜빡임 보정조차 안 될 정도로 안습일 때 말이다. 기본 VGA는 하드웨어 가속이고 뭐고 아무것도 없는 느려터진 모드이다. 당장 시스템 종료를 위해 Alt+F4를 눌러 보면, 화면 배경 전체에 검은 도트가 반씩 씌워지는 것조차도 단번에 안 되어 점이 내려오는 게 보일 지경이다.

안전 모드에서 disable UI가 엠보싱 없이 출력되었던 것은 바로 그래픽 모드 때문이었다.
엠보싱은 지금 컴퓨터의 관점에서야 그야말로 껌값인 처리이지만, 나름 더블 버퍼링이라는 오버헤드가 필요한 연산이었으니 말이다.

이런 9x와는 달리, NT 계열은 1993년에 출시된 첫 버전 3.1부터가, 그것도 한글· 한자 오버헤드 따위도 없는 영문 원판이 램이 최하 12MB 이상 필요한 왕창 무거운 물건이었다. 범용성과 안정성, 이식성을 위해서 컴의 성능을 쫙쫙 빼다 쓰는 형태로 설계되었기 때문이다. 기본 문자 집합부터가 1바이트가 아닌 2바이트 크기였으니 그것도 메모리를 추가로 잡아먹었을 테고. (그러니 그 시절에 NT를 돌릴 수 있는 컴을 가진 사람이 도대체 얼마나 됐겠는가.)

블로그 글에 따르면, NT는 그야말로 “All machines are fast.”라고 가정하고 태생적으로 SM_SLOWMACHINE 플래그를 사용하지 않는다고 한다. 즉, 어떤 컴퓨터에서나 언제나 false를 되돌린다.
그러니 Windows 2000에서는 VGA 16색 안전 모드에서도 UI에 엠보싱이 적용되는 이유가 논리적으로 바로 설명이 된다.

단, 신기한 것은 2000은 VGA 16색 안전 모드에서도 그래픽이 그렇게 느리게 느껴지지 않는다는 점이다.
게다가 그 모드에서도 그래픽이 마구 바뀌는 곳에 마우스 포인터를 가져갔을 때 포인터가 깜빡거리지 않는다! 하드웨어 제어를 어떻게 했는지 굉장히 궁금해지는 대목이다. 뭔가 굉장히 탄탄하고 안정적이라는 느낌이 든다.

Windows XP부터는 더 나아가 이제 안전 모드에서도 VBE인지 뭔지 슈퍼 VGA 규격을 사용한다. 비록 하드웨어 가속이 없을지언정 일단 트루컬러는 무조건 보장된다. 그래서 초라한 16색, 256색 따위는 정말로 볼 일이 없어졌으니 참 격세지감이다. 그냥 16비트 컬러냐 32비트 컬러냐의 양자 선택만이 있을 뿐이다.

그리고 고전 테마 말고 새로운 테마에서는 disabled UI에 엠보싱 자체를 하지 않는다. 오히려 SM_SLOWMACHINE 스타일과 같은 맥락인 회색 단색으로 회귀했다. 고전 테마가 아예 없어진 Windows 8부터는 엠보싱은 아련한 과거 추억이 됐다.

그래픽 모드가 아예 단색일 때는 diabled UI는 글자에다가 배경색 도트를 반반씩 뿌려서 흐리게 했었는데 그게 16컬러 시절에 엠보싱으로 바뀌었고, 이것이 궁극적으로는 알파 채널로 변모하는 듯하다. 사실 엠보싱은 트루컬러+알파채널+ClearType에 친화적인 방식이 아니긴 하니까 말이다. (맑은 고딕을 엠보싱해서 흐리게 출력하면 보기가 대략 좋지 않다)

참고로 MS Office 제품 중에 Word와 Excel은 운영체제의 대화상자 API가 아니라 자체 개발한 대화상자와 GUI 라이브러리를 사용한다. 얘들은 고전 테마 기준으로 push 버튼은 엠보싱으로 출력하지만, 라디오나 체크 박스는 단순 회색으로 disabled 상태를 출력한다. 즉, 비주얼이 짬뽕이며, 운영체제 GUI와 외형이 완전히 같지는 않다.

사용자 삽입 이미지

끝으로..
Windows에 사용자의 컴퓨터 성능을 체크하는 듯한 기능 몇 군데를 좀 살펴보고 글을 맺도록 하겠다.
하나는 비스타와 7 시절에 있던 그 유명한 Windows 체험지수인데, 이건 8부터는 없어졌다.
다른 하나는 '내 컴퓨터' 속성의 "고급 - 성능 옵션"에서 각종 시각 효과를 설정하는 곳이다.

이건 Windows XP에서 처음 도입된 걸로 기억하는데, 윈2000이나 돌릴 법한 좀 간당간당한 컴에서 "최적 성능으로 조정"을 켜면 그림자 효과나 창의 애니메이션 등 몇몇 효과가 알아서 제외된다.
하지만 이것은 SM_SLOWMACHINE 플래그와는 별개로 구현된 기능이라고 생각하면 되겠다. 또한, 요즘 컴퓨터에서는 그런 거 성능이 문제되는 경우는 전혀 없다고 봐도 무관하고.

아무튼 흥미로운 사실을 하나 알게 됐다.

Posted by 사무엘

2015/04/11 08:30 2015/04/11 08:30
,
Response
No Trackback , 3 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1082

본인이 인터넷에서 굉장히 고맙게, 유용하게 잘 열람하는 정보 중 하나는 지도이다.
참 대단하지 않은가? 항공 사진, 길거리 사진, 길 찾기, 실시간 대중교통 연계와 도로 상황 안내 등... 정말 혀를 내두르는 수준이다. 이젠 도대체 얼마나 더 똑똑해질 거리가 남아 있는 걸까?

아울러, 지도의 일종인 차량용 내비게이션 소프트웨어도 도대체 어떤 천재가 만들었나 싶은 생각이 든다. 도로 상황을 감안해서 길을 찾는 건 당연한 소리이고, 그걸로도 모자라서 길 가는 중에 실시간으로 “해당 경로에 사고가 발생했습니다. 우회 경로를 재탐색할까요?”까지도 튀어나온다.

2013년엔 구글 회장이 한번 방북을 하고 났더니 구글어스가 평양을 중심으로 북한의 세부 지리 정보(단순 항공 사진은 예전부터 제공했음)를 제공하기 시작했다. 얘를 시작으로 2014년 하반기부터는 국내 지도 사이트들도 북한 정보를 제공하기 시작했다.
고무적인 현상이다. 물론 구글어스도 그 자체는 처음부터 구글이 개발한 게 아니라 타 업체 솔루션을 인수한 것이긴 하지만 말이다.

본인이 예전에 인터넷 지도에 대해 썼던 글은 이 모든 기능이 별도의 응용 프로그램이 아니라 웹에서 웹 표준 기술만으로 바로 구현 가능해진 것이 신기하다는 요지였다.
이번에는 다른 분야에서 대단히 신기하게 느껴지는 것에 대해 이야기를 늘어놓아 보겠다. 바로 이미지 가공 기술이다.

지도 사이트들이 제공하는 평면 항공 사진은 (1) 넓디넓은 영역을 한결같이 위에서 아래를 내려다보는 단일 각도로 본 이미지이다. 그런데 이거 정말 가공을 많이 했겠다는 생각이 들지 않는가?
이미지에서 원근감이라는 걸 완전히 제거하고 건물들이 마치 스타크래프트 맵처럼 보이게 해야 한다. 중심에서 먼 곳의 건물일수록 모양이 왜곡되어 보이는 카메라 렌즈의 오차를 보정해야 한다.

물론 엄청 높은 곳에서 촬영을 하면 건물 자체의 높이로 인해 발생하는 원근감은 상당수 없어지지만 이번엔 반대로 고층 건물도 높이가 전혀 표현되지 않게 되며, 또 사진의 화질이나 해상도, 그리고 구름으로 인한 시야 가려짐 같은 기술적인 문제도 커진다. 게다가 지구 자체도 근본적으로 평면이 아니라 둥근 구이니, 이로 인한 평면의 왜곡은 카메라의 위치가 높아질수록 더욱 부각되어 보일 것이다.

이런 항공 사진은 전세계의 것을 동시에 촬영하기란 불가능할 테니 여러 사진, 혹은 연속적으로 촬영된 사진을 파노라마 사진 만들듯이 연결해야 할 것이고 이 사진들은 촬영 시간대도 최대한 일치해야 할 것이다(광량의 차이). 또한, 주행 중이어서 시시각각 위치가 변하는 자그마한 자동차나 열차의 모습은 어떻게 보정을 하면 좋을까?
이런 것들을 다 극복하고 전국· 전세계의 항공 사진을 최대한 일관성 있는 색조와 각도로 엮는 것은.. 그 어려움과 복잡함이 정말 말도 못 할 것 같다. 비행기에서 아래를 내려다보고 사진만 팡팡 찍는다고 해서 구현 가능한 게 아니다.

사용자 삽입 이미지
(사진으로 나타난 63 빌딩의 높이와, 그림자의 길이를 비교해 보자.;; 각도가 뭔가 자연스러운 것 같지는 않다. 보정을 한 게 아닐까..)

그 보정이 자동화가 가능한지 아니면 일일이 수작업으로 행해지는지가 궁금하다.
마치 요런 영화 촬영 기법을 떠올리게 한다. 피사체는 시간이 정지한 듯 꼼짝 않고 있는데 카메라가 뱅그르르~ 돌아가면서 다른 위치와 각도에서 피사체를 응시하며 촬영하는 것 말이다. 심지어 사람이 하늘에 붕 떠 있는 채로 그런 장면이 나오기도 하니 더욱 신기한 일이다.

그리고 다음으로 생각할 것은 로드뷰이다.
이것은 앞의 항공 평면 사진과는 반대로, (2) 단일 시점에서의 view를 모든 각도로 제공하는 것이다. 이것은 어쨌든 연속으로 촬영할 수는 없기 때문에 로드뷰의 시점은 수~십수 미터 간격으로 띄엄띄엄 제공된다.

사용자 삽입 이미지

이런 시점 view는 지금이야 지도 사이트에서 쉽게 열람할 수 있는 기능이 됐지만, 옛날에 2000년대 초엔 철도청 홈페이지에서 자바 애플릿 형태로 비슷한 기능을 제공한 게 있었다.
바로 새마을· 무궁화· 통일호 내지 전동차의 객실 내부를 저런 로드뷰처럼 상하좌우 둘러보는 기능이었다.

이 기능은 내부적으로 2차원 평면 형태의 파노라마 사진을 한 장 저장하고, 그 그림의 일부에다 원근법을 적용하여 변형한 것을 표시하는 형태로 구현되어 있다. 내가 아는 건 이게 전부이고 구체적으로 어떤 계산을 하는지, 그리고 이런 용도로 사용하는 사진은 어떤 형태이고 어떻게 촬영하는지에 대해서는 잘 모른다. 그야말로 상하좌우 시야각이 다 열려 있는 특수한 카메라를 써야 할 텐데..

내가 10여 년 전에 이미 3차원 그래픽 시연 프로그램이라는 것도 만들어 봤지만, 비트맵 이미지로부터 3차원 시야를 어떻게 구현하는지는 여전히 감이 안 온다.
2차원 이미지에서 원근감을 넣거나 없애고, 평면과 공간 사이를 오고 가게 하는 기술이 참 대단하게 느껴진다. 그 기술이 인터넷 지도, 더 나아가 증강현실 같은 것도 가능하게 한 셈이다.

Posted by 사무엘

2015/04/08 08:32 2015/04/08 08:32
,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1081

Windows API 메모

1.
Windows API에서 DrawText는 gdi도 아니고 user 계층에 있는 고급 함수인 주제에 여러 줄(DT_SINGLELINE 플래그 없는 기본 모드)을 찍을 때에도 세로 정렬(DT_VCENTER, DT_BOTTOM)을 좀 지원해 주면 어디 덧나나 싶다. 오래 전부터 개인적으로 매우 대단히 아쉽다고 생각해 온 점이다.

얘는 gdi 계층에 있는 다른 글자 출력 함수들과는 달리, 글자수를 -1 (null-terminate string을 가정하고 알아서 길이를 계산하게)로 줄 수가 있으며, 긴 파일/디렉터리 이름의 중간을 생략하여 찍거나 액셀러레이터 &를 다음 글자의 밑줄로 바꿔 출력하는 기능, 심지어 밑줄만 출력하는 기능도 있다.
& 전처리의 경우, 하는 게 아니라 “끄는 게” 별도의 플래그로 주어져 있을 정도로 기본 기능이다.

그러니 이건 천상 운영체제 내부에서 자기네 GUI 출력용으로 쓰는 함수인데 제3자도 사용할 수 있게 공용 API로 열어 놨다는 뜻이다.
안 그래도 텍스트를 처음부터 끝까지 쭉 읽어 봐야만 할 수 있는 처리들이 즐비한데, 그에 비해 멀티라인 텍스트의 세로 정렬은 텍스트 전체에서 \n 개수를 세어서 줄 수만 파악하고 나면 아주 손쉽게 구현 가능한 처리이다.
그러니 왜 지원을 안 하는지가 몹시 의문이다.

공교롭게도 운영체제의 컨트롤들 중에 static text는 DrawText의 기능을 사용해서 그런지 multiline 상태에서 세로로 중앙이나 아래 정렬을 하는 옵션이 없다.
그러나 버튼(push, radio, check 모두)들은 그런 옵션이 있다.

2.
아마 이건 예전에 의견을 한번 피력한 적이 있는데 다시 적자면..
본인은 선에 안티앨리어싱을 해서 그리는 기능 정도는 그냥 Pen 관련 GDI 함수/구조체에다가도 스타일로 추가해서 지원을 좀 해 줬으면 하는 생각을 한다. PS_SMOOTH 정도로..;;
마치 Cleartype이 적용된 글자를 찍기 위해 생소한 API를 굳이 사용할 필요가 없는 것처럼 말이다. 그냥기존 LOGFONT 구조체의 lfQuality에 새로운 값이 추가되는 걸로 훌륭하게 잘 구현되지 않았던가.

21세기 초에 야심차게 도입됐던 GDI+는 하드웨어 가속 버프도 없이 거의 버림받은 신세가 됐고, Direct2D는 COM을 사용하는 등 API 패러다임이 너무 다르다.
하지만 GDI는 유구한 역사를 자랑하는 Windows의 창립 멤버 API이고 이제 와서 도저히 버릴 수가 없는 압도적인 짬밥을 보유하고 있으니.. 그냥 유지보수 차원에서만 지원되는 legacy가 돼 버렸고 GDI API에 근본적인 확장은 없을 것으로 생각된다.

3.
유니코드 UTF16 문자열과 여타 8바이트 기반 인코딩(UTF8 포함) 사이를 변환하는 API 함수는 잘 알다시피 WideCharToMultiByte와 MultiByteToWideChar이다.
얘는 Windows NT가 유니코드+2바이트 wide char 기반으로 통 크게 설계되었을 때부터 역사를 함께 해 왔다. 옛날에 Windows 3.x에다가 Win32s를 설치하면 단순히 32비트 커널+썽킹 코드뿐만 아니라 코드 페이지 변환 테이블도 잔뜩 설치되었다. 32비트 EXE/DLL은 리소스의 내부 포맷부터가 유니코드인 관계로, 이들을 당장 변환할 수 있어야 하기 때문이다.

유니코드에서 여타 인코딩으로 변환하는 것은 마치 double에서 short로의 형변환처럼 큰 집합에서 작은 집합으로 이동하는 변환이다. 그러니 인코딩에 존재하지 않는 문자는 ? 같은 default 문자로 치환된다.
그런데, 별도의 플래그가 없다면 WideChar... 함수는 약간의 '유도리'를 발휘하여 동작한다. 여러 유니코드 문자가 한 여타 인코딩으로 변환될 수 있다는 뜻이다.

예를 들어, 유니코드를 KS X 1001로 변환한다고 치면, 원래 거기에 있던 호환용 한글 자모 ㄱ(U+3131)만 0xA4, 0xA1로 바꾸는 게 아니라 표준 한글 자모 영역에 있는 U+1100(초성 ㄱ)과 U+11A8(종성 ㄱ)까지 다 호환용 한글 자모 ㄱ으로 바꾼다는 뜻이다. ?로 바꾸지 않는다.
이런 예가 호환용 한글 자모나 일부 유럽 문자에 대해서 더 존재한다. 유럽 문자라 함은, 대문자 버전이 존재하지 않을 경우 그냥 소문자 버전으로 바꾸는 식이다.

이런 동작을 원하지 않고 엄밀하게 변환을 하고 싶다면 WC_NO_BEST_FIT_CHARS라는 플래그를 반드시 줘야 한다. 얘는 변환된 타 인코딩을 유니코드로 역변환했을 때 원래의 유니코드로 정보가 유지되지 않는다면 무조건 ?로 바꾼다. 즉, U+11??대의 표준 한글 자모는 호환용 한글 자모로 바뀌지 않는다. 이 옵션은 Windows NT4에도 존재하지 않으며, 98/2000부터 새로 추가된 얼마 안 되는 기능이다.

어느 방식을 사용할지는 그야말로 상황에 따라 다르다. 문자열을 복사하는 함수만 해도 버퍼 크기가 초과되었을 때 그냥 뒷부분을 융통성 있게 잘라 버려도 괜찮은 경우가 있는가 하면, 반드시 정확도가 보장되어야 해서 차라리 예외가 발생해야 하는 경우도 있을 수 있으니 말이다.

한편, 여타 인코딩에서 유니코드로 바꾸는 경우는 작은 집합에서 큰 집합으로 가는 것이니 일단은 유니코드에 대응하지 못하는 문자 걱정은 없다.
하지만 아무래도 여러 바이트가 한 글자를 구성하다 보니 정규화가 잘못되어서 해당 인코딩에 해당하지 않고 유니코드로 변환 자체가 될 수 없는 바이트 나열이 들어있을 수 있다. 이 경우는 유니코드로 변환했다가 다시 그 인코딩으로 역변환을 했을 때 바이트 나열이 원래대로 돌아올 수가 없게 된다.

이런 일이 발생했는지를 엄격하게 체크하려면 Multi... 함수에다 MB_ERR_INVALID_CHARS 플래그를 주면 된다.
<날개셋> 편집기는 이 두 경우를 모두 체크하여 불러오기가 제대로 되지 않았을 때, 혹은 저장과 함께 정보가 소실될 우려가 있을 때 경고 메시지가 나온다.
저장이야 UTF8 내지 UTF16 같은 유니코드 계열 인코딩만 골라 주면 문제가 없지만, 불러오기 자체가 문제가 있었다면 그 어떤 인코딩을 쓰더라도 다시 저장하는 순간 정보 소실이 생기기 때문이다.

4.
다음으로, 우클릭 메뉴를 구현할 때 즐겨 쓰이는 TrackPopupMenu(Ex) 함수에 대해서도 좀 한 마디 하겠다.
사실 얘는 굳이 임의의 지점을 우클릭했을 때 외에도, 어떤 버튼을 눌렀을 때 메뉴가 튀어나오게 하는 용도로도 많이 쓰인다. 그래서 Ex 버전에서는, 메뉴가 상하좌우 좀 치우친 곳에서 튀어나와서 위치 보정이 필요하더라도, 그 버튼 영역은 메뉴에 의해 가려지지 않게 하는 유용한 옵션이 추가되었다.

윈도 Vista 이상에서부터는 버튼의 오른쪽 끝에 ▼라는 split 버튼을 넣는 옵션이 추가된 관계로, 팝업 메뉴는 이 UI와 연동되어 즐겨 사용된다. 본인이 개발하는 <날개셋> 한글 입력기의 제어판 UI에도 물론 적극 활용되었다.

그런데 그건 그렇고.. 본인이 이 함수에 대해서 좀 이해가 안 되는 면모는 크게 두 가지이다.
얘는 HWND를 하나 인자로 받는다. 사용자가 메뉴를 ESC로 취소하지 않고 뭔가 항목을 선택하면 그 명령 ID가 부모 윈도우에다가 WM_COMMAND의 형태로 전달된다. 이것은 일단은 팝업 메뉴 말고도 단축키 내지 프로그램 창에 기본으로 딸린 메뉴를 선택했을 때와 동작의 일관성을 맞추기 위한 조치이다.

그러나 그렇게 하지 말고 사용자가 선택한 명령 ID가 그냥 함수의 리턴값으로 바로 오게 할 수도 있다. DLL 같은 걸 만들기 때문에 응용 프로그램의 기본 메뉴 연계 따위를 생각 안 하는 환경에서는 이런 디자인이 훨씬 더 유용하다. 그래서 이때는 flag에다가 TPM_RETURNCMD를 주면 된다.

사소해 보이는 팝업 메뉴의 디자인도 이렇게 두 양상으로 나누어 생각할 수가 있는 것이다.
마우스의 드래그 드롭 동작을 각 WM_LBUTTONDOWN, WM_MOUSE, WM_LBUTTONUP 핸들러 함수에다 제각기 따로 처리할지, 아니면 WM_LBUTTONDOWN 안에다가 또 message loop을 만들어서 한 함수 안에다가 다 집어넣을지의 차이와 비슷한 맥락이다.

아무튼, 메뉴에서 TPM_RETURNCMD에 대해, MSDN에는 "determine the user selection without having to set up a parent window for the menu."라는 문장까지 버젓이 있는데..
그럼에도 불구하고 TPM_RETURNCMD가 있더라도 HWND hParent의 값은 어떤 경우에도 NULL이어서는 안 된다. 심지어 자신이 만들지 않은 다른 윈도우(데스크톱 전체 윈도우 같은)를 줘도 안 되고 동작이 실패한다.

WM_COMMAND를 안 받으면 이 윈도우는 정말 레알 천하에 필요하지 않은데도 말이다. 애초에 메뉴가 튀어나오는 좌표도 언제나 화면 좌표이지 부모 윈도우 같은 걸 받지도 않는다. 그래도 이 윈도우는 없으면 안 된다.
그래서 <날개셋> 한글 입력기는 부득이하게 화면에 표시도 안 되는 message-only 윈도우를 간단히 만들어서 이걸 셔틀로 삼아 메뉴를 띄운 뒤, 메뉴가 사라지자마자 그 윈도우를 메시지 펌핑 하나 안 하고 파괴해 버리는 꼼수를 불가피하게 쓴 부분도 있다. 순전히 삽질이다.

이게 한 가지이고, 다른 하나는.. TPM_NONOTIFY라는 플래그는 왜 있느냐는 것이다. TPM_RETURNCMD 플래그가 있으면 명령 ID는 리턴값으로 오고 WM_COMMAND가 가지 않아서 이미 no notify의 효과가 나는데 저 플래그가 또 하는 일이 무엇인지 MSDN만 봐서는, 또 내 직관과 경험만으로는 모르겠다. 알 수 없는 노릇이다.

5.
인터넷에서 갓 다운로드한 파일은 운영체제가 뭔가 좀 다르게 취급한다는 걸 컴퓨터(일단은 Windows 기준으로) 사용자라면 경험적으로 다들 아실 것이다.
Word나 Excel 같은 프로그램에서 문서를 열면 "이 문서는 인터넷에서 가져온 것이기 때문에 위험할 수 있다. 매크로를 기본적으로 꺼 놨다" 이런 꼬리표가 붙는다. msi나 exe는 잠재적인 범죄자로 취급되며, 특히 디지털 서명 같은 게 없으면 다루기가 정말 까다로워져 있다.

먼 옛날 2000년대 중반엔 Windows XP에 보안 업데이트가 행해져서 이렇게 '인터넷 다운로드'로 분류돼 있는 CHM(컴파일된 HTML)은 아예 화면에 표시가 되지 않게 됐다. 파일 속성을 들어가서 '차단 해제'를 해 줘야만 이들 파일도 일반 파일들과 동등하게 다룰 수 있게 된다.
(XP도 초창기엔 읽기 전용 매체인 CD도 아니고 USB 메모리가 autorun.inf 실행이 됐을 정도로 UI 차원에서의 보안이 굉장히 막장이긴 했다. 이것도 다 훗날 보안 업데이트를 통해 막혔음.)

그나저나 저런 '다운로드 파일' 보안 속성은 운영체제 내부에서는 어떻게 구현되어 있을까?
가장 간단하게 생각할 수 있는 방법은 도스 시설부터 존재했던 파일 속성이다. 일명 ARHS(기록, 읽기 전용, 숨김, 시스템)의 형태로 존재하던 것 말이다. 실제로 Windows에는 이것 말고도 압축/암호화 등 내부적으로 쓰이는 속성이 더 있다.

하지만 다운로드 속성은 그런 비트 형태의 속성으로 구현되어 있지는 않다.
바로 파일 시스템 차원에서 제공되는 대체 데이터 스트림이 해당 파일에 꼬리표처럼 붙는데 거기에 있는 zone identifier가 이 파일이 인터넷에서 왔음을 나타낸다.

대체 데이터 스트림은 당장 내 컴퓨터의 다운로드 디렉터리에서 DIR /r을 하면 정체를 확인할 수 있다. 내 기억이 맞다면 CreateFile 함수로 저 대체 스트림의 내용을 바로 확인할 수도 있으며, IZoneIdentifier 인터페이스 등을 얻어서 이것을 조작할 수도 있다. 물론 저 꼬리표를 제거하는 것도 포함해서 말이다. 자세한 방법 소개는 The Old New Thing 블로그 내용을 링크하는 것으로 대체하겠다.

이런 기능이 과거의 FAT 계열 파일 시스템에서 가능했을 것 같지는 않고.. 언제 도입되었는지는 잘 모르겠다. 최소한 Windows 9x 시절의 IE 6 미만에는 없었던 것 같다.

Posted by 사무엘

2015/04/05 08:35 2015/04/05 08:35
, ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1080

* 지금으로부터 무려 4년 전에 Windows 공용 컨트롤에 대해서 글을 쓴 적이 있었는데 오늘은 그에 대한 연장선이다. 또 옛날 이야기를 늘어놓아 보겠다.

예전에도 글을 썼듯이, 공용 컨트롤은 좀 더 새끈한 UI를 제공하기 위해, Windows 1.0 이래로 기본 제공되던 시스템 컨트롤에 추가적으로 도입된 컨트롤들이다.
사용 전에 InitCommonControls 함수 호출이 필요하다지만, 요즘은 공용 컨트롤 (6.0) 매니페스트를 지정하는 것만으로도 초기화가 자동으로 되기 때문에 EXE에서는 이 절차가 굳이 필요하지 않다.

공용 컨트롤들은 완전히 새로운 기능이라기보다는 Windows의 특정 응용 프로그램이나 Office에서 내부적으로 자체 구현으로 돌리던 싸제 컨트롤이 보급품으로 바뀌는 경우가 많다.
이들은 16비트 시절을 경험한 적이 없고 Windows 95/NT 3.51과 역사를 같이하기 때문에, '32비트'를 강조하기 위해 클래스들의 이름이 대부분 32로 끝난다는 특징이 있다. ListView, TreeView 같은 것들은 이런 1기 공용 컨트롤이다.

그 뒤 Internet Explorer 3 이후로 공용 컨트롤은 IE의 버전업을 따라 비약적으로 발전하기 시작했다. 달력 컨트롤, 날짜 선택 컨트롤, ReBar 같은 건 운영체제 보급 컨트롤이라기보다는 뭔가 델파이 컴포넌트 같은 느낌이 드는데.. 이것들은 IE와 함께 도입된 2기 컨트롤이다.

그렇게 새로운 GUI 컨트롤을 만들어서 자기 혼자만 안 쓰고 꼬박꼬박 다른 프로그래머에게도 공개한 건 의도는 좋지만, 그 대신 1990년대 말엔 4~5.x대의 온갖 버전의 comctl32.dll이 난립하면서 Windows가 DLL hell 비판을 받기도 했다. 응용 프로그램이 자신을 기준으로 하는 comctl32.dll을 시스템 디렉터리에다가 막 덮어쓰면서 운영체제의 안정성을 떨어뜨렸기 때문이다.

공용 컨트롤의 3기는 side-by-side assembly라는 방식으로 DLL hell을 종식시키고 GUI가 근본적으로 싹 바뀐 Windows XP와 함께 도래했다. 그리고 3기와 함께 추가된 새로운 공용 컨트롤은 아시다시피 하이퍼링크 컨트롤이다. 인터넷 시대가 도래하면서 하이퍼링크 역할을 하는 컨트롤의 필요성은 예전부터 대두되어 왔으니 말이다.

텍스트 전체가 단일 링크인 게 아니라 A 태그로 둘러싸인 부분만 링크이며, 한 컨트롤 내부에 여러 링크가 있을 수 있기 때문에 더욱 편리하다. A 태그가 없는 하이퍼링크 컨트롤은 그냥 텍스트 static 컨트롤과 별 차이가 없다. 얘는 재래식 InitCommonControls(Ex)가 아니라 오로지 공용 컨트롤 6.0 매니페스트로만 사용 가능하다.

공용 컨트롤들 중에 에디트 컨트롤과 동작이 비슷해 보이는 건 IPv4 주소를 입력받는 컨트롤이 있는데, 내부적으로 자그마한 에디트 컨트롤을 4개 나란히 생성하여 동작한다. 운영체제의 제어판 밖에서는 별로 볼 일이 없는 물건임. IPv6 주소를 입력받을 때는 그냥 일반 에디트 컨트롤을 썼더라.

그리고 잘 쓰이지는 않지만 단축글쇠 입력 컨트롤도 있다. 캐럿도 생성하고 언뜻 보기에 에디트 컨트롤의 서브클래싱 버전 같지만 얘는 에디트 컨트롤을 사용하지 않고 독자적으로 동작하는 물건이다. 사용 가능하거나 반드시 써야 하는 modifier를 Ctrl, Alt, Shift 중에서 지정할 수 있다.
<날개셋> 한글 입력기는 이것들의 좌우 구분이 가능해야 하고 Win키까지도 modifier로 지정 가능해야 하는 관계로, 용도에 맞지 않아서 단축글쇠 규칙 편집 UI에서도 이 컨트롤을 사용하지 않았다.

위의 컨트롤과는 달리 리치 에디트 컨트롤은 공용 컨트롤이 아니다. 얘는 혼자 독자적인 DLL을 갖고서 따로 노는 물건이기 때문에 초기화도 공용 컨트롤과는 다른 방법으로 한다. 복잡한 워드 프로세서를 통째로 컴포넌트화한 것이기 때문에 이것 하나만으로도 다른 어지간한 컨트롤들의 덩치를 모조리 능가한다고 봐야 할 것이다.
예전에도 한번 글로 썼듯이 리치 에디트 컨트롤은 파일 이름과 버전 사이의 관계가 굉장히 이상하게 꼬였다. SxS 방식을 쓰는 것도 아니고.

IE 웹브라우저 컨트롤은 공용 컨트롤이 아닐 뿐만 아니라 일반 윈도우 자체도 아니다. ActiveX 컨트롤이기 때문에 COM API를 써서 훨씬 더 복잡한 방식으로 초기화해서 사용해야 한다. MFC의 도움 없이는 난 불러다 써 보지도 못했다.

comctl32.dll에는 공용 컨트롤을 구동하는 코드가 주로 들어있을 테니 이들을 초기화하는 함수 말고 딱히 다른 기능이 있을까 싶은 생각이 든다. 하지만 기성 대화상자를 변형하여 동작하는 property sheet나 wizard GUI를 구동하는 함수도 여기 있고, 또 image list를 관리하는 함수들도 죄다 여기에 들어있다. 이게 user나 gdi에 들어있지 않고 comctl에 들어있는 이유는, 이 이미지 리스트들은 여러 공용 컨트롤들이 이미지를 표시할 때 한데 공유하는 자료구조이기 때문이다.
그런데 윈도우 컨트롤이 전혀 아닌 물건이 다른 공용 컨트롤과 같은 등급의 카테고리에 문서화돼 있으니 이건 좀 의아한 점이다.

공용 컨트롤들에 대해 본인이 오랫동안 의아하게 생각해 온 점은.. 클래스 이름들의 작명에 일관성이 없다는 점이다. 작명 방식은 크게 세 가지가 있는데, 이것들이 별다른 원칙 없이 뒤죽박죽으로 섞여 있다. 32라는 숫자로 끝난다는 점 말고는 다른 공통점이 없는...데, 그러고 보니 하이퍼링크와 pager 컨트롤은 예외적으로 32가 안 붙었다!

  1. Sys+대문자 계열: SysIPAddress32, Header32, Link, ListView32, TreeView32, TabControl32, Animate32, MonthCal32, DateTimePick32, Pager
  2. msctls_+소문자 계열: msctls_hotkey32, statusbar32, trackbar32, updown32, progress32
  3. 아무 접두사가 없음: ToolbarWindow32, ReBarWindow32, ComboBoxEx32

어지간한 응용 프로그램에서 안 쓰이는 경우가 없는 도구 모음줄과 상태 표시줄만 해도 클래스 이름의 작명 스타일이 (2)와 (3)으로 서로 다르다.

심지어는 소스 코드상으로 클래스 이름을 나타내는 매크로 상수조차도 작명 방식에 통일성이 없다. WC_* 로 시작하는 명칭이 있는가 하면 그냥 *CLASSNAME로 끝나는 명칭도 있다. (toolbar, rebar, statusbar)
서로 다른 팀에서 별개로 만들던 컨트롤들을 한데 합쳐서 이런 일이 생긴 것 같다. 물론 대세는 WC_* 스타일이다.

마소에서도 이런 식의 이름 혼란에 대해서 의식을 전혀 안 하고 있는 건 아니다.
공용 컨트롤들이 사용하는 구조체를 보면 TV_*로 시작하는 구조체가 NMTV*로 바뀌고 예전 명칭은 typedef로 처리되는 등, rename을 종종 하기도 한다. 하지만 처음부터 개명을 할 일이 없게 명칭을 잘 정하는 게 더 좋았을 것이다.

이상이다.
그나저나 공용 컨트롤의 스펙을 다시 보니 옛날에는 Native font control이라는 게 있었던 모양이다. 클래스 이름도 NativeFontCtl이라고 당당하게 있는 윈도우인데.. 도대체 뭘 하는 물건이었지?

The native font control is an invisible control that works in the background to allow a dialog box's predefined controls to display the current system language.


MSDN에 문서화는 이렇게 돼 있지만, 도대체 이런 윈도우를 만들어서 해결하려고 한 문제가 무엇인지.. 그리고 지금은 그게 왜 불필요해졌는지에 대한 의문은 해결되지 않는다. 공용 컨트롤의 세계도 다시 살펴보니 재미있다.

Posted by 사무엘

2015/02/28 08:25 2015/02/28 08:25
,
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1067

1. elseif 키워드

프로그래밍 언어에 따라서는 else if를 한데 묶은 축약형인 elseif 또는 elif 키워드를 별도로 제공하는 경우가 있다.
베이직이나 파이썬, 그리고 프로그래밍 요소 중에 없는 게 없는 백과사전형 언어인 Ada에는 저게 있다.

하지만 파스칼, C/C++이나 그 파생형 언어들은 전통적으로 그게 없다. 굳이 그걸 또 제공할 필요 없이 기존 if/else만으로도 동일한 표현력과 계산 능력 자체는 낼 수 있으며,
또한 더 큰 이유로는, 이들 언어는 안 그래도 공백이나 줄바꿈에 구애를 받지 않는 freeform 문법이기 때문이다. 필요하다면 어차피 else if를 한 줄에 나란히 연달아 써도 elseif와 얼추 비슷한 비주얼을 만들 수 있다. (컴파일러의 구문 분석 스택은 복잡해지겠지만..) 베이직과 파이썬은 그렇지 않다.

elseif 축약형은 else 절에서 실행되는 구문이 다음 if 절에 '완전히' 포함되어 있을 때 유용하다.
원래는 else 다음에 소스 코드의 들여쓰기가 한 단계 증가해야 하지만 그렇게 하기는 귀찮고..
수평적인 들여쓰기 단계에서 여러 개의 if를 대등한 위상에서 마치 switch-case처럼 늘어놓고 싶을 때 elseif가 쓰인다.

이런 점에서 보면 elseif 축약은 if-else에 대해서 tail-cut recursion을 한 것과 개념적으로 유사하다.
함수 재귀호출 뒤에 또 다른 추가적인 계산이 없다면, 그런 단순 재귀호출 정도는 스택을 사용하지 않는(= 한 단계 깊이 들어가는) 단순 반복문으로 바꾸는 것 말이다.

사실 C/C++은 elseif 축약이라는 개념은 언어 자체엔 없고 전처리기에만 #elif라는 형태로 있다.
전처리기는 알다시피 freeform 문법이 아니기 때문에 elif 없이 else와 if를 동시에 표현하려면 얄짤없이 줄 수가 둘로 늘어나야 하니,
문법을 최대한 간단하게 만들고 싶어서 부득이 그런 지시자를 넣은 것 같다.

2. NULL 포인터와 0

하루는 통상적으로 사용하던 #define NULL을 0에서 nullptr로 바꾸고 날개셋 코드를 리빌드해 봤다. 그랬더니.. 생각지 못했던 곳에서 엽기적인 컴파일 에러가 떴다.

아니 내가 머리에 총 맞았었나.. 왜 bool 변수에다가 NULL을 대입할 생각을 했지? =_=;;
HRESULT 리턴값에다가 S_OK 대신에 return NULL을 해 놓은 건 도대체 뭔 조화냐.
그리고 그 정도는 애교고.. obj=NULL이 원래는 컴파일 에러가 났어야 했는데 잘못된 코드를 생성하며 지나쳐 버리는 경우가 있었다. 포인터를 별도의 클래스로 리팩터링하는 과정에서 실수가 들어간 것이다.

그 클래스가 정수 하나를 인자로 받는 생성자가 있기 때문에 obj=Class(0)으로 자동으로 처리되고 넘어갔는데, 그 클래스는 독자적인 메모리 할당이 있으면서 대입 연산자 같은 것도 별도로 존재하지 않았다.
이런 일을 막으려고 C++엔 나중에 생성자에 explicit이라는 속성을 지정하는 키워드가 추가되었지만 그걸 사용하지 않는 레거시 코드를 어찌할 수는 없는 노릇이고..

아무튼 언어에서 type-safety를 강화하는 게 이렇게 중요하다는 걸 알 수 있었다.
Windows 플랫폼 헤더 include에서 NULL의 definition이 nullptr로 바뀌는 날이 언제쯤 올까? 옛날에 16비트에서 32비트로 넘어갈 때는 핸들 타입에 대한 type-safety를 강화하면서 STRICT 상수가 도입된 적이 있었는데.

NULL은 C 시절에 (void *)0, 초창기 C++에서는 타입 오버로딩 때문에 불가피하게 그냥 0이다가 이제는 nullptr로 가장 안전하게 변모했다.
개인적으론, PSTR ptr = false; 도 컴파일러 차원에서 안 되게 좀 막았으면 좋겠으나.. 포인터에 0상수 대입은 뭐 어찌할 수 없는가 보다.

3. 자바의 문자열

자바(Java)로 코딩을 하다 보면 나처럼 C++ 사고방식에 머리가 완전히 굳은 사람의 관점에서 봤을 때 궁금하거나 불편하다고 느껴지는 점이 종종 발견된다.
int 같은 기본 자료형이 아니면 나머지는 모조리 클래스이다 보니 한 함수에서 데이터 참조용으로나 잠깐 사용하고 마는 int - string 쌍 같은 것도 못 만드는지? 그런 것도 죄다 새 클래스로 만들어서 new로 할당해야 하는지?

그리고 기본 자료형은 값으로만 전달할 수 있으니 int의 swap 함수조차 만들 수 없는 건 너무 불편하지 않은지?
인클루드가 없는데 자신 외의 다른 클래스에 존재하는 public static final int값이 switch case 상수로 들어오는 게 가능한지? 등등..

이와 관련되어 문자열은 역시 자바 언어에서 좀 어정쩡한 위치를 차지하며 특이하게 취급되는 물건이다.
얘는 일단 태생은 기본 자료형이 아닌 객체/클래스에 더 가깝다. 그래서 타입의 이름도 소문자가 아닌 대문자 S로 시작하며, 이 개체는 가리키는 게 없는 null 상태가 존재할 수 있다.

그러나 얘는 문자열 상수의 대입을 위해서 매번 new를 해 줘야 하는 건 또 아니다. 이건 예외적으로 취급되는 듯하다.
그럼 그냥 String a; 라고만 하면 얘는 길이가 0인 빈 문자열인가(""), 아니면 null인가? 그리고 지역 변수일 때와 클래스 멤버 변수일 때에도 그 정책이 동일한가? 뭐 직접 회사에서 프로그램을 짜 본 경험으로는 전자인 것 같긴 하다.

단, 자바의 문자열을 다룰 때는 주의해야 할 점이 있다. 자바 프로그래머라면 이미 잘 숙지하고 계시겠지만, 문자열의 값 비교를 ==로 해서는 안 된다는 것이다. equals라는 메소드를 써야 한다.
==를 쓰면? C/C++식으로 얘기하자면 문자열이 들어있는 메모리 포인터끼리의 비교가 돼 버린다. 애초에 포인터의 사용을 기피하고 다른 걸로 대체하는 컨셉의 언어에서, 이런 동작은 99% 이상의 경우는 프로그래머가 의도하는 결과가 아닐 것이다.

C++에서야 문자열 클래스에 == 연산자가 오버로딩되지 않은 경우가 없을 테니 언어가 왜 저렇게 만들어졌는지 이해하기 어렵겠지만.. 자바는 연산자 오버로딩이란 게 없는 언어이며 String은 앞서 말했듯이 기본 자료형과 클래스 사이의 어중간한 위치를 차지하는 물건이기 때문에 이런 디자인의 차이가 발생한 듯하다. 자바는 안 그래도 걸핏하면 클래스 새로 만들고 get/set 등 다 메소드로 구문을 표현해야 하는 언어이니까.
오죽했으면 본인은 회사에서 자바 코드를 다루면서도 문자열 비교를 실수로 ==로 잘못 해서 발생한 버그를 발견하고 잡은 적도 있었다.

그나저나 유사 언어(?)인 스칼라, 자바스크립트 같은 언어들은 ==로 바로 문자열 비교가 가능했던 걸로 기억한다.

4. true iterator

파일을 열어서 거기에 있는 문자열을 한 줄씩 얻어 오는 함수(A), 그리고 각 문자열에 대해 출력을 하든 변형을 하든 일괄적인 다른 처리를 하는 함수(B)를 완전히 분리해서 별도로 작성했다고 치자. 혹은 한 디렉터리에 파일들을 서브디렉터리까지 빠짐없이 쭉 조회하는 함수(A)와, 그 찾은 파일에 대해서 삭제나 개명 같은 처리를 하는 함수(B) 구도로 생각할 수도 있다.
그런데 이 둘을 연계시켜서 같이 동작하게 하려면 어떻게 하는 게 좋을까?

이럴 때 흔히 떠올릴 수 있는 방법은,
A 함수에다가 B 함수까지 인자로 줘서 호출을 한 뒤, A의 내부 처리 loop에서 B에 넘겨줄 데이터가 준비될 때마다 B를 callback으로 호출하는 것이다. B는 간단한 일반 함수 + context 데이터 형태가 될 수도 있고, 아니면 가상 함수를 포함한 인터페이스 포인터가 될 수도 있다.

데이터 순회를 하는 A 자체도 파일을 열고 닫거나 내부적으로 재귀호출을 하는 등 state가 존재하기 때문에 매번 함수 실행을 시켰다가 종료하기가 곤란한 경우, 상식적으로 A를 먼저 실행시킨 뒤에 A가 계속 실행되고 있는 중(= 상태도 계속 유지되고)에 그 내부에서 B를 호출하는 게 바람직한 게 사실이다.
물론, 반복문 loop을 B에다가 두고, 반대로 B에서 A를 callback 형태로 호출하는 것도 불가능한 건 아니다. 그런데 프로그래밍 언어에 따라서는 이런 B 중심적인 사고방식의 구현을 위해 좀 더 획기적인 기능을 제공하는 물건도 있다.

def func():
    for i in [1,5,3]:
        yield i

a=func()
print a.next()
print a.next()
print a.next() # 예상하셨겠지만 1, 5, 3 순서대로 출력

파이썬에는 함수에 return 말고 yield 문이 있다. 그러면 얘는 함수 실행이 중단되고 리턴값이 지정되기는 하는데..
다음에 그 함수를 실행하면(정확히는 next() 메소드 호출 때) 처음부터 다시 실행되는 게 아니라, 예전에 마지막으로 yield를 했던 곳 다음부터 계속 실행된다. 예전의 그 함수 호출 상태가 보존되어 있다는 뜻이다.

난 이걸 처음 보고서 옛날에 GWBASIC에 있던 READ, DATA, RESTORE 문과 비슷한 건가 싶었는데.. 저건 당연히 GWBASIC을 아득히 초월하는 고차원적인 기능이다. C++이었다면 별도의 클래스에다가 1, 5, 3 static 배열, 그리고 현재 어디까지 순회했는지를 가리키는 상태 인덱스 정도를 일일이 구현해야 했을 텐데 저 iterator는 그런 수고를 덜어 준다.

단순히 배열이 아니라 binary tree의 원소들을 prefix, infix, postfix 방식으로 순회한다고 생각해 보자.
순회하는 함수 내부에서 다른 콜백 함수를 호출하는 게 아니라 매번 원소를 발견할 때마다 리턴값을 되돌리는 형태라면..
구현하기가 굉장히 까다로울 것이다. 스택 메모리를 별도로 할당한 뒤에 재귀호출을 비재귀 형태로 일일이 구현해 주거나, 아니면 각 노드에다가 부모 노드의 포인터를 일일이 갖춰 줘야 할 것이다.

C++의 map 자료형도 내부적으로는 RB-tree 같은 자가균형 dynamic set 자료구조를 사용하는데, 이런 iterator의 구현을 위해서 편의상 각 노드에 부모 노드 포인터를 갖고 있는 걸로 본인은 알고 있다. RB-tree는 내부적으로 로직이 굉장히 복잡하고 까다로운 자료구조이긴 하지만, 그래도 부모 노드 없이도 구현이 불가능한 건 아닌데 말이다.
안 그랬으면 iterator가 자체적으로 스택을 멤버 변수로 갖거나, 최소한 메모리 할당· 해제를 위해 생성자나 소멸자까지 갖춰야 하는 복잡한 class가 돼야 했을 것이다. 어떤 경우든 포인터 하나와 비슷한 급인 lightweight 핸들이 될 수는 없다.

개인적으로는 지난 여름에 <날개셋> 한글 입력기 7.5에 들어가는 새로운 한글 입력 순서 재연 알고리즘을 구현할 때 비슷한 레벨의 iterator를 비재귀적으로 구현한 적이 있는지라, yield문의 의미가 더욱 절실히 와 닿는다.

Posted by 사무엘

2015/02/25 08:38 2015/02/25 08:38
, , ,
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1066

우주 개발과 관련하여 일반적인 항공· 우주덕, 역덕들에게 잘 알려져 있는 유명한 사고는 아폴로 1호나 13호, 그리고 챌린저와 컬럼비아 우주왕복선처럼 유인 우주선에서 발생한 인사사고 위주이다. 그게 임팩트가 크다.

하지만 컴퓨터공학 관련 수업에서 종종 언급되는 우주 사고는 저런 것들보다는 덜 알려진 무인 우주선의 오동작· 자폭 사고 두 건이다. 바로 (1) 1999년에 미국이 발사한 화성 기후 탐사 인공위성의 추락 사고와, (2) 1996년 유럽 우주국에서 발사한 정지 궤도 진입용 아리안 5호 로켓의 자폭 사고이다. 이것들은 다른 기계 구조적인 실수· 결함이 아니라, 전적으로 발사체 포함 로켓을 제어하는 소프트웨어의 버그로 인해 발생했기 때문이다.

전자는 서로 다른 팀의 엔지니어들이 같은 물리량에 대한 단위계를 제각각 다르게 가정하고 코딩을 하는 바람에, 계산 결과의 scale이 산으로 가 버려서 위성이 추락한 정말 안습한 사례이다. 흔히 길이(미터 vs 인치)의 착오라고 알려져 있는데, 더 자세한 문헌을 찾아 보니 사실은 단위 시간당 힘(킬로그램힘 vs 파운드)의 착오이다. 뭐 어느 것이든 표준 단위계와 비표준 단위계의 착오인 건 마찬가지이고 우리나라로 치면 제곱미터와 평, 킬로그램과 근 같은 게 헷갈린 것과 동일하다.

이 때문에 무려 9개월간의 긴 여행을 마치고 기껏 화성까지 잘 가서 궤도에 진입하려던 위성은 예상보다 고도가 급격히 낮아졌으며, 화성의 뒷면으로 들어가는 예상 시각보다 더 일찍 통신이 끊어졌다가 다시는 통신이 회복되지 못했다. 화성의 대기권에까지 들어가 버린 위성은 대기와의 마찰열로 인해 파괴되고 추락했다.

지구로부터 수천만 km나 떨어져 있는 다른 행성에서 벌어진 사고이다 보니, 사고 장면도 전해지는 게 없다.
사고의 원인이 어처구니없는 실수 때문이었음이 밝혀지자 미국 내부에서도 “우리도 미터법의 국내 도입이 시급합니다”라는 여론이 당연히 일었다. 그러나 오랜 관행을 바꾸는 일은 요원해 보인다.

한편, 후자의 사고도 사연이 만만찮게 안습하다.
로켓을 제어하는 프로그램의 내부에는 64비트 float 부동소수점을 16비트 int로 형변환을 하는 루틴이 있었다. 알다시피 이건 양 자료형의 표현 범위의 차이가 엄청나다. 단순히 소수점이 잘리는 것 이상으로 수의 표현 가능한 범위 자체가 잘릴 위험이 높다.

다만, 이전의 아리안 4호에서 이게 별 문제가 된 적이 없었던 관계로 이 부분을 맡은 엔지니어는 앞으로도 오버/언더플로우가 발생할 일은 없다고 판단했다. 그래서 성능 향상을 위해 범위 검사를 하는 옵션을 제외하고 프로그램을 빌드해서 우주선에다 탑재했다.

그런데 아리안 4호와 5호는 로켓의 규격이 서로 달랐으며, 일어나지 않으리라 예상했던 그것이 실제로 일어났다.
완전히 엉뚱하게 형변환된 정수 숫자가 예외 처리도 없이 계산식에 들어가면서 프로그램의 내부 상태는 엉망이 되었으며, 사태 극복을 할 수 없던 컴퓨터 프로그램은 결국 최후의 수단으로 설정되어 있던 자폭 모드로 진입했다. 그래서 아리안 5호 로켓은 발사된 지 겨우 37초 만에 기수를 아래로 숙이면서 추락했다.

중앙 통제실은 싸늘한 초상집 분위기로 변함. 망연자실한 직원들..;; (☞ 동영상 링크)
무인 우주선인 관계로 인명 피해는 없었지만 둘 모두 수억 달러급의 손실을 초래했다. 나로 호의 발사 실패하고도 급이 다른 게, 아리안 5호만 해도 나로 호보다 5배 이상 더 무거운(= 크기도 더 큰) 로켓이었기 때문이다. 그게 고스란히 폭죽으로 전락해 버렸으니.

학부 시절에 소프트웨어공학 수업 시간 때 들은 얘기를 먼 훗날 대학원에서 프로그래밍 언어 수업에서 다른 교수로부터 또 들으니 감회가 새로웠다.
전자 시간에는 체계적인 소프트웨어의 테스트/검증의 중요성에 대해 얘기하면서, 후자 시간에는 프로그래밍 언어 차원에서 타입 검증과 예외 처리의 필요성을 얘기하면서 저것들이 타산지석 사례로 소개되었다.

그나저나 아리안 5호에 들어가는 프로그램도 Ada로 작성되었다고 한다.
Ada에는 배열 첨자 범위라든가 형변환 overflow 예외를 감지하는 기능이 있고, 그걸 끄는 옵션도 별도로 존재한다.
C/C++처럼 무작정 프로그래머에게 모든 걸 맡기기보다는 적당히 언어 시스템이 개입해서 안전을 추구하는 것도 많다 보니 Ada가 프로그래밍 언어계에서는 꽤 noble한 대접을 받는가 보다. 하지만 배열 첨자를 마치 함수 호출처럼 ()로 하고, 명칭에 대소문자 구분이 없는 것은 좀 Basic스럽고 요즘 언어가 아닌 구시대 언어 같은 느낌이 든다.

참고로 Ada는 명칭 자체가 여자 이름인 반면, 코볼은 주 설계자가 수학자 출신의 여성 해군 장성이다(그레이스 호퍼).

Posted by 사무엘

2015/02/19 08:36 2015/02/19 08:36
, , ,
Response
No Trackback , 4 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1064

뭐, 무식이 자랑이랄 수는 없겠지만,
본인은 전산학 내지 컴퓨터공학의 여러 분야들 중에서 문외한에 가깝게 제일 모르는 분야는 통신, 네트워크, 웹, 보안 쪽이다.
왜 제일 모르느냐 하면, 저건 컴퓨터 한 대만으로 독학이 가능하지 않고, 뭔가 '감'을 터득할 수 없는 분야이기 때문이다. 그래서 그런 걸 잘하는 사람들이 부럽다..

일례로 완전 최저수준 소켓 API와, 고수준 HTTP API 사이의 중간 과정에 대한 감이 전혀 없다. 후자도 분명 전자를 이용해서 구현됐을 텐데, 내부 구현이 어찌 돼 있는지 난 아는 게 없다.
그리고 네트워크 트래픽이 컴퓨터의 I/O 병목엔 어떤 영향을 끼치는지, 그 패킷이 어떻게 해서 한 운영체제 내부의 특정 응용 프로그램으로 잘 전달이 되는지, DDoS 공격이 서버 컴퓨터에 어떤 물리적인 영향을 끼쳐서 서버를 뻗게 할 수 있는지, (아님 단순히 프로세스/스레드의 무한 스폰으로 인해 소프트웨어적인 자원 고갈만으로 뻗는 건가?)

HTTP 프로토콜에서 파일 업로드는 어떤 절차를 거쳐서 되는지, 방화벽이라는 게 정확히 뭘 하는 물건인지,
왜 구닥다리 윈도 2000/XP sp0을 띄운 채로 랜선을 꽂으면 뭐가 뚫려서 어떻게 되는지..
요즘은 네트워크 패킷은 하부 계층에서 압축이나 암호화를 좀 하는 게 있는지 등등..

이런 것들은 난 하나도 모..른..다. 저런 거 하나도 몰라도 <날개셋> 한글 입력기 개발하는 덴 아무 지장이 없기 때문이다.
그렇다고 해서 내가 컴퓨터 명령어 체계나 운영체제/소프트웨어 자체의 내부 구조나 보안에 대해 전혀 모르느냐 하면 그것도 물론 아니니.. 지식의 분포가 좀 불균형하다면 불균형한 셈이다.

본인은 초딩 중고학년 때 개인용 PC, 중학교 때 모뎀과 PC 통신, 고등학교 때 인터넷과 이메일, 그리고 대학교 때 무선 인터넷과 휴대전화의 순으로 문명의 이기들을 접했다. 랜 선이라는 걸 태어나서 처음으로 구경한 게 고등학교 때부터인데, 그 기간 동안 언젠가 집도 인터넷 접속 방식이 전화 모뎀에서 전용선으로 바뀌었다. 그때가 한창 전국적으로 인터넷 전용선이 깔리던 시절이었으니까.

지금까지 통신 기술은 정말 눈부신 속도로 발전했다.
신문· 방송에서 기자의 이메일을 공개하는 게 대세가 된 게 1990년대 후반부터이다.
지금으로부터 10여 년 전엔 '블로그'라는 단어가 도전 골든벨의 마지막 문제의 답이었다는 게 믿어지시는가? (그것도 학생이 못 맞혔고 그 당시엔 내게도 생소했다)

옛날에는 인터넷 연결을 위해서도 PC 통신을 할 때처럼 먼저 전화를 걸어야 했다. 사용 시간 카운터가 올라가는 자그마한 인터넷 연결 창이 뜬 동안만 인터넷을 이용할 수 있었다.
또한, 모뎀과 마우스를 동시에 사용하려면 두 물건을 서로 COM 포트가 충돌하지 않게 배치를 해야 했다.
Windows 3.x에서는 운영체제 차원의 네트워크 지원이 전무하기 때문에 트럼펫 Winsock인지 뭔지 하는 걸 먼저 설치해야 했다.

이 모든 것들이 지금은 까마득한 옛날 이야기가 됐다.
지금은 뭔가 그렇게 상태를 확인하면서 인터넷을 해야 하는 상황은 스마트폰 태더링으로 무선 인터넷을 쓸 때 정도이고 이것도 제약, 압박감, 부담 같은 건 옛날과는 비교할 수 없이 작아졌다.
메인보드가 어떻게 공간 워프를 했는지, 요즘은 유선 랜과 무선 랜도 전부 내장되어 나온다. 따로 뭘 장착조차 할 필요 없이 바로 접속만 하면 된다.

오늘날 인터넷이라고 불리는 그 통신망은 OSI 레이어 계층 중 제3계층(네트워크 계층)을 차지하는 IP라는 프로토콜을 기반으로 동작한다. IPv4, IPv6 같은 주소 체계도 이 계층에서 규정하는 것이기 때문에 모든 인터넷 통신은 이 체계를 기반으로 구성되어 있다.

그리고 그 아래의 제4계층(전송 계층)에는 인터넷 프로토콜을 따르는 네트워크 패킷을 보내는 방식의 차이를 규정하는 프로토콜이 있는데, 크게 TCP와 UDP가 있다.
TCP는 보낸 패킷이 반드시 순서대로 도착한다는 것은 보장되지만, 보냈던 단위랄까 형태가 그대로 도착하지는 않는다.
aaa, bb, ccccc, ddd, e 이렇게 패킷을 보냈으면 받는 쪽은 aa, ab, bccc, cc, dd, d, e 뭐 이렇게 받을 수도 있고 다른 형태가 될 수도 있다. 조립은 받는 쪽에서 알아서 해야 한다.

UDP는 TCP와는 달리 보낸 패킷이 원래의 형태 그대로 간다는 보장은 되지만.. 일부 패킷이 전송 과정에서 누락될 수가 있다.
즉, 위의 경우 ddd가 누락돼서 aaa, bb, ccccc, e 이렇게 갈지도 모르지만.. 일단 간 놈은 원래 형태 그대로 간다. 패킷의 누락 여부 판단을 받는 쪽에서 알아서 해야 한다.
그래서 TCP는 일종의 스트림 지향적이며, UDP는 개개의 패킷이 모 아니면 도 형태로 가는 메시지 지향적이다.

형태도 보존되고 누락 현상도 없는 만능 프로토콜이 없는 이유야 뭐, 세상에 값도 싸고 성능도 좋은 물건은 존재하지 않기 때문인 것과 같은 맥락일 것이다.
그게 필요하면 UDP 같은 걸 기반으로 패킷 누락을 감지하고 재전송을 요청하는 로직을 응용 프로그램이 별도로 구현해 줘야 한다.

온라인 게임에서는 “기관총 난사 내지 캐릭터 이동 같은 것만 UDP이고 나머지는 다 TCP”라는 말 한 마디로 요약된다.
자주 발생하기 때문에 반응성이 중요하고 적당히 좀 씹혀도 상관 없는 것만 UDP이고.. 나머지 크리티컬한 것들은 다 TCP를 써야 한다는 뜻이다.
그러나 온라인 게임에서 발생하는 트래픽의 상당수, 대략 70% 가까이는 그래도 UDP 방식이라고 한다.

실시간으로 스트리밍되는 대용량 오디오/비디오 데이터도 자명한 이유로 인해 UDP 방식으로 전송된다.
이런 차이를 보면, TCP와 UDP의 관계는 사실상 무손실 압축과 손실 압축의 관계나 마찬가지인 것 같다.

TCP의 경우 응용 프로그램이 아니라 아래의 프로토콜 차원에서 패킷의 누락을 감지하여 누락이 있는 경우 재전송 요청을 한다.
그런데 모바일처럼 네트워크 환경이 원래 워낙 열악해서 패킷 손실이 굉장히 자주 발생하는 곳에서는
TCP 방식에서는 끝도 없이 재전송 요청을 하고 받은 데이터에 결함이 없는지 체크와 빠꾸만 반복하느라 응용 프로그램이 그 동안 멍하니 있어야만 하는 일이 발생한다고 한다.
즉, TCP가 구조적으로 오버헤드가 더 크니, 네트워크가 열악한 곳에서는 그런 동작을 감안해야 한다.

게임에서 그래픽 엔진, 물리 엔진, 동영상/캡처 엔진도 아니고 네트웍 엔진 미들웨어로 먹고 사는 분이 국내에 일단 한 분 계신다. 넷텐션의 대표이사인 배 현직 씨. 이 업계에서는 이미 유명인사이다.

난 네트워크 쪽 프로그래밍을 해 본 건 먼~ 옛날에 소켓 API 대충 뚝딱해서 오목을 만들고..
DirectPlay를 써서 스크래블 정도 보드 게임에다가 네트웍 플레이를 넣어 본 게 전부이다. 그래도 그것만으로도 굉장히 재미있는 경험이었다.
저수준에서 패킷 암호화, 각종 오류 처리 그런 건 모른다. DPlay는 나름 하드웨어 독립을 추구한 통신 API이긴 한데, 요즘은 모뎀이고 시리얼 케이블 그딴 건 다 없어졌으니 그런 추상화 계층이 필요가 없어지면서 자연스레 도태했다. 잘은 모르겠지만 제1계층(물리 계층)은 거의 획일화가 돼 버린 것 같다.

※ 여담. IPX는 어디로 갔는가?

스타크래프트에서 배틀넷 말고 그냥 LAN으로 친구들끼리 팀플을 할 때, 옛날에는 배틀넷 다음으로 위에서 둘째인 IPX를 으레 고르곤 했다. 그러나 어느 패치 때부터인가 맨 아래에 UDP가 추가되었으며 그걸 고르는 걸로 구조가 바뀌었다. IPX는 동작하지 않기 시작했다. 어찌 된 일일까?

IPX라는 프로토콜은 똑같이 이더넷 랜선으로 통신을 하지만, 오늘날의 인터넷과는 다른 방식으로 통신을 한다. 즉, IP와 대등한 제3계층에서 방식이 다른 프로토콜인 것이다. IPX는 옛날에 네트워크 솔루션으로 유명했던 노벨 사에서 개발했고 실제로 매우 널리 쓰이기도 했지만 오늘날은 IP에 밀려서 사라졌다.

Windows 95때까지만 해도 네트워크 구성요소들을 설치하고 나면 기본으로 깔리는 것은 IPX였다. TCP/IP 지원 기능은 운영체제 CD를 넣어서 별도로 설치해야 했다. 무슨 말이냐 하면, 오늘날 당연시되고 있는 이 컴퓨터의 IP 주소를 설정하는 기능이 Windows 95에는 기본으로 없었다는 뜻이다. (심지어 네트워크 기능이 설치된 컴에서도)

사용자 삽입 이미지

그 다음 1990년대 중후반, Windows 98부터는 인터넷의 중요성이 워낙 크게 부각되기 시작했으니 TCP/IP 지원도 같이 포함되었다.
여담이지만 Windows에서는 등록정보/속성을 나타내는 단축키가 R인 편인데, 95에서는 유독 저 대화상자에서만 R은 삭제이고, 등록정보는 P였다. 무척 불편했는데 이 역시 98부터는 다같이 R로 개선됐다.

하긴, 본인도 옛날부터.. Windows가 근거리 네트워크 차원에서 제공하는 컴퓨터 간의 폴더 공유 기능과, 웹브라우저로 띄우는 인터넷은 기술적으로 무슨 관계인가 궁금하긴 했다.
인터넷 열풍 앞에서 IPX는 점점 잉여로 전락했으며, Vista부터는 드디어 IPX 지원이 hlp 도움말만큼이나 짤렸다. 그래서 스타크래프트도 근거리 팀플에 인터넷 프로토콜을 사용하는 UDP 지원이 추가된 것이다.

Posted by 사무엘

2015/02/05 08:37 2015/02/05 08:37
, , , ,
Response
No Trackback , 10 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1058

1. 오픈소스

잘 알다시피 C/C++은 메모리 할당이나 문자열 등, 바이너리 차원에서 뭔가 언어나 구현체가 buliding block을 규정해 놓은 게 없다시피하며, 그나마 표준이 나온 것도 강력한 구속력을 갖고 있지는 못하다. 그러니 이 지저분함을 참다 못해서 COM 같은 바이너리 규격이 나오고 닷넷 같은 완전히 새로운 프레임워크도 나왔다.

아니면 일각에서는 소프트웨어 컴포넌트를 재배포할 때, 빌드된 라이브리러리를 주는 게 아니라 난독화 처리만 한 뒤 소스 코드를 통째로 넘겨주면서 빌드는 이 코드를 쓰는 쪽에서 자기 입맛대로 알아서 하라는 극단적인 조치를 취하기도 한다. 차라리 오픈소스 진영이 이런 점에서는 융통성이 더 있는 셈이다.
하지만 어지간한 컴덕력을 갖추지 못한 사람은.. 복잡한 빌드 시스템/configuration들을 이해할 수 없어서 소스 코드까지 통째로 줬는데도 줘도 못 먹는 촌극이 벌어진다.

이런 라이브러리 내지 유닛, 패키지는 기계어 코드로든 다른 바이트 코드로든 소스 코드가 바이너리 형태로 용이하게 재사용 가능한 형태로 가공되어 있는 파일이다.
그런데 실행문이 들어있는 소스 코드가 반드시 그대로 노출돼야만 하는 분야도 있다.

크게 두 갈래인데, 하나는 C++의 템플릿 라이브러리이고, 다른 하나는 웹 프로그래밍 언어 중에서도 전적으로 클라이언트 사이드에서 돌아가는 자바스크립트이다.
동작하는 환경 내지 타겟은 둘이 서로 완전히 극과 극으로 다르지만, 전자는 컴파일 때 최적화 스케일의 유연성 때문에, 그리고 후자는 선천적으로 기계 독립적이고 극도로 유연해야만 하는 웹의 특성상 오픈소스가 강제되어 있다.

자바스크립트는 비록 전통적인 기계어 EXE를 만드는 데 쓰이는 언어는 아니지만 그렇다고 해서 만만하게 볼 물건이 절대로 아니다. 람다, 클로저 등 어지간한 최신 프로그래밍 언어에 있는 기능은 다 있으며, 플래시에 하드웨어 가속 3D 그래픽까지 다 지원 가능한 경지에 도달한 지가 오래다.
또한 웹에서의 영향력이 워낙 막강하다 보니 전세계의 소프트웨어 업체들이 눈에 불을 켜고 실행 성능을 필사적으로 끌어올려 놓았다. 비록 컴파일을 통한 보안 유지는 안 되지만, 어느 수준 이상의 코드 난독화 기능도 당연히 있다.

뭐, C++ 표준 템플릿 라이브러리도 헤더 파일을 열어 보면, 남이 못 알아보게 하려고 코드를 일부러 저렇게 짰나 싶은 생각이 든다. 온갖 주석이 곁들여져서 알아보기 쉽게 널널하게 작성된 C 라이브러리의 소스들과는 형태가 달라도 너무 다르다..

C++ 템플릿에 대해서 한 마디 더 첨언하자면.. 제한적으로나마 함수나 몸체를 일일이 인클루드해서 노출하지 않아도 되는 방법이 있긴 하다.
몸체를 한 cpp(= 번역 단위)에다가만 구현해 놓은 뒤, 거기에다가 소스 코드 전체를 통틀어 그 템플릿이 인자가 주어져서 쓰이는 모든 형태를 명시만 해 주면 된다.

template Sometype<char>;
template Sometype<wchar_t>;

템플릿 함수에 대해서 template<> 이렇게 시작하는 특정 타입 전용 케이스를 만드는 것과 비슷해 보이는데..
위와 같은 식으로 써 주면, 해당 코드가 컴파일될 때 이 템플릿이 저런 인자로 실현되었을 때의 대응 코드가 모두 생성되고, 이게 다른 오브젝트 파일들이 링크될 때 같이 연결되게 된다. 이런 문법이 있다는 것을 15년 동안 C++ 프로그래밍을 하면서 처음 알았다.

물론 저것 말고 다른 임의의 새로운 타입으로 템플릿을 사용하고 싶다면 그렇게 템플릿을 사용하는 번역 단위에서 또 다시 템플릿의 선언부와 몸체를 싹 읽어들여서 분석을 해야 한다.
아마 과거의 export 키워드가.. 저런 템플릿 인자의 사용 형태를 자동으로 파악하는 걸 의도하지 않았나 싶은데 그래도 세상에 쉬운 일이란 없었던 듯하다.

2. 웹 프로그래밍의 성격

HTML, CSS, 자바스크립트 삼신기는 마치 웹 프로그래밍계에서의 삼권 분립이기라도 한 것 같다. 아무래도 당장 화면에 표시되는 핵심 컨텐츠가 HTML이니 요게 행정부에 대응하는 듯하며, HTML을 표시할 규격을 정하는 CSS는 사법부에 가깝다. 끝으로, 인터랙티브한 동작을 결정하는 자바스크립트는 입법부 정도?
물론 HTM 파일 하나에다가 스타일과 자바스크립트 코드를 다 우겨 넣었다면 그건 뭐 “짐이 곧 국가다, 법이다” 식으로 코드를 작성한 형태일 것이다.

예로부터 본인이 느끼기에 웹 프로그래밍은 뭔가 시대의 최첨단을 달리는 것 같고 간지와 뽀대가 나고 실행 결과가 사용자에게 가장 직접적으로 드러나 보이는 신기한 영역인 것 같았다. 하지만 (1) 코드와 데이터, 클라이언트와 서버, 코딩과 디자인의 역할 구분이 영 모호하며, 컴퓨터의 성능을 100% 뽑아내는 듯한 전문적이고 하드코어한 느낌이 안 들어서 마음에 안 들었다. 가령, 도대체 어디서는 java이고 어디서는 jsp이고 어디서는 js인지?

(2) 또한 이 바닥은 작성한 소스 코드가 제대로 보호되지 못한다. 서버 사이드에서만 돌아가는 PHP 같은 건 클라이언트에게는 노출이 안 되겠지만 그것도 서버 개발자들끼리는 결국 오픈소스 형태로 공유될 수밖에 없으니 말이다. 옛날에 제로보드의 소스가 그랬듯이.

끝으로, (3) 특정 CPU 아키텍처나 플랫폼에 구애되는 게 없다 보니 기반이 너무 붕 뜨는 느낌이고, 브라우저마다 기능이 제각각으로 달라지는 거 호환 맞추는 노가다가 필요한 것도 싫었다.
뭐, IE와 넷스케이프가 경쟁하고 IE6이 세계를 사실상 평정했던 먼 옛날에는 그랬고 지금은 이 문제는 많이 해소됐다. 바야흐로 2015년, HTML5 표준안까지 다 완성된 지경이니, 웹 프로그래밍도 이제 충분히 성숙했고 기반이 탄탄히 잡혔다. 격세지감이다. ActiveX도 점점 퇴출되는 중이다.

2004년에 IE6에 대한 대항마로 파이어폭스 0.8이 혜성처럼 등장했고, 2008년엔 구글 크롬이 속도 하나로 세계를 평정해서 IE의 독점 체계를 완전히 견제해 냈다. 지금은 크롬이 속도는 괜찮은 반면, 메모리 사용량이 너무할 정도로 많아서 파이어폭스가 다시 반사 이득을 보는 구도이다. 오페라는 Windows에서는 영 좀 마이너한 콩라인 브라우저가 아닌가 모르겠다.
그리고 무슨 브라우저든지 버전업 숫자 증가폭이 굉장히 커졌으며, 탭 브라우징에  메뉴와 제목 표시줄을 숨겨 놓는 인터페이스가 필수 유행이 돼 있다.

3. 보안 문제

세월이 흐르면서 웹 프로그래밍 환경이 좋아지고 있는 건 사실이지만, 보안 때문에 예전엔 바로 할 수 있었던 일을 지금은 못 하고 뭘 허가를 얻고 번거로운 절차를 거쳐야 하는 건 다소 불편한 점이다.
특히 내가 느끼는 게 뭐냐 하면, 한 HTML 파일에서 자신과 다른 도메인에 있는 CSS나 JS 같은 걸 덥석 인클루드 하는 걸 브라우저가 굉장히 싫어하게 됐다는 점이다. 이런 걸 이용한 보안 취약점 공격이 지금까지 많았는가 보다.

"이 사이트에는 안전한 컨텐츠와 위험한 컨텐츠가 같이 섞여 있습니다. 위험한 것도 모두 표시하시겠습니까?"라는 메시지가 바로 이런 상황에서 뜬다.
IE의 경우 예전에 잘 표시되던 사이트가 갑자기 표시되지 않을 때, 권한 취득을 위해 레지스트리에다 자기 프로그램 이름이나 사이트를 등록하는 등 조치를 취해야 했다.
구글 크롬은 발생 조건이 IE와 동일하지는 않지만, 자체 판단하기에 악성 코드의 실행을 유도하는 걸로 의심되는 지시문이 HTML 소스에 있는 경우, 화면 전체가 위험 경고 질문 화면으로 바뀐다.

최근에는 크롬과 IE에서는 멀쩡하게 보이는 웹 페이지가 파이어폭스에서만 제대로 표시되지 않는 문제가 있어서 회사 업무 차원에서 사이트 디버깅을 한 적이 있었다. 요즘 세상이 무슨 세상인데 웹 표준이나 렌더링 엔진의 버그 때문일 리는 없고, 파이어폭스가 자바스크립트 엔진으로 하여금 외부 도메인로부터 인클루드된 CSS 속성에 접근하는 걸 허용하지 않아서 발생한 문제였다.

4. 파일 관리가 되는 게시판

본인도 여느 프로그래머와 마찬가지로 다니는 회사에서 요즘 모바일에 웹까지 별별 걸 다 손대며 지냈다. 하긴, 공학 박사라 해도 취업 후에는 돈 되는 분야, 뜨는 분야를 따라 자기 주전공 연구 분야가 아닌 것도 손대 봐야 할 텐데 하물며 그보다 급이 낮은 단순 엔지니어들은 말이 필요하지 않을 것이다.

요즘은 게시판이나 블로그 엔진을 만들려면 단순무식한 텍스트 기본 폼이 아니라 위지윅 웹 에디터가 필수이다. ckeditor 컴포넌트에다가 이미지 업로드 기능을 연결해 넣을 일이 있었는데 이것도 여간 골치아픈 일이 아니라는 걸 작업을 하면 할수록 깨닫게 됐다.
손이 정말 많이 간다. 하지만 그걸 일일이 하지 않으면 이미지는 단순 외부 링크밖에 못 넣는 반쪽짜리가 된다.

이미지 파일이 하나 HTTP 규격대로 업로드되어 왔으면 서버 측에서는(PHP든 JSP든 무엇이든) 파일 크기가 적당한지(개별 파일 크기와 지금까지 업로드된 파일의 전체 크기 모두) 체크하여 적당하다면 이름을 중복 없는 랜덤 이름으로 바꿔서 서버에 저장한다. 이름에 한글이 들어간 파일이라고 업로드나 로딩이 제대로 안 되는 일이 없어야 하니까.

그 뒤에 그 그림을 불러올 수 있는 URL을 에디터 컴포넌트에다가 알려 준다. 이것도 간단하게 만들자면 그냥 서버의 특정 디렉터리를 그대로 노출하는 식으로 만들면 되겠지만 보안상 위험하니 가능한 한 제3의 장소에서 파일을 되돌리는 서버 프로그램 URL을 주는 게 안전하다.

위지윅 에디터에서는 임의의 개수의 파일이 업로드될 수 있기 때문에 글에 얽힌 첨부 파일들을 따로 디렉터리나 DB 형태로 관리해서 글이 삭제될 때 같이 지워지게 해야 한다.
사실, 이쪽으로 조금만 더 신경 쓰면 글별로 아예 첨부 파일 관리자라도 간단한 형태로 만들어야 하게 된다. 우와..;;

그리고 골때리는 건, 아직 작성 중이고 정식으로 등록하기 전의 임시 상태인 글에 첨부된 그림들을 처리하는 방식이다.
일단은 그림들이 임시 폴더에다가 올라가고 주소도 임시 폴더 기준이지만 글이 정식으로 등록됐다면 글 중에 삽입된 이미지들의 주소를 수동으로 바꿔야 하고 파일도 옮겨야 한다.
또한 그 상태로 글이 더 등록되지 않고 사용자가 back을 눌렀다면, 서버에 올라왔던 임시 파일들도 나중에 지워 줘야 한다. 이런 것까지 도대체 어떻게 다 구현하지?

이건 일게 위지윅 에디터 컴포넌트가 감당할 수 있는 수준이 아니기 때문에 그걸 블로그 엔진이나 게시판에다 붙여 쓰는 웹 프로그래머가 자기 서버의 사정에 맞게 세팅을 해야 한다.
겨우 이미지 업로드 기능 하나만 달랑 구현하는 테크닉을 소개한 블로그만으로는 정보가 너무 부족했다.
Windows에서 공용 컨트롤에다 드래그 드롭을 처음부터 직접 구현하는 것만큼이나 손이 많이 갔다. 나 같은 이 바닥 초짜로서는 그저 경악스러울 뿐.

프로그램의 완성도를 더 높이려면, 사용자가 곱게 이미지 파일만 올리는 게 아니라 php나 html 같은 보안상 위험한 파일을 올리는 건 아닌지 감시해야 한다. 첨부 파일 정도가 아니라 위지윅 웹 에디터 자체도 위험하다고 그런다. HTML이 근본적으로 문서와 코드가 뒤섞인 형태이다 보니 정말 매크로가 잔뜩 든 Office 문서처럼 취급되는가 보다.
아무튼, 나모 웹에디터와 제로보드가 뜨던 시절에 비해 요즘 웹은 너무 방대하고 복잡하다.

Posted by 사무엘

2015/02/02 08:39 2015/02/02 08:39
, , , ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1057

요런 개념을 표로 일목요연하게 한 번쯤 정리할 필요를 예전부터 느꼈던지라, 잠시 짬을 내어 만들었다. C++ 프로그래머라면 고개가 절로 끄덕여질 내용 되겠다. 

토큰 type 앞 type 뒤 value 앞 양 value 사이 value 뒤
&   참조자형 명시 address-of (L-value만) 비트 AND  
&&   R-value 참조자형 명시   논리 AND  
*   포인터형 명시 배열 및 포인터 역참조 곱셈  
±     양/음 부호 덧셈/뺄셈  
괄호() (1) 형변환(typecast)
(2) 타입 선언자 나열 순서 조절
함수형 명시 연산 순서 조절   함수 호출
대괄호[]   배열형 명시     배열 참조
부등호<>   템플릿 인자 명시   비교 템플릿 함수 인자
콤마,       (1) 콤마 연산
(2) 함수 호출/템플릿 인자 구분
(3) 변수 선언 구분
 

  • 괄호는 영어에서 접속사도 되고 지시형용사/지시대명사, 관계대명사까지 다 되는 that만큼이나 정말 다재다능한 물건이다.
  • 베이직은 이례적으로 =, ()가 쓰임이 중첩되어 있다. =가 대입과 동등 연산을 모두 담당하며, ()가 함수 호출과 배열 첨자를 모두 담당한다. 함수 호출은 문법적으로 매우 제한된 문맥에서만 허용되니, C/C++같은 함수 포인터가 존재하지 않는다면 () 중첩이 아주 불가능하지는 않은 듯하다.
  • C/C++은 @ $ ` 기호를 전혀 사용하지 않는다. 예전에 베이직은 각종 기괴한 기호들을 이용하여 변수의 자료형을 표현하곤 했다. A$는 문자열, A%는 정수, A#은 실수 같은 식이다.
  • 파스칼은 포인터형을 선언하는 토큰이 ^인데, C/C++와는 달리, 포인터형을 나타낼 때와 포인터를 역참조할 때 토큰이 등장하는 위치가 서로 다르다. ^Type 그리고 Value^ 이런 식.
  • int *a와 int a[5]배열에 대해서는 똑같이 *a를 쓸 수 있지만, 잘 알다시피 배열의 역참조와 포인터의 역참조는 개념적으로 다르다. C/C++을 처음 공부하는 초보자가 굉장히 혼동할 수도 있을 듯하다.
  • 포인터는 다중 포인터가 존재할 수 있고 역참조도 여러 단계를 연달아 할 수 있다. 그러니 *가 여러 개 연달아 올 수 있다. 그 반면, 참조자는 구조적으로 딱 한 번만 참조/역참조가 가능하게 만들어진 포인터의 축소판이다. 그렇기 때문에 &&에다가 별도로 R-value 참조자라는 물건을 집어넣을 수도 있다. 이걸 생각해 낸 고안자는 정말 천재다.
  • 일반적으로 &는 address-of 연산자이며 R-value를 상대로는 적용이 되지 않는다.
    그러나 일부 값은 L-value가 아님에도 불구하고 & 연산자의 적용이 가능하며, 심지어 a와 &a가 동일하게 취급되는 것도 있는데, 바로 static 배열과 일반 함수이다.
    기본적으로 포인터의 성격을 갖추고 있는지라 &를 안 해도 기본적으로 자신의 주소가 되돌아오고, &를 붙여도 무방하다는 오묘한 특징이 있다.
  • 한편, C/C++에서 배열은 고유한 자료형임에도 불구하고 함수의 인자로 전달되거나 리턴값이 될 때는 그냥 포인터든 배열의 포인터든 포인터의 형태로만 전달된다. 배열 그 자체가 전달되지는 못한다.
    배열을 생으로 함수를 통해 주고 받으려면 구조체에다가 배열을 집어넣어야 한다.

Posted by 사무엘

2015/01/02 19:39 2015/01/02 19:39
,
Response
No Trackback , 4 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1046

« Previous : 1 : ... 11 : 12 : 13 : 14 : 15 : 16 : 17 : 18 : 19 : ... 23 : 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:
2677610
Today:
2178
Yesterday:
2124