요즘은 거의 찾을 수 없는 관행인데, 옛날에 메뉴가 달린 Windows용 프로그램 중에는 파일, 편집 같은 다른 메뉴는 왼쪽에 있는 반면 '도움말' 같은 마지막 메뉴 하나만은 맨 오른쪽에 따로 떨어져서 배치된 것들이 종종 있었다. 그게 유행이었다. 특히 16비트 Windows 3.x 시절에 말이다.

사용자 삽입 이미지

음성학 연구용으로 많이 사용되는 사운드 편집기 프로그램인 프라트(Praat)는 최신 버전까지도 그런 형태라는 게 흥미로웠다. 단어 배치로 치면 단순히 space가 아니라 왼쪽 정렬 탭과 오른쪽 정렬 탭으로 구분된 셈이다.

사족을 덧붙이자면, 얘는 도움말 메뉴뿐만이 아니라 프로그램 외형이 전반적으로 좀 범상치 않아서 혹시 qt나 자바 같은 범용 프레임워크로 GUI를 만들었나 하는 생각이 들었다. 허나 Spy++로 들여다보니 그렇지는 않아 보인다. 다만 컴파일러는 Visual C++이 아니라 gcc 계열을 써서 빌드 됐더라. 그리고 얘 자체가 크로스 플랫폼 프로그램이기도 하고 도움말까지 자체 구현인 걸 보면, 독자 개발한 자체 GUI 라이브러리 자체는 사용한 것으로 보인다. (그리고 이 정도 엄청난 프로그램이 무료 공개라는 게 참 대단하다!)

그나저나, 오른쪽에 따로 떨어진 메뉴 아이템은 어떻게 구현된 걸까?
본인은 아무 근거 없이 정말 막연하게.. 왼쪽의 맨 마지막 아이템과 오른쪽으로 밀려난 첫 아이템 사이에 "아마 separator 아이템이 있는 게 아닐까?"라고 오랫동안 생각했다. 하지만 실제로 이걸 넣어 보니, 아이템과 아이템 사이에 공간만 더 생길 뿐 정렬 방식이 달라지지는 않았다.

이걸 구현한 방식은 허무할 정도로 간단하다. 바로 체크/disabled 등을 나타내는 그 상태 플래그에 MFT_RIGHTJUSTIFY라는 정보도 같이 들어있다. 즉, 플래그에는 자신의 상태도 들어있고 속성도 같이 들어있는 것이다. 그건 뭐 윈도우 스타일도 마찬가지이지만.

여러 메뉴 아이템에 그 스타일이 있으면, 스타일이 등장하는 첫 아이템부터 나머지 메뉴 아이템들은 죄다 오른쪽에 정렬되어 나온다. 가로로 배치된 메뉴 말고 세로로 배치된 메뉴 내지 우클릭 팝업 메뉴 같은 데서는 이 플래그는 아무 기능도 하지 않는 잉여이다. 그걸로 끝이다.
마치 콤보 박스의 extended UI 스타일만큼이나 자주 보기 쉽지 않은 UI이다 보니, 더 특별한 방법이 있는가 싶었는데 다소 실망스럽기까지 했다.

예전에 메뉴에 대해서 한번 글을 쓴 적이 있었는데 그 당시에는 오른쪽 정렬 메뉴 아이템에 대해서는 미처 생각을 못 하고 있었다. 그러니 이번에는 메뉴와 관련해서 non-client 영역 얘기나 좀 더 하고 글을 맺겠다.
메뉴가 표시되는 영역다 응용 프로그램이 자체적으로 뭔가 출력을 하는 예로 옛날에 Freecell 게임이 있었다. 남은 카드의 수가 메뉴의 오른쪽 끝에 나타났기 때문이다.

사용자 삽입 이미지

프리셀은 유사품인 카드놀이(solitaire)와는 달리, 처음부터 32비트 코드 기반으로 개발되어 옛날에 Win32s와 함께 제공되기도 했던 역사적인 유물이다.

그런데 Windows XP로 오면서 살짝 옥에티가 생겼다.
XP의 기본 luna 테마에서는 가로로 배치된 메뉴 표시줄은 회색이다. 하지만 펼쳐진 메뉴 창은 흰색이다. 고전 테마 때는 이런 일이 없었는데 역사상 처음으로 메뉴 표시줄의 배경색과 메뉴 창의 배경색이 서로 달라진 것이다.
허나 프리셀은 "남은 카드 수"를 메뉴 창의 배경색으로 출력하는지 옅은 회색 배경에 흰색 배경으로 글자가 찍혀서 뭔가 이질감이 생겨 있다.

그래서 Vista인가 7부터 프리셀은 화면 하단에 상태 표시줄이 별도로 추가되었고, 남은 카드 수는 거기에다 출력하게 동작이 바뀌었다.

그림을 그리라고 운영체제가 보장을 해 준 클라이언트 영역 말고, 창의 프레임이나 제목 표시줄, 메뉴 표시줄 등은 논클라이언트(non-client) 영역으로 분류된다.
여기는 일단은 운영체제가 알아서 모든 처리를 해 준다. 그릴 일이 있을 때 WM_NCPAINT라는 메시지를 날려 주기는 하지만, 어지간해서는 응용 프로그램이 그걸 건드리지는 않는 게 좋다.

전에도 한번 말했듯이 MS Office는 95 시절에 캡션 바(제목 표시줄)를 독자적으로 그러데이션을 입혀서 그리곤 했다. 이건 1회 유행으로 끝났고 그러데이션은 나중에 Windows 98에서 완전히 전체적으로 적용되었다.
더 옛날 16비트 시절에 시스템 차원의 훅킹이 더 쉽던 시절엔 모든 창의 캡션에 응용 프로그램이 자신의 기능을 수행하는 버튼 같은 것도 막 집어넣기도 했던 것 같다.

허나, 그 동작을 어설프게 가로채면, Windows가 버전업 되어서 논클라이언트 영역의 비주얼이 또 바뀌었는데 응용 프로그램은 동기화가 안 되어서 외형이 이상해지고 프로그램이 오동작 할 수가 있게 된다.
일례로, 과거에 아래아한글 97은 논클라이언트까지 완전히 독자적으로 GUI를 그리던 대표적인 프로그램이다. 그런데 Windows XP 테마에서는 윈도우의 논클라이언트 가장자리에 둥그런 모서리 region이 적용되었다.
하지만 아래아한글 97은 기본 region은 딱히 건드리지 않고 그림만 직사각형 region을 기준으로 곧이곧대로 그렸기 때문에 가장자리가 짤려서 대화상자에 약간 glitch가 있었던 것이다.

하지만 Windows는 맥 OS처럼 GUI가 선택의 여지가 없이 독재 획일화인 운영체제는 아닌지라 당장 MS 자신들부터가 Office나 Visual Studio 같은 중요한 밥줄 프로그램들은 운영체제의 표준 GUI 따위는 전혀 안 쓴다.
특히 리본 UI는 논클라이언트를 완전히 제멋대로 재정의해서 쓴다. 논클라이언트 영역에 적용되는 Aero 투명 효과까지 세밀하게 제어하면서 말이다. 지금은 Aero가 폐기돼서 별 의미는 없어졌지만 말이다.

MS 같은 GUI 잉여짓을 할 여력이 없는 프로그래머라면 WM_NCPAINT를 다룰 일은 별로 없겠지만, custom 컨트롤을 만드는 경우라면 불가피하게 이 메시지를 직접 처리해야 하게 된다.
공용 컨트롤 6 매니페스트가 적용되었더라도 윈도우의 테두리는 그냥 가만히 놔 두면 새끈한 테마 형태가 아니라 옛날의 밋밋한 기본 스타일로 그려지기 때문이다. OpenThemeData와 DrawThemeBackgroundEx 같은 함수로 수동으로 그려야 한다.

이런 부류의 글은 결론이 언제나 동일하다. Windows 프로그래밍은 재미있더라.

Posted by 사무엘

2015/05/16 08:25 2015/05/16 08:25
, ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1094

※ 컴퓨터 & 프로그래밍

1.
예전에 본인은 시스템 종료 중에라도 사용자가 무슨 동작을 취하면, 컴을 아주 꺼 버리는 시스템 종료가 아니라 그 뒤 '재시작'으로 종료 모드를 바꾸는 기능이 있으면 좋겠다는 제안을 한 적이 있다. 그것과 비슷한 제안인지도 모르겠는데, 또 하나 아이디어를 내자면 이렇다. 사용자가 한동안 컴퓨터를 건드리지 않아서 모니터가 꺼지거나 컴퓨터가 절전· 최대 절전· 종료 등으로 바뀌게 되면, 그 모드로 진입하기 전에 화면에 10초나 5초 정도 카운트다운을 좀 띄웠으면 좋겠다.

프레젠테이션을 할 때처럼 화면을 빤히 보고 있으면서 키보드· 마우스만 안 건드리고 있는데 화면이 갑자기 꺼져 버려서 당황한 적이 여러 번 있었다. 화면 보호기 정도는 카운트다운 없이 바로 진입해도 상관 없겠지만 아예 하드웨어적인 변동이 생기는 저런 모드는 예고가 있으면 좋겠다.

2.
동영상 엔진인 '코덱'과 과거의 컴퓨터 통신 장비인 '모뎀'이 정확히 같은 조어법에 의해 거의 같은 구조의 이니셜을 가진 단어이구나.

3.
식당에서 주문을 한 뒤에야 "아 손님, 죄송하지만 재료가 떨어져서 그 메뉴는 지금 제공이 안 됩니다" 이런 메시지를 받으면 허탈하잖아. 애초에 메뉴판에 그런 메뉴는 disable된 상태로 시각 피드백이 있으면 좋겠다.

4.
공동 작업을 하는 코드의 명칭에 영어 스펠링이 틀린 게 많아서 작업에 지장을 적지 않게 받은 적이 있었다. 검색이 안 되기 때문이다. 이쯤에서 분명 availableItem이런 단어가 있는 걸 봤었는데 나중에 보니 avalible이라고 돼 있는 식.
이건 당장 버그나 성능 같은 동작과 직접적인 관련이 있지는 않지만, 그래도 또 다른 형태의 민폐이다. 도서관으로 치면, 책을 보고 나서는 자기 분류 코드상으로 있어야 할 곳이 아닌 엉뚱한 곳에다 책을 꽂은 것과 같다. "잘못 꽂힌 책은 없는 책과 같습니다. 정리는 사서가 알아서 할 테니까 열람하신 책은 그냥 여기에 놔 두세요" ;;;;

5.
관광 가이드를 매뉴얼과 스케줄 대로 승객들을 안내하는 컴퓨터 프로그램에다가 비유한다면, 이 사람이 수행하는 프로그램의 소스 코드는 정말 그야말로 try ... catch문으로 빽빽이 무장하고 있어야겠구나 하는 생각이 들었다.
누군가가 갑자기 아플 때, 뭔 물건을 놔 두고 왔을 때, 여권을 잃어버렸을 때, 긴급한 사고가 발생했을 때, 일행 중 일부가 없어져서 못 찾을 때 등등.. 그 어떤 예외 상황에서도 패닉과 스케줄 펑크를 최소화하는 방향으로 의연히 대처가 가능해야겠다.

6.
Windows 환경에서 응용 프로그램이 자기 영역으로 사용할 수 있는 메모리 주소는 64KB 이상부터이다. NULL 포인터인 0자체뿐만이 아니라 첫 64KB는 가상 메모리 영역 설계 차원에서 봉인되어 있으며, 이 주소에 메모리를 읽거나 쓰는 건 무조건 에러가 난다. 사실, 0 자체뿐만 아니라 64KB 정도까지는 막혀 있어야 NULL포인터 자체뿐만 아니라 NULL로부터 구조체 멤버를 참조한 포인터도 에러로 처리될 수 있을 것이다. ((POINT *)NULL)->y처럼.

아울러, 과거의 Windows 9x는 이보다 제약이 더 커서 64KB가 아니라 상위 4MB까지가 추가로 막혀 있었다. 64K부터 4M까지의 영역은 16비트 프로그램(도스용 & Windows용 모두)이 사용한다. (☞ 이에 대한 더 자세한 설명)

이런 이유로 인해 전통적으로 32비트 Windows 프로그램들은 시작 주소(preferred base)가 딱 4MB로 맞춰지곤 했다. NT 계열에서는 꼭 4MB가 아니라 64KB 이상 아무 지점이어도 상관이 없지만, 4MB 이상이어야 윈도 9x와 NT계열에서 모두 실행 가능하기 때문이다.

그런데 이건 오늘날까지도 하드디스크가 C로 시작하는 디스크 드라이브 관행과도 정확히 일치하는 것 같다.
플로피 디스크가 완전히 없어졌음에도 불구하고 A, B 드라이브는 사실상 결번으로 남아 있으니 말이다. 요즘은 하다못해 USB 메모리 드라이브를 거기에다 할당해도 될 것 같은데!

※ 알고리즘

7.
longest common subsequence를 구하는 문제와 longest increasing subsequence를 구하는 문제는 서로 관련이 있는 무척 흥미로운 문제인 것 같다.
가만히 생각해 보니, 후자는 임의의 sequence와, 그 입력을 오름차순으로 정렬한 sequence와의 longest common subsequence를 구하는 것과 같다. 그러므로 후자는 전자 문제로 다항 시간 만에 변환 가능한 special case이다.

두 문제는 일단 다이나믹 프로그래밍으로 O(n^2)의 복잡도로 풀 수 있지만, 더 작고 특수한 케이스인 후자는 O(n log n)의 해법도 있다.
전자 문제는 문장의 정확도를 구하는 알고리즘, 소스 코드의 diff 툴 등 활용되는 분야가 굉장히 많다. 지금은 어떤가 모르겠는데 내 때에는 국제 정보 올림피아드의 첫째 날 1번 문제가 해법이 이 형태로 귀착되는 경우도 종종 있었다. 1999년도의 꽃병 문제는 대놓고 저런 타입이었고, 2000년도의 palindrome 문제도 자신과 자신을 역순으로 뒤집은 단어와의 longest common subsequence를 구하는 것과 동일하다.

8.
엑셀에서 파이 모양 차트를 그리면 아이템별로 파랑, 빨강, 주황 등 알록달록한 색깔이 배당되어 차트가 그려진다.
그런데 최초의 색깔인 파랑부터 아이템 N에 이르기까지, 색깔을 선별하는 방식이 과연 무엇일까?
Office 2003까지는 뭔가 보라색 위주의 우중충하고 칙칙한 색깔 위주였는데 2007부터는 그래도 예전보다 훨씬 더 세련되게 바뀌었다.

이건 뭔가 RGB나 hue 같은 색공간에서 최대한 균등하게, 마치 흑에서 백으로 디더링 픽셀을 하나씩 채워 나가듯이 색깔을 뽑아낸 것 같다(관련 링크). 그 구체적인 알고리즘이 궁금하다.
그리고, 이런 픽셀 채우기 문제의 domain을 2차원 평면이 아니라 3차원 공간으로 확장하면 문제의 난이도가 어찌 되는지도 궁금하다.

※ 자동차

9.
자동차 차량 취급 설명서의 각종 선택사양에만 적용되는 설명들은 C/C++ 코드에서 #if #endif 전처리기에 대한 아주 좋은 예시라 여겨진다.

10.
오늘날 "일찍 나는 새가 벌레를 잡는다"보다 훨씬 더 현실적으로 와 닿는 말은 "일찍 움직이는 차가 주차 자리를 차지한다"라고 해도 과언이 아닐 것이다.

※ 기타 미분류

11.
공항 안에 개인 물품 보관함 같은 게 있으면 단독 여행 시에 유용하겠다는 생각이 든다. 이곳과 계절이 크게 다른 지역을 여행 갈 때 지금 입은 옷을 보관해 놓는다거나, 반입 금지 내지 무게 제한에 걸린 물건을 귀국 때까지 임시로 보관할 수 있게 말이다. 물론 후자의 경우는 당사자가 보관함까지 갔다가 돌아오는 게 곤란하니, 추가 비용을 부담해서 보관 대행을 맡길 수 있어야 하겠다.

12.
비행기와 열차의 큰 차이:
열차는 출발 15분 전부터 승강장으로 입장이 가능한 반면, 비행기는 출발 15분 전에 탑승이 종료된다는 것이다.
그리고 여담인데, 내 경험상 인천 공항을 출발한 비행기는 견인차에 끌려 터미널을 떠난 순간부터 활주로에 진입하여 이륙을 시작할 때까지도 거의 정확히 15분이 소요된다.

13.
"바탕체 레귤러"라는 서체 이름을 보고는 바탕체 볼드가 아니라
"바탕체 라지"가 순간적으로 먼저 떠올랐다.
요즘 커피를 너무 많이 마셨나 보다....? =_=;;
하긴, 아메리카노가 생각이 안 나서 순간 "아프리카노요"라고 주문을 했다는 사람 얘기도 있으니..;;

14.
몇 년 전부터 우리나라에서는 우측통행, 도로명 주소 등 일상생활과 직접적인 관계가 있는 여러 규범이 바뀌었으며, 이런 차원에서 단위도 비표준 단위가 통상적으로 쓰이던 곳까지 SI 단위가 강제 추진되었다.
고기의 무게는 오래 전부터 '근'이 거의 전멸하고 100그램 단위로 다 정착을 한 것 같지만 여전히 오락가락하는 곳은 부동산에서 다루는 건물이나 땅의 면적이다.

그런데 내가 보기에도 '1평'을 '3.3제곱미터'로 바꿔서 실생활에서 유리한 게 없다. 부자연스러울 뿐만 아니라 음절수도 너무 많아서 발음하기가 불편하다. 바꿀 거면 사람이 실제로 생각하는 넓이의 덩어리도 1제곱미터나 10제곱미터 단위로 업데이트가 돼야 할 텐데.
참, 그나저나 화면의 크기를 표기할 때 으레 쓰이는 '인치'는 센티미터로 바뀌기라도 했는지 궁금하다. 여기도 평이나 근 만만찮게 좀 이상한 단위가 관습적으로 쓰여 온 곳이니까 말이다.

Posted by 사무엘

2015/04/19 08:36 2015/04/19 08:36
, , , , ,
Response
No Trackback , 7 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1084

C/C++ 같은 지극히 static한 컴파일 지향 언어에서는 상상도 못 할 일이겠지만, 아주 다이나믹하고 인터프리터를 지향하는 프로그래밍 언어들은 문자열에 담긴 자기 언어 코드를 지금의 변수/함수 context를 기준으로 실행해 주는... 엄청난 기능을 제공하기도 한다.

PHP와 자바스크립트 모두 eval이라는 함수가 이 일을 한다. 루비던가 펄이던가 문자열의 동적 처리가 강한 다른 언어에도 응당 동일 기능이 있다.
문자열 변수에 들어있는 값(가령 "abc")을 통해서 해당 이름의 변수에 접근하는 것(abc)도 가능하다. C/C++에서는 지역 변수는 그냥 함수 스택 프레임으로부터 고정된 오프셋 숫자를 나타내겠지만, 저런 언어에서 지역 변수는 힙 기반의 해시나 트리를 참조하는 유동적인 이름-값 key 명칭에 해당할 것이다.

그리고 또 다르게 비유하자면, static type 언어는 테이블의 각 필드별로 타입과 정보량이 매우 엄격하게 정해져야 하는 데이터베이스와 같고(액세스),
dynamic type 언어는 셀에 들어가는 값과 타입이 자유자재로 바뀌어도 되는 스프레드 시트와 같다(엑셀).
어마어마한 대용량의 데이터를 처리하는 성능은 까다롭고 전문적인 DB가 훨씬 더 뛰어나지만, 그래도 엑셀 정도만 돼도 성능이 굉장히 좋아지고 그러면서 일상생활에서는 더 편리하다. 세상엔 그 정도 tradeoff는 어디에나 있는 듯하다.

그나저나, 문자열 코드를 실행하는 기능은 편리한 건 그렇다 치더라도 보안을 매우 취약하게 만들 수 있다는 점을 감안하고 사용해야 한다. C 언어의 %d, %s 같은 포맷 문자열만 해도 이미 위험하기 때문에 입출력 포맷 문자열에다가는 사용자로부터 실시간으로 입력받은 문자열을 절대 넣지 말아야 한다는 것이 불문율이다. 그런데 하물며 저건 문자열에 명시된 대로 아예 프로그램이 실행되니 C의 포맷 문자열과는 차원이 다른 방식으로 위험할 수밖에 없다.

본인이 경험해 본 프로그래밍 언어 중에 코드를 실시간으로 생성하고 자기 자신을 변형할 수 있는 물건의 원조는 역시 GWBASIC이었다.
비록 얘는 문자열을 코드로 그대로 해석하고 실행하는 기능은 없지만, 그래도 RUN, CHAIN, MERGE처럼 파일 차원에서 임의의 코드를 즉석에서 실행하는 기능은 있었기 때문이다.

다음은 스택 기반의 복잡한 파싱 없이 15*(2+5), 256^2, 1/5 등의 수식을 쓱쓱 계산해 내는 계산기 프로그램이다. 내가 이걸 생각한 게 20여 년 전의 일이다. ㅎㅎ

10 INPUT "Expression? ", A$
20 IF A$="" THEN END
30 OPEN "TMP.BAS" FOR OUTPUT AS #1
40 PRINT #1, "10 ON ERROR GOTO 30"
50 PRINT #1, "20 PRINT "+A$+": GOTO 40"
60 PRINT #1, "30 PRINT "+CHR$(34)+"Error"+CHR$(34)
70 PRINT #1, "40 RUN "+CHR$(34)+"CALC.BAS"+CHR$(34)
80 CLOSE #1
90 RUN "TMP.BAS"

프로그램의 동작 원리를 알면 피식 웃음이 나올 것이다. 이 프로그램이 하는 일이라고는 입력받은 수식을 그대로 PRINT하는 프로그램을 생성한 뒤, 그걸 실행하는 것이 전부이기 때문이다. 즉, 실질적인 계산을 하는 부분은 내가 짠 코드가 아니라 GWBASIC 인터프리터 자체인 것이다.
그나마 수식에 오류가 있어도 뻗지 않게 하려고 ON ERROR GOTO를 쓰는 일말의 치밀함(?)을 보였다.

사실, RUN은 지금 내 프로그램을 지우고 실행 제어를 다른 프로그램으로 완전히 옮기는 명령이다. MERGE나 CHAIN을 쓰면 내 프로그램의 컨텍스트를 유지하면서 타 프로그램을 그대로 병합을 할 수가 있으며 이게 내가 의도한 형태에 더 가깝다. 하지만 본인은 저 두 명령은 어떻게 사용하는지 잘 모른다.

MERGE 같은 경우 QuickBasic이나 QBasic으로 넘어가면서 후대의 베이직 언어들도 지원하지 않게 되었으며, 후대의 언어들도 차라리 문자열 코드를 실행하는 eval은 지원해도 저런 무지막지한 명령을 지원하지는 않는다. 먼 옛날에 컴퓨터와 함께 제공되었던 두툼한 MS-DOS 겸 GWBASIC 매뉴얼에서나 그런 명령에 대한 자세한 설명을 볼 수 있었던 걸로 기억한다.

자, 그럼 다시 자바스크립트 얘기로 돌아온다. 얘는 HTML, CSS와 더불어 웹을 구성하는 한 축이며, HTML 문서가 MS 오피스의 Word, Excel 같은 일반 문서라면 자바스크립트는 그런 문서에 첨부된 매크로나 마찬가지이다. 다른 모든 언어들은 사용하기 위해서 해당 언어 구현체들 런타임이나 엔진을 설치해야 하지만 자바스크립트는 웹브라우저만 있는 운영체제라면 바로 돌려볼 수 있다는 차이가 존재한다.

자바스크립트에는 딱히 컴파일· 링크 같은 건 없지만 난독화 겸 간소화(compaction)라는 리팩터링 후처리를 거쳐서 서버에 올라가곤 한다. 굳이 IOCCC 같은 대회를 노려서는 아니다. 핵심 알고리즘의 누출을 막고(분석을 완전히 막지 못하더라도, 어렵게 만들거나 지연시키기 위해) 크기도 줄이기 위해서이다.

난독화 내지 간소화를 자동으로 해 주는 도구가 당연히 존재한다. 모든 주석이나 공란은 제거되며 지역 변수는 전부 a~z, aa ... 처럼 식별만 가능하지 최대한 짤막하고 암호 같은 명칭으로 바뀐다. 상수 명칭의 조합으로 좀 더 알아보기 쉽게 숫자를 표현하던 것도 당연히 다 실제 숫자로 치환된다.

그런데 자바스크립트를 리팩터링해 주는 툴 중에는 그런 명칭 치환 수준을 넘어서 코드를 암호화에 가까운 완전히 다른 형태로 바꿔 버리는 것도 있다. 그런 덩어리를 보면 예전 코드는 다 이상한 문자열로 바뀌고 코드의 가장 바깥은 eval을 호출하는 형태가 돼 있다. 그 문자열을 원래의 자바스크립트 코드로 해독하는 건 다른 치환과 디코딩 함수들이고 말이다.

이건 개념적으로 실행 파일 압축과 동일한 기법이다. 거기도 압축을 푸는 데 필요한 최소한의 코드만 들어있고 나머지는 데이터를 압축을 풀어서 코드를 생성함으로써 실행되니까 말이다. 그게 기계어 코드가 아닌 인터프리터 스크립트형 언어에도 저런 식으로 존재할 수 있다는 게 신기하게 느껴진다. 자바스크립트의 JIT는 저런 것까지 다 감안해서 동작해야 할 테니 런타임이 안 그래도 거의 VM 수준의 스케일로 만들어져야 할 듯하다.

이념적으로 C/C++의 완전 정반대편에 있는 동적 언어를 익혀서 나쁠 건 없는 것 같다. JS는 그렇다 치더라도 파이썬은 슬라이싱 기능이 완전 마음에 든다. 늘 하는 생각이지만, 메모리 관리가 자동으로 되고 마음대로 new를 남발해도 되는 언어로 코딩을 하는 기분은 운전으로 치면 정말 클러치 걱정을 할 필요 없는 자동변속기 차를 모는 것과도 같아 보인다.

그리고 저런 동적 언어들은 아무래도 문자열 처리가 강세이며, 언어의 설계자가 확실히 정규 표현식 덕후라는 생각이 들었다.
메모리 관리를 할 필요가 없는 건 편하지만 C/C++의 포인터처럼 아주 저수준 직관적으로 문자열에 접근을 할 수 없는 건 좀 불편하다. 새로운 언어를 익히면 그 언어의 String 클래스가 제공하는 멤버 함수(메소드)부터 새로 익혀야 하니까.

또한 자바스크립트, 정규 표현식, URL, 그리고 HTML 내부에 제각각으로 돌아가는 탈출문자들 때문에 신물이 났다.
문자열을 검색· 치환하는 함수들이 다 있는 그대로 찾아 바꾸는 게 아니라 기본적으로 정규 표현식 기반이다. 역슬래시나 따옴표 같은 크리티컬한 문자 그 자체를 찾거나 그걸로 바꿔야 할 때 프로그램이 곧이곧대로 동작하지 않아서 그것도 좀 애로사항이었다.

Posted by 사무엘

2015/04/16 19:36 2015/04/16 19:36
,
Response
No Trackback , 4 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1085

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

하지만 메모리 할당은 그렇지 않다. 수~수십 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

« Previous : 1 : ... 11 : 12 : 13 : 14 : 15 : 16 : 17 : 18 : 19 : ... 23 : Next »

블로그 이미지

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

- 사무엘

Archives

Authors

  1. 사무엘

Calendar

«   2024/11   »
          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:
2989659
Today:
1219
Yesterday:
1477