« Previous : 1 : ... 55 : 56 : 57 : 58 : 59 : 60 : 61 : 62 : 63 : ... 221 : Next »

1. 텔레비전의 변천사, 라디오와의 차이

요즘 전화기가 유선, 무선, 위성, 인터넷이라는 네 방식이 있는 것처럼 텔레비전 방송에도 개념적으로 유선, 무선, 위성, 인터넷 방식이 모두 존재한다.
단, 전화는 처음에 유선(전화선)으로 시작했다가 나중에 무선 휴대전화(기지국..)가 개발된 반면, 텔레비전은 처음에 무선(지상파)부터 시작했다가 난시청 지역의 방송 접근성 개선을 위해 유선(케이블) 방송이 나중에 개발되었다는 차이가 있다.

사용자 삽입 이미지

(지상파 수신을 위한 텔레비전 안테나. 뭔가 기하학적인 직선 모양이다. 텔레비전 본체에 달린 더듬이 모양의 작대기 두 개만이 전부가 아니고, 이런 실외 안테나도 필요했던가 보다.)

텔레비전의 무선 신호는 처음에는 NTSC 내지 PAL이라는 아날로그 방식이었다. 동시대의 비디오 테이프에 VHS와 베타멕스라는 두 방식이 있었던 것처럼 텔레비전용 아날로그 신호도 하나만 있는 게 아니었다. 우리나라는 미국을 따라 NTSC 방식을 사용했었다.

처음에는 흑백만 지원하다가 1980년대부터 국내에도 컬러 방송이 시작됐는데, NTSC는 신호 구조에 하위 호환이 잘 지켜졌던 모양이다. 흑백 수상기는 여전히 흑백 영상이 나오면서 컬러 수상기에서는 컬러를 자연스럽게 볼 수 있었다.

그러다가 21세기에 와서는 신호가 통째로 디지털 방식으로 바뀌었다. 화질이 놀라울 정도로 크게 향상됨과 동시에, 화면의 종횡비도 바뀌어서 가로로 더 납작해졌다(4:3에서 16:9로).
이렇게 신호의 내부 구조가 달라진 한편으로 TV의 디스플레이 소자도 너무 크고 무겁던 브라운관이 퇴출되고, LCD 같은 얇은 물건이 등장했다. 2000년대 말~2010년대 초에 싹 물갈이가 된 것 같다.

우리나라에서 아날로그 방송 송출이 중단된 것은 2012년 말부터이다. 내 기억이 맞다면 지상파 방송이 평일에도 상시 24시간 방송을 하기 시작한 것도 2012년 하반기쯤으로 비슷하다.
물론 평일 낮 시간대는 대부분의 사람들이 생업에 바쁘니, TV를 보는 일이 잘 없다. 그래서 24시간 방송을 하더라도 저 때는 새 컨텐츠보다는 그냥 기존 방송의 재방송 위주로 편성되는 편이다.

그래도 24시간 방송 덕분에 텔레비전에서 화면조정 컬러바를 볼 일이 옛날에 비해 훨씬 더 없어진 것은 신통한 노릇이다. 서울 지하철 2호선은 순환선인 관계로 타 노선에 비해 종점에서 멈추는 열차를 찾기가 어려운 것처럼 말이다.

참고로, 이런 텔레비전에 비해 라디오는.. 디지털이건 아날로그건 음질이 크게 달라질 게 없으며, 전쟁· 천재지변 같은 상황에서도 아주 가볍고 저렴하고 단순한 장비만으로 수신이 가능한 게 좋다는 특성상 오늘날까지도 재래식 아날로그 방식이 여전히 유지되고 있는 듯하다.

그리고 라디오는 TV와 달리 딴 일을 하면서도 청취가 가능하다는 매우 중요한 특징이 있다. 특히 하루 종일 귀가 심심한 수많은 영업용 자동차(버스, 트럭, 택시) 운전사들의 고정 수요도 있기 때문에 망할 일이 절대 없다.
그러니 새벽 심야는 몰라도 평일 낮 시간에 라디오 방송이 끊겼던 적은 전무했다. 그리고 라디오 방송은 TV 방송보다 생방송의 비중이 더 크기도 한 것 같다. 이 바닥은 텔레비전과는 분위기 내지 물이 근본적으로 많이 다른 셈이다.

2. 위성 방송

무선 신호 얘기를 하다가 라디오 얘기도 나오면서 얘기가 옆길로 많이 샜는데.. 다시 본론으로 돌아오겠다.
유선, 무선 다음으로 위성은 기존 무선의 스케일을 확 키운 버전이라고 볼 수 있다. 통상적인 텔레비전 송신탑이나 휴대전화 기지국은 제아무리 높아 봤자 지상에 존재하며, 감당 가능 영역이 국내의 일정 지역까지로 한정된다. 그러나 정지 궤도 위성은 그야말로 지구 반대편으로 전파를 보낼 수 있다.

그래서 텔레비전의 위성 방송은 위성으로 쏘는 외국의 방송을 시청 가능하게 했으며, 위성 전화는 선박이나 남극 기지처럼 지상의 통신 인프라와 고립된 오지 외지에서 통신을 가능하게 해 주었다. 물론 위성 서비스는 통상적인 지상 기반 무선 서비스보다 단가가 훨씬 더 비싼 것을 감수해야 한다.

우리나라는 1995년에 무궁화 인공위성이 발사된 뒤부터 자체적인 위성 방송이 시작되었다. 하지만 그 전에도 외국의 통신 위성으로부터 서비스를 받는 외국 위성 방송 자체는 있었다.
내 기억으로 1990년대 중반, 그 시절에 국내에 수신된 대표적인 외국 위성 방송은 홍콩 StarTV였다. 뭔지는 모르겠지만 금빛 별 모양이 그려진 방송국 엠블럼이 나오고.. 정체 모를 팝송의 뮤직비디오와 운동 경기 중계가 많이 나오는 예능 채널 같았다.

본인은 스타TV 장면을 그 시절에 다니던 학원의 텔레비전에서 보고, 그리고 노래방 기계 화면의 배경 동영상으로도 많이 봤다! 그래, 그땐 그랬다. 이 TV에서는 어째 우리집에서는 볼 수 없는 외국 텔레비전 방송이 나오는지 의문을 가질 법도 했는데 그때는 그저 그러려니 하고 넘어갔던 것 같다.
그리고 비슷한 시기에 어디 친구 집에서 웬 일본 NHK 방송이 나오는 것도 본 적이 있다. 무슨 브랜드명이기라도 한지 화면에서 BS라는 이니셜이 자꾸 눈에 띄었던 것 같은데.. 알고 보니 그 이니셜 자체가 위성(S) 방송(B)이라는 뜻이었다.

사용자 삽입 이미지

(위성 방송 수신용 안테나. 접시 모양이다.)

위성 방송은 워낙 능력이 출중하니 통상적인 지상파 방송의 시청이 안 되는 문제를 해소하는 용도로 쓰이지만, 앞서 얘기한 것처럼 외국 방송 내지 위성 방송 사업자가 제공하는 고유한 컨텐츠를 시청하는 용도로도 쓰인다. 위성 방송 사업자가 2000년대에 개척한 영역은 바로 교통수단 내부에서의 이동 방송이다.

그래서 그 무렵에 고속버스에서는 ‘스카이라이프’ 위성 방송이 개시되었으며.. 새마을호 열차에서는 ‘코모넷’이라는 업체로부터 납품받은 위성 방송이 나오기 시작했다.
사실, 교통수단에다가도 전기 공급해 주고 안테나만 잘 꽂으면 그냥 지상파 TV를 수신하지 못하란 법이 없다. 그 시절에 버스가 아닌 승용차용 자그마한 흑백 TV도 있었다. 하지만 이렇게 교통수단용 별도의 이동 방송이 존재했던 건 신호 수신 때문이 아니라 고유한 방송 컨텐츠 때문이었다.

위성 방송은 다 좋지만 날씨의 영향을 받는 편이며, 특히 차량이 하늘이 보이지 않는 곳(터널..)에 진입하면 신호가 끊어지고 방송이 중단되는 단점이 있었다. 고속버스 말고 새마을호의 영상 서비스는 위성 방송이지만 그렇지 않았던 것 같은데 지금은 기억이 잘 안 난다.

2000년대 초중반까지 새마을호 열차에 위성 방송 기반의 영상 서비스를 제공했던 업체가 바로 그 이름도 유명한 ‘코모넷’이다. 새마을호의 시종착 때 Looking for you라는 마의 음악을 선곡해서 집어넣은 장본인도 코모넷인데.. 그 배후에 누가 있었는지 본인으로서는 너무나 궁금할 따름이다. 그 코모넷은 2000년대 후반에 소리소문 없이 망하고 폐업해 버렸으며, 이후에 창업주의 근황이 보도된 것도 전무하다. 그에 따라 새마을호의 영상 서비스는 2006년경에 연합뉴스로 바뀌었다가.. 얼마 못 가 완전히 폐지됐다.

3. 인터넷 TV (IPTV)

여기서 인터넷이란 유선 무선 같은 물리적인 기술 계층이 아니라, 그런 기술을 활용하고 해석하는 프로토콜 계층에서의 차이를 가리킨다. 인터넷의 물리적인 통신 방식이야 동축 케이블이나 유리 섬유 케이블 같은 유선으로 구현될 수도 있고, 수 GHz대의 와이파이 무선 통신으로 구현될 수도 있다.
단지, 영상과 음성 정보를 IP라는 인터넷 프로토콜에 근거한 패킷 형태로 주고받으며, 인터넷 자체가 양방향 소통 체계이다 보니 재방송, VOD 서비스 같은 인터랙티브한 서비스 제공도 덤으로 가능하다는 차이가 있다.

즉, 인터넷 TV에서 인터넷이란 컴퓨터 웹브라우저를 통해 사용자에게 친숙한 WWW(월드 와이드 웹)는 아니고, 그보다 저수준에서 인터넷의 범주에 드는 기술인 것이다. 개인이 마음대로 진행하는 유튜브나 아프리카 기반의 인터넷 방송하고도 당연히 다른 얘기이다.
인터넷 TV를 시청하려면 해당 서비스에 가입하고 거기서 받은 셋톱박스와 전용 안테나를 설치해야 한다. 아니, 지상파 외에 위성이나 유선 같은 다른 기술 계층의 TV를 시청하려면 저런 추가적인 장비가 필요하다.

인터넷 TV로는 지상파뿐만 아니라 기존 케이블 TV까지도 시청 가능하기 때문에 전통적인 케이블 TV와 사업 영역이 겹친다. 그래서 두 업종 간에 티격태격 하는 경우가 있다. 그래도 서로 완전히 동치는 아니기 때문에 IPTV 전용 채널이라든가 케이블 방송 전용 채널도 있다.

4. 유선(케이블) 방송

텔레비전은 원래 전파를 수신해서 동영상을 보여주는 물건인데 유선이라니..?? 그렇다고 CCTV도 아니고? 일면 의아한 생각이 드는데.. 헷갈릴 필요가 없다.
그 '케이블'이라는 건 방송국에서 유선 방송 사업자 사이가 연결돼 있다는 뜻이다. 방송사와 소비자 사이에 계층이 하나 더 생긴 셈이다. 거리가 충분히 가까워졌으니 소비자는 이 정도 양질의 신호는 셋톱박스 하나만 장착함으로써 수신할 수 있다. 물론 이 전파는 암호화도 돼 있어서 지상파 방송처럼 간단하게 시청할 수는 없다.

무선이 아니라 유선인 덕분에 이 방송은 기존 지상파 방송의 난시청 문제를 해결함과 동시에 지상파보다 훨씬 더 많은 채널도 제공할 수 있다. KBS, MBC, EBS, SBS가 뭔가 1군 지상파라면.. 그 다음으로 YTN, 아리랑 TV라든가 국회방송, 법률방송, 기독교 방송, 온게임넷 같은 건 유선· IPTV· 위성 형태로만 시청 가능한 2군 정도 되겠다. 물론 위성 방송처럼 외국 방송을 쏴 주는 건 아니고, 국내 한정이다.

이런 2군 방송들은 특정 장르와 분야의 방송으로만 한정되곤 했는데 얘들도 지상파 방송처럼 일반적이고 범용적인 분야로 방송 영역을 확장시킨 것이 그 이름도 유명한 종편, 종합 편성 채널이다. 채널A, JTBC, TV조선 같은 것 말이다.

유선, 무선, 위성, 인터넷 이런 것들이 자동차로 치면 수소 연료전지, 순수 전기, 하이브리드, CNG 개조, LPG 개조 같은 온갖 연료 바리에이션을 보는 느낌이다. 또는 택시, 렌트, 카셰어링, 타다 등의 서비스 바리에이션 같기도 하고..
지금까지 얘기가 나왔던 것을 한데 정리하면 이렇게 요약된다.

  • 태초에 제일 단순한 KBS MBC 같은 지상파 방송이란 게 있었으며, TV는 지상파 방송을 시청하라고 만들어진 물건이었다.
  • 그런데 지상파의 난시청 문제를 해결하기 위해 유선 방송이란 게 개발되었고 유선 방송 전용 채널도 많이 생겨났다. 즉, 얘는 지상파의 상위 호환이다.
  • 위성 방송은 유선 방송의 상위 호환이다. 덤으로 외국 위성 방송의 수신이 가능하고 교통수단 내부에서 쓰인 바 있다. 지상파의 지리적 한계를 극복하긴 했지만 얘만의 고유한 약점(날씨와 지형 제약)도 있다.
  • 인터넷 TV도 유선 방송의 상위 호환이며 오늘날의 대세이다. 뭔가 파일 기반인 게 DB 기반으로 바뀐 듯한 느낌인데(전문적인 계층 추가).. 인터넷 전화와 마찬가지로 인터넷 서비스가 불통되면 얘 역시 몽땅 먹통이 돼 버린다. (와이파이 전파 상태 내지 해저 케이블..)

그리고..

(1) 지상파 방송이 접근성이 제일 좋긴 하지만.. 지상파 방송이 곧 전국구 방송을 의미하지는 않는다. 가령, SBS는 엄연한 지상파 방송이지만 20세기까지는 서울· 수도권에서만 시청 가능했다. 지금도 경기방송, OBS경인TV 같은 건 지상파이지만 지방 방송이다.

(2) YTN의 경우 뉴스 보도 전문이니 종편은 아니다. 그런데 연합뉴스 TV는 YTN과 어떤 관계인지 난 잘 모르겠다.

(3) DMB는 스마트폰 같은 모바일 기기에서 TV를 시청하기 위한 기술 규격인데, 기존 TV 신호 기술과 비교했을 때 무슨 문제를 해결하기 위해 개발된 것인지 개인적으로 잘 모르겠다. 뭔가 더 좋은 구석이 있겠지..

(4) 엄청 옛날에는 전화기가 동그란 다이얼이 달려 있었듯이, 텔레비전도 옛날에는 채널을 변경하는 인터페이스가 - + 버튼이 아니라 다이얼 두 개였다. 한 다이얼은 2부터 13 정도까지 VHF라 하여 좁은 영역을 건드리고, 다른 다이얼은 UHF라고 두 자리수의 훨씬 더 넓은 영역을 촘촘하게 다뤘다.
대부분의 채널은 그냥 비어 있어서 지정해 봤자 치지직 잡음밖에 안 들렸는데.. 여기에다가 유선 방송 셋톱박스를 설치해야 그 채널들이 방송으로 채워졌던 것 같다.

사용자 삽입 이미지

(5) 옛날에는 매일 아침 지상파 방송에 CNN, NHK 등의 세계 외신의 주요 보도를 짤막하게 보여주는 '세계 뉴스'라는 코너가 있었는데.. 요즘이야 그것 말고도 외신 보도를 접하는 방법이 차고 널렸기 때문에 남사스럽게 그런 걸 하지 않는다. 정보를 접하는 게 이렇게 쉽고 간편해진 건 정말 전율에 가까운 모습이다.

(6) 덕분에 “텔레비전에 내가 나왔으면 정말 좋겠네”라는 동요도 의미가 참 무색해져 있다. 이제는 참신한 컨텐츠와 끼만 있으면 누구라도 개인 방송을 개설해서 언제든지 전세계에 자기 얼굴을 얼마든지 알릴 수 있기 때문이다.
다만, '메이저 지상파 방송'에 어떤 형태로든 출연하는 건 예나 지금이나 쉬운 일이 아니며 아무나 할 수 있지 않다. 본인조차도 지상파 방송 출연 경력은 현재까지도 지난 2005년 초의 스펀지 타자 실험맨이 처음이자 마지막이다.

(7) 컴퓨터에 TV 수신 카드라는 것도 있었는데.. 이 역시 격세지감이다. 국내에서는 두인 전자라는 기업에서 만들곤 했다.

Posted by 사무엘

2020/07/20 08:35 2020/07/20 08:35
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1775

1. 가상 함수 테이블을 강제로 없애기

C++에서는 가상 함수가 하나 이상 존재하는 클래스는 그렇지 않은 클래스와 좀 다른 취급을 받게 된다. 자기가 새로 선언한 것뿐만 아니라 기반 클래스로부터 상속받은 가상 함수까지 포함해서 말이다.
가상 함수가 존재하는 모든 클래스들은 각각 가상 함수 포인터 테이블(v-table)이라는 일종의 global 배열을 할당받으며, 이를 가리키는 포인터를 멤버로 갖게 되고 생성자에서 그 포인터의 값이 초기화된다.

클래스에 온갖 파생 클래스와 가상 함수들이 추가되고 상속 단계가 복잡해질수록, 이 테이블들의 개수와 크기도 무시 못 할 비중을 차지하게 된다.
A라는 클래스에 가상 함수가 5개 있으면 A의 v-table은 크기가 5이다. 그런데 A로부터 상속받은 클래스 B가 또 가상 함수를 3개 추가한다면 B의 v-table의 크기는 5+3 = 8이 된다. 그런 식이다. 다중· 가상 상속 같은 것까지 자연스럽게 지원하려면 B의 v-table에도 A의 것이 고스란히 몽땅 중복 등재돼 있어야 한다.

그런데 문제는 순수 가상 함수가 들어가 있어서 단독으로 인스턴스가 생성될 일이 전혀 없는 클래스에 대해서도 일단은 동작의 일관성을 보장하기 위해 v-table이 잉여스럽게 따로 생성된다는 것이다.
왜냐하면 B라는 클래스의 생성자는 그 정의상 기반 클래스 A의 생성자를 반드시 호출하게 돼 있고, A의 생성자가 하는 일 중 하나는 A에 대한 고유한 v-table을 참조하는 것이기 때문이다.

어차피 값이 거의 전부 다 NULL이고 실제로 쓰일 일도 없는 v-table이 쓸데없이 생성되는 일을 막기 위해, Visual C++에는 __declspec(novtable)이라는 비표준 확장 속성이 도입되었다. 특히 COM이라는 규격을 C++ 언어와 바이너리 차원에서 연계하려다 보니 이 기능이 없어서는 도저히 안 되는 지경이 되었다.

굳이 순수 가상 함수가 없더라도, 어쨌든 반드시 파생 클래스 형태로만 쓰이지 그 자체가 인스턴스 형태로 선언될 일이 없는 클래스라면 __declspec(novtable)를 지정해 주는 게 메모리 절약 측면에서 좋다. 날개셋 한글 입력기의 경우 이걸 적용해 주니 ngs3.dll의 크기가 수 KB 남짓 줄어드는 효과가 있었다.

특히 데이터 멤버 없고 순수 가상 함수밖에 없는 인터페이스라면 이거 지정이 무조건 필수이다. 이래서 후대의 객체지향 언어들은 구조체(생성자 소멸자 가상 함수 없는 POD) vs 클래스뿐만 아니라, 일반 클래스 vs interface(추상 클래스)도 언어 차원에서 구분한다는 게 느껴졌다. 이게 단순히 외형이나 기분상의 구분만을 의미하지 않는다.

파생 클래스 쪽의 초기화가 덜 된 오브젝트에 대해서 순수 가상 함수를 호출해 버리면 보통은 pure virtual function call이라는 C++ 예외가 발생한다. 그런데 __declspec(novtable)가 지정된 오브젝트에 대해서 가상 함수를 호출해 버리면 저런 high-level 예외가 아니라 그냥 NULL 포인터 접근에 준하는 평범한 access violation 에러가 나면서 프로그램이 죽게 된다.

2. 가상 함수 테이블의 메모리 오버헤드

가상 함수 테이블 얘기를 계속하겠다.
B가 가상 함수가 100개 있는 A라는 클래스를 상속받아서 자신만의 가상 함수를 새로 만드는 것 없이, A의 함수 중 딱 한두 개만 override를 했더라도.. 컴파일러는 내용이 한두 개밖에 차이가 나지 않고 거의 동일한 원소 100개짜리 함수 포인터 배열을 또 하나 만들게 된다. B의 모든 인스턴스들이 한데 공유하는 가상 함수 포인터 테이블 말이다. 일종의 copy-on-modify와 비슷한 메커니즘이다.

그렇게 해야만 A건 B건 오브젝트 종류를 불문하고 가상 함수의 주소를 배열 오프셋만으로 O(1) 시간 만에 곧장 얻어 와서 호출을 할 수 있기 때문이다.
가상 함수 호출은 그렇잖아도 오버헤드가 일반 함수 호출보다 훨씬 더 큰데, 메모리를 아낀답시고 주소를 얻는 것조차 클래스 계층이나 함수 테이블을 뒤지는 방식이어서는 곤란하다.

하지만 이런 내부 디테일과 오버헤드를 생각하면 클래스를 아무 부담 없이 막 상속받기가 좀 부담스러워진다. 뭐, 요즘처럼 컴퓨터에 메모리가 풍족한 시대에 무슨 198, 90년대 같은 메모리 구두쇠 쫌생이처럼 코딩을 할 필요는 없지만, 그래도 같은 값이면 컴퓨터의 자원을 아껴 쓰는 게 프로그래머의 미덕 아니겠는가?

그렇다고 언어의 스펙을 바꿔 버릴 수는 없으니, Windows용 C++ 라이브러리인 MFC는 저런 가상 함수 테이블 오버헤드를 줄이기 위해, CWnd 클래스의 Windows 메시지 handler 함수는 가상 함수 대신 pointer-to-member 기반의 message map으로 구현한 것으로 잘 알려져 있다.

얘는 사용자가 실제로 overriding을 한 함수의 개수만큼만 테이블의 크기가 커지니 공간 사용은 효율적이다. 그러나 메시지에 대응하는 함수를 찾는 일은 매번 테이블을 선형 검색을 해야 하기 때문에 시간 복잡도가 O(n)으로 커진다.
이 부분은 정말 밥 먹듯이 자주 호출되는 부분이고 목숨 걸고 성능을 최적화해야 하는지라, MFC 소스에서도 극히 이례적으로 인라인 어셈블리가 사용되어 있다~! (wincore.cpp의 AfxFindMessageEntry 함수)

객체지향을 구현하기 위해서 이런 시간-공간 tradeoff를 따져 봐야 하니, C++보다 더 유연한 디자인을 추구한 Objective C 같은 언어는 메시지를 아예 문자열로 식별하게 했다(selector). 이름으로부터 실제 주소 매핑은 물론 hash 기반이고..

C++은 배열 오프셋 기반이어서 당장 성능 자체는 제일 좋으나, 클래스에서 뭐가 바뀌었다 싶으면 몽땅 재컴파일 해야 한다. 그리고 그런 경직된 체계에서 다중/가상 상속과 멤버 함수 포인터까지 구현하려다 보니 디자인에 무리수가 많이 들어갔고, 그 뒷감당은 결국 컴파일러 제조사들이 비표준 확장을 도입해서 땜빵하는 촌극이 벌어졌다.

그에 비해 Objective C는 신호등 교차로 대신 회전 교차로, 수동 변속기 대신 아예 자동 변속기 같은 접근을 한 셈인데.. C++ 같은 C의 이념 적당히 절충이 아니라 객체지향 언어라는 이론만 생각해 보면 저런 접근 방식도 충분히 가능은 하겠다는 생각이 든다.

3. 캐사기 auto

람다 함수가 최초로 도입된 C++0x 시절에 곧장 가능했던 것 같지는 않다만..
요즘 람다는 인자와 리턴값의 타입으로 auto를 지정할 수 있다.. >_<
auto에서 또 auto가 가능하다니? 그것도 C++ 같은 언어에서.. 이건 좀 꽤 사악한 기능인 것 같다.

auto Func = [](auto x) -> auto { return x * 2; };
printf("%f\n", Func(1.4));
printf("%d\n", Func(25));

람다가 일반 C++ 함수 같은 오버로딩이나 템플릿을 지원하지는 않으나, 저기서는 auto가 템플릿 인자 역할을 하는 거나 마찬가지이다.
생긴 건 함수와 비슷하지만 캡처가 지원되고 호출 규약 제약 없고 저런 것까지 가능하니.. 기존의 함수와는 형태가 얼마나 다른지 알 수 있다.

그나저나 람다 함수는 함수 자기 자신을 지칭해서 재귀 호출을 하는 방법은 여전히 없는지? this처럼 자기 함수 자신을 가리키는 예약어가 필요하지 않을까 싶다. __self??
그리고 사실은 C++도 기반 클래스를 지칭하는 __super 같은 키워드도 있어야 할 듯하다.

4. export의 재탄생

C++에서 완전 존재감 없는 잉여로 전락했던 auto가 10여 년 전 2010년대부터는 잘 알다시피 언어의 패러다임의 변화를 주도하는 거물로 환골탈태했다.
그것처럼.. 지난 2000년대에 비현실적인 흑역사 키워드로 전락하고 봉인됐던 키워드 export가.. 2020년대부터는 모듈 선언이라는 완전히 다른 용도로 다시 쓰이게 될 것으로 보인다. 하긴 import, export는 그 대상이 무엇이 됐건 프로그래밍에서는 굉장히 즐겨 쓰일 만한 의미의 동사이긴 하다.

지난 수십 년을 C처럼 원시적인 텍스트 include와 라이브러리 링킹에 의존했던 C++에 파스칼이나 D처럼 신속하게 빌드 가능한 모듈, 유닛이 도입되려는가 궁금하다.
그럼 이제 C/C++에서 제일 존재감 없는 잉여 키워드로는 signed만 남는 건지?

C++이 이 정도까지 변하고 있는데 #pragma once는 좀 언어 차원에서 정식으로 표준으로 수용할 생각이 없는지 궁금하다.
이제 플랫폼을 불문하고 지원하지 않는 컴파일러가 거의 없는 사실상의 표준이며, 실용적으로도 꼭 필요한 기능인데 말이다.

5. 템플릿의 prtial specialization

C++ 템플릿에는 template<typename X> class A처럼 말 그대로 템플릿을 선언하는 게 있는가 하면, template<> class A<XXX> 처럼 특정 타입에 대해 specialization을 하는 문법이 있다. 둘의 형태를 주목하기 바란다.
그런데 C++03즈음에서는 partial specialization이라고 해서 단일 타입이 아니라 일정 조건을 만족하는 템플릿 인자에 대해서는 몽땅 이 specialization을 사용하도록 하는 기능이 추가되었다. 가령 X가 포인터 타입이라든가, 템플릿 인자 A,B를 받는데 B가 A 내부의 pointer-to-member 타입이라거나 할 때 말이다.

이런 specialization에서는 template<typename X> class A<X*> 내지 A<X, X::*> 같은 식으로 template과 A에 템플릿 인자가 모두 들어온다.
개인적으로 이런 기능을 사용해 본 적은 없다. 특정 상황에서 편리한 기능이 될 수는 있겠지만.. 컴파일러를 만드는 건 더욱 어려워질 것 같다..;; 저 시절에 이런 기능과 함께 export까지 제안됐었다니 참 가관이다.

6. C++ 코딩 중에 주의해야 할 점

예전 글에서 언급했던 것들이지만 다시 한데 정리해 보았다.
참고로, 클래스의 상속 관계에서 부모/자식(parent/child), 기반/파생(base/derived)은 서로 기능적으로 아무 차이 없이 기분 따라 섞여서 쓰인다.
인자와 매개변수도(argument/parameter) 그냥 섞여 쓰이는 것 같다.

(1) C++에서는 오버로딩과 오버라이딩이 동시에 같이 자동 지원되지는 않는다.
가령, 한 클래스에서 foo라는 같은 이름으로 void foo()와 void foo(int x)를 모두 정의하는 건 당연히 가능하다(오버로딩).
하지만 둘 중 하나를(가령, 후자) 가상 함수로 지정해서 파생 클래스에서 그놈을 오버라이딩 했다면.. 파생 클래스에서는 오버라이딩 되지 않은 다른 '오버로드' 버전은 자동 지정이 되지 않는다. 이건 혼동을 피하기 위해 C++ 언어의 스펙 차원에서 일부러 그렇게 설계된 것이다.

파생 클래스에서 전자를 사용하려면 기반 클래스의 이름을 거명해서 기반::foo() 이런 식으로 언급하거나..
아니면 파생 클래스의 선언부에서 using 기반::foo; 라고 명시적으로 선언을 해 주면 된다. 그러면 foo만으로 기반 클래스의 두 함수를 곧장 접근할 수 있게 된다. using에 이런 활용 가능성도 있었다니~!

(2) 클래스 객체의 생성 단계에서 멤버들이 초기화되는 순서는 헤더에서 선언된 순서이지, 생성자 함수의 이니셜라이저 목록에 등장하는 순서가 아니다. 그렇기 때문에..

class Bar {
    int x,y;
public:
    Bar(int n): y(n), x(y+1) {}
};

위의 코드에서 x는 제대로 초기화되지 않는다. x(y+1)이 y(n)보다 먼저 실행되기 때문이다.
이건 방대하고 복잡한 클래스를 구현할 때 정말 쉽게 간과하고 실수할 수 있는 특성인데 컴파일러가 경고라도 해 줘야 되지 않나 싶다. 함수 안의 지역변수에서 "초기화되지 않은 변수 거시기를 사용했습니다" 경고를 띄우는 것과 비슷한 로직으로 별로 어렵지 않게 구현할 수 있을 텐데..??

(3) 생성자와 소멸자 함수에서는 자기 객체에 대한 가상 함수를 호출하지 말아야 한다. 비록 기반 클래스의 생성자 및 소멸자가 파생 클래스의 생성자 및 소멸자의 실행 과정에서 같이 호출되겠지만.. 기반 클래스는 자기 객체가 진짜로 기반인지 아니면 상속된 파생 클래스로부터 호출된 것인지를 전혀 알 수 없으며, 알려고 하지도 말아야 한다. 그냥 기반 클래스 자신의 일만 하면 된다.

이는 프로그래머에게 불편을 끼치는 규제가 아니라 언어의 컨셉이 그러하기 때문이다. 생성자와 소멸자는 Windows 메시지로 치면 그냥 WM_CREATE/DESTROY가 아니라 NC 버전이다. (WM_NCDESTROY는 자식 창들이 몽땅 다 사라지고 없는 상태에서 호출) 그리고 복잡한 처리를 절대로 해서는 안 되는 DllMain 함수와도 같다.

기반 생성자는 이 객체가 파생 클래스로서의 초기화가 되기 전에 먼저 호출되고, 기반 소멸자는 파생 클래스 부분이 소멸된 뒤에 맨 나중에 호출된다. 그러니 파생 클래스가 어떠한지를 따지는 것은 전혀 무의미한 것이다.
이건 위의 2번과 마찬가지로 초기화의 순서와 관련해서 저지르기 쉬운 실수이다. 2번은 멤버들의 초기화 순서이고(수평??) 3번은 상속 계층에서의 초기화 순서이다(수직??).

소멸자 자체는 반드시 가상 함수 형태로 선언해야 하지만 소멸자 내부에서 또 다형성을 기대하지는 말아야 한다는 것이 아이러니이다.

(4) 멤버 함수 포인터로 가상 함수의 오버라이딩을 제어할 수는 없다.
한 함수 포인터로 기반 클래스의 함수 내지 파생 클래스의 오버라이딩 함수 중 원하는 것의 주소를 저장하고, ptr의 값(정확히는 ptr이 가리키는 vtbl의 값)과 무관하게 ptr에 대해 원하는 함수를 강제 호출하는 것은 가능하지 않다.

그런 시도를 했을 때 함수 포인터에 저장되는 것은, 그저 ptr->vtbl에 매핑된 버전의 함수를 따로 호출하는 역할을 수행하는 thunk뿐이다.
그러니 멤버 함수 포인터를 동원한다 하더라도 vtbl을 거친 가상 함수 본연의 동작을 변형할 수는 없다.

Posted by 사무엘

2020/07/17 08:35 2020/07/17 08:35
,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1774

1. 독특한 윈도우 클래스

Windows의 GUI에는 버튼, 리스트박스, 에디트 컨트롤 등의 잘 알려진 제1군 컨트롤 윈도우들이 있고, 공용 컨트롤이라는 제2군 윈도우도 있다. 이런 것들은 누구나 널리 사용하라고 클래스 이름도 널리 알려져 있다.
그런데 Spy++로 들여다보면, 정식으로 공개되지 않았지만 이런 클래스들이 공용으로 쓰이는 것 같다. 정체가 무엇인지 궁금하다.

  • NetUIHWND: MS Office 프로그램들, 그리고 심지어 워드패드와 그림판에서도 리본이 이 클래스 이름으로 만들어져 있다. 마소에서만 내부적으로 사용한다. (Visual C++의 MFC 확장판에서 제공되는 리본은 마소 오리지널이 아님)
  • DirectUIHWND: task dialog 내부, IE 브라우저의 탭, 탐색기 제어판에서 뭔가 웹페이지처럼 꾸며진 대화상자들에서 종종 쓰인다. 클래스 스타일에 CS_GLOBALCLASS도 버젓이 지정돼 있다. 마소 내부에서 사용하는 GUI 엔진 윈도우 같은데..
  • HwndWrapper[모듈이름;;GUID]: 닷넷이나 WPF처럼 통상적인 Windows API나 기성 컨트롤이 아닌 다른 프레임워크를 이용해서 GUI를 꾸미는 프로그램 같다. 내가 아는 프로그램 중에는 Visual Studio 2010과 그 이후 버전, 그리고 아래아한글(+ 타 오피스 제품 포함) 요 두 프로그램만이 이런 스타일이다.

2. 파일 및 디렉터리의 삭제, 디스크 제거

Windows는 프로그램을 memory-mapped file 형태로 불러온다는 특성으로 인해.. 실행 중인 프로그램을 삭제할 수 없다. 그래서 실행 중인 프로그램을 제거하거나 업데이트 하는 것도 좀 어려운 편이다.
실행 중인 프로그램 파일을 '개명'하는 건 가능하다. 여기서 개명이란 같은 드라이브 안에서의 디렉터리 이동도 포함한다.

이런 Windows와 달리 macOS나 리눅스는 실행 중인 프로그램 파일을 삭제할 수 있다.
허나, 실행 중인 프로그램의 current directory를 당장 삭제할 수 있는 운영체제는 내가 아는 한도 내에서는 없다. 삭제 예약만 해 놓은 뒤, 프로그램이 종료되거나 디렉터리가 딴 데로 바뀌었을 때 삭제되는 게 그나마 제일 관대한 처분이다.

프로세스의 current directory는 USB 메모리를 안전하게 제거할 수 없다고 운영체제가 꼬장 부리면서 사용자를 귀찮게 하는 요인 중 하나이기도 하다.
특히 Windows의 경우, 파일 열기/저장 공용 대화상자를 열어서 USB  메모리를 조회하면 해당 프로그램의 current directory도 거기로 바뀌기 때문에 대화상자를 닫은 뒤에도 저런 꼬장이 발생할 가능성이 높아진다.

뭐, 사용자가 USB 메모리를 물리적으로 강제로 제거해 버리는 것에는 장사 없다. 과거의 디스켓이나 광학 드라이브도 매체를 강제로 꺼낼 수 있었으니 말이다. 하지만 이런 일이 발생하면 운영체제의 관점에서는 current directory가 갑자기 없어지는 것이나 다름없고, 거기에 파일이 열려 있던 것들도 다 꼬여 버린다. 프로세스가 아닌 스레드를 강제 종료하는 것만큼이나 좋지 않은 현상이다.
디스크 내용을 날리고 싶지 않으면 사용자도 가능한 한 그런 짓은 하지 않는 게 좋다.

3. Windows의 런타임 환경

Windows에서 전통적인 API 기반의 네이티브 코드 데스크톱 프로그램 말고 선보였던 프로그램 실행 환경으로는..
먼저 2000년대(XP..)엔 .NET이란 게 있었다. 얘는 네이티브가 아니라 가상 머신에서 돌아갔고, 언어도 C#가 주류였다. C++에는 C++/CLI라는 변종 언어가 도입됐다.
그 뒤 2010년대(8..)엔 메트로 UI와 함께 C++/CX라는 변종 언어가 도입됐다. 얘는 데스크톱 환경은 아니지만 의외로 네이티브 코드 기반이었다.

.NET이 표방했던 것이 언어의 통합이라면, Windows 8이 표방했던 것은 기기(PC와 모바일?)의 통합이었다. 그래서 오죽했으면 시작 버튼을 없애는 초강수까지 뒀었다.
그러나 Windows 8은 망했으며, 결정적으로 Windows Phone/Mobile도 안드로이드와 iOS에 완전히 밀렸다. 이젠 마소에서도 그쪽 사업을 접었다. 그 대신 Windows 10은 ARM용이 정식으로 출시되어서 그 CPU에서 네이티브 데스크톱 앱을 그대로 돌릴 수 있게 됐다.

그럼 이 와중에 메트로 앱을 돌리는 Windows Runtime이라는 건 무슨 존재의 의미를 갖게 되는지 난 궁금하다. 답을 잘 모르겠다.
그냥 데스크톱 앱보다 글자 큼직하고 시각적으로 flat하고, 안드로이드 용어로 치자면 material design스러운 외형을 제공하는 GUI 엔진 그 이상도 이하도 아니어 보이는데..??
쉽게 말해 기존 공용 컨트롤이 '제어판'을 가동한다면 이 UI 엔진은 '설정'을 가동한다는 것이다.

마소에서 새로운 걸 시도한 것이 언제나 다 성공적이고 오래 유지된 건 아니었던 듯하다.
그래서 GDI+는 닷넷 시절에 잠깐 뜨다가 Direct2D 부류로 대체됐고, 오히려 운영체제의 근간으로서 넘사벽급의 짬밥을 자랑하는 재래식 GDI보다도 존재감이 없어졌다.
Edge 브라우저는 잘 알다시피 재개발되어서 사실상 크롬과 다를 바 없는 브라우저가 됐다. 마소의 지난 20여 년의 개발 트렌드를 회고해 보니 이런 분석과 결론이 나온다.

4. 이모지, 날개셋 입력 패드

Windows 10의 1803버전쯤부터는 win+.을 눌러서 이모지 문자표를 꺼내는 기능이 추가되었다.
날개셋 한글 입력기에서는 지난 9.81 버전부터 자체적으로 이모지 문자표가 추가되었다. 그럼 이건 언뜻 보기에는 기능 중복처럼 보이지만 실제로는 꼭 그렇지 않다.

운영체제의 이모지 문자표는 마우스로 딴 델 클릭하기만 해도 사라져 버리는 반면, 날개셋의 입력 도구는 그렇지 않다. 더구나 결정적으로 이 문자표는 날개셋 편집기에서 자체 입력기를 지정해 놓았을 때는 사용할 수 없다.
그리고 내 프로그램에서는 선택된 이모지를 복사하기, 그리고 cursor가 가리키는 이모지를 문자표에서 찾아서 역으로 표시하기 같은 기능도 갖추고 있다.

예전에도 언급했듯, 2018~19년에 걸쳐 추가된 ‘필기 인식’과 ‘이모지’는 날개셋의 고유 기능이 아니라 운영체제가 제공하는 기능을 자체적인 UI 껍데기를 씌워서 제공하는 대표적인 입력 도구이다. Full feature를 갖춘 한글 IME로서 저런 기능도 한 프로그램 내부에서 제공할 필요가 있기 때문이다.

뭐 그건 그런데.. 운영체제에서 기본 제공하는 이모지 문자표는 응용 프로그램에다가 어떤 방식으로 이모지 문자를 보내는 걸까? 그게 갑자기 궁금해졌다. 쟤는 기술적으로는 ‘날개셋 입력 패드’과 비슷하게 훅킹을 사용해서 IME 메시지를 보낼 것 같은데..

프로그램이 TSF를 감지하는지 여부를 따져서 지원하면 TSF 방식으로 문자를 보내고, 그렇지 않으면 기존의 IME 메시지를 보낸다는 것을 확인할 수 있었다. IME 메시지만을 사용하는 날개셋 입력 패드보다 더 고차원적이다. 사실, TSF를 지원해야만 메트로 앱에서도 이모지를 입력할 수 있을 것이다.

날개셋 입력 패드를 처음 개발하던 시절에 본인도 개인적으로는 TSF 방식을 뚫어 볼까 생각을 했었다. 이게 성공하면 외부 모듈뿐만 아니라 입력 패드도 편집기와 비슷하게 주변 문자를 자유자재로 변형하면서 신기한 기능을 제공할 수 있다.

그러나 외부 모듈 하나만 개발해 봐도 내 경험상 TSF는 기술 난이도가 헬이고 응용 프로그램별로 제멋대로 동작하는 구현의 파편화가 너무 심하다. 더구나 그 TSF의 혜택을 보는 프로그램은 매우 소수이며, 편집기와 외부 모듈 다음으로 입력 패드 자체도 사용 빈도가 매우 낮은 제3군의 실험적인 유틸일 뿐이다.

그렇게 TSF를 뚫어 봤자 훅킹은 어차피 메트로 앱에서는 통하지도 않기 때문에 그 단계에서 막히니..
시간과 노력 대비 타산이 맞지 않는다는 결론을 얻어서 그 이상의 연구는 포기했다. 입력 패드는 write-only인 IME 프로토콜만 사용하는 데스크톱 앱 전용 프로그램으로 굳어져서 오늘에 이르고 있다.

5. 유니코드 5.2 자모

아시다시피 지난 10여 년 전에 KS X 1026-1 및 유니코드 5.2에서 옛한글 자모가 여럿 추가되었다. 시기가 시기이다 보니 연속된 공간을 쭉 확보하지 못하고 덕지덕지 지저분하게 추가되었지만, 그래도 잘 살펴보면 프로그래머의 관점에서 복잡함과 불편함을 최소화하는 방식으로 추가되었음을 알 수 있다.

답부터 말하면, 어떤 글자가 한글 초성인지 중성인지 종성인지 판별하기 위해 과거(유니코드 1.1)에는 A<=X<=B라는 영역 검사 하나만이 필요했다. 이제는 그래도 그 영역 검사가 각 성분별로 하나씩만 더 추가되면 된다.

  • 초성은 U+1100부터 1159까지 하던 영역에서 끝부분을 115E로 늘린 뒤(5개 추가), A960부터 A97C라는 새로운 영역 한 곳만 더 살펴보면 된다.
  • 중성은 U+1160부터 11A2까지 하던 영역에서 끝부분을 11A7로 늘린 뒤(5개 추가), D7B0부터 D7C6이라는 새로운 영역을 더 살펴보면 된다.
  • 종성도 그런 식으로 기존 영역을 조금 확장하고 나서 새로운 영역을 더 살펴보면 된다.

잘 알려져 있지 않지만, KS X 1026-1은 종성에 ㅇ으로 시작하는 겹받침(ㅇㄱ, ㅇㄲ, ㅇㅇ, ㅇㅋ)을 그냥 이응이 아닌 옛이응으로 수정한 규격(잠수함 패치..)이기도 하다.

그리고 KS X 1026-2는 정규화 규칙을 추가로 규정해서 현대 한글을 옛한글 자모의 조합으로 표현하는 것을 금지하고, 현대 한글 글자마디와 옛한글 자모가 합성되는 것도 명시적으로 금지했다. 한 한글은 오직 한 가지 방식으로만 표현되게 했다.
Windows는 2012년에 나온 8부터 저게 반영됐고, 날개셋 편집기에서는 지난 2015년에 나온 8.0 버전에서야 반영됐다. 저 표준을 제정한 분과 개인적으로 얘기까지 나누며 설명을 들은 뒤에야 말이다. 엇, 그러고 보니 둘 다 8부터네?!?

유니코드 2.0에서 한글 글자마디 11172자를 따 온 건 예전에 서울 올림픽 유치 성공만큼이나 역사에 길이 남을 위대한 쾌거였다. 현대 한글이 그렇게 정립된 뒤에 옛한글도 저렇게 됨으로써 한글 코드 논란은 완전히 종식됐다.

그 뒤로 유니코드 자체도 2003년쯤이던가 4.0에서 U+10FFFF라는 상한을 명확하게 정하고, 이 이상 글자를 더 등록하지는 않을 것이라고 못을 박았다. 그 이상은 UTF-16으로는 더 표현할 수가 없기 때문이다. 그래서 UTF-8도 4바이트 방식까지만 사용하고 5~6바이트 방식은 봉인했다.

따라서 현재 유니코드에 정의된 모든 평면이 고갈되고 글자들로 꽉 차는 날이 온다면.. 유니코드 위원회는 해산될 것이다. 지금의 문자 코드 체계는 지구와 현 인류 문명이 송두리째 멸망하지 않는 한 쭉 이어질 것으로 보인다.

Posted by 사무엘

2020/07/14 19:32 2020/07/14 19:32
,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1773

1. 중국(청): 삼전도비와 독립문

영국에서 높으신 분들이 한국을 방문하면 6· 25 설마리 전투에 참전했던 영국군 글로스터 대대를 기념하는 전적비를 반드시 찾아간다고 한다(파주 적성면 소재). 영국은 나름 미국 다음으로 대규모로 참전했던 나라이다. 본인은 지난 2013년에 거기를 답사한 바 있으며, 답사기가 이 블로그에도 올라와 있다.

다음으로 영국 같은 훈훈한 예는 아니겠지만, 옛날에 일본인 관광객이 서울을 들렀다가 반드시 찾아가는 관광 코스 중 하나는 광화문과 경복궁 사이에 놓여 있던 조선총독부 청사였다고 한다. 뭐, 그 건물은 잘 알다시피 김 영삼 정권 때 헐렸다.

허나, 저것보다 더 안 좋은 예가 있다. 조선 시대에 병자호란 이후로 청나라에서 사신이 오면.. 얘들은 반드시 삼전도비부터 찾아가서 저게 잘 보존돼 있나 확인했다고 한다. -_-;; 그 이유는 더 말할 필요가 없을 것이다. 부득이하게 현장에 직접 못 가면 비석 표면의 탁본 인증이라도 받았다고 한다. 우리나라 역사에 이런 시절이 있었다는 게 실감이 가는가?

사용자 삽입 이미지

물론 옛날에는 일체의 사대주의가 몽땅 다 지금 우리가 굉장히 부정적인 색안경을 끼고 보는.. 그저 목숨 부지하기 위해서 밸도 자존심도 다 저버리는 비굴한 굴종만을 의미하는 건 아니었다.

옛날에는 사람과 국가 사이에 지금보다 뭔가 수직적인 위계 관계 질서가 훨씬 더 중요시되었다. 그리고 약자가 자기 팔자대로 먹고 살기 위해서는 인근의 강자에게 조공을 바치면서 조공 이상의 가성비로 다른 강자들로부터 보호를 받는... 뭐 그런 공생 관계가 필요한 것도 있었다. 이는 일제 시대에 일제의 통치에 적극적으로 저항하지 않고 소극적으로 따랐던 사람들을 몽땅 친일파 매국노라고 욕하는 게 위험 편협한 사고방식인 것과도 비슷한 이치이다.

조선의 경우 명나라와 청나라 사이에서 어영부영 하다가 병자호란이 벌어졌는데 전쟁에서 그만 져 버렸다. 그래도 여러 나라 안팎 정세 덕분에 어째 나라가 통째로 멸망당하지는 않았다. 그 대신, 왕이 누구 보는 앞에서 이마를 몸소 땅바닥에 짓찧기까지 하면서 한국사 전체를 통틀어 유례를 찾기 힘든 모욕을 당하게 됐다(삼전도의 굴욕).

고려 시절에는 도읍을 강화도로 옮기고, 그걸로도 모자라서 몽골· 원나라의 간섭을 받으면서 왕도 계속해서 충짜 시리즈로 나오던 적이 있었다. 훗날 구한말 때는 아관파천 같은 민망한 흑역사도 발생하긴 했다만.. 저건 그 시절에 통용되던 사대주의의 범주도 넘는 수준이었다.
삼전도비는 지금으로 치면 독립기념관의 야외에 내팽개쳐진 채 방치돼 있는 조선총독부 청사 지붕 첨탑을 능가하는 극도의 치욕의 산물이었다.

그리고 서대문 형무소 근처에 놓여 있는 독립문은 바로.. 우리 조선이 드디어 황제-_-를 보유한 대한제국이 됐고 삼전도비를 애지중지 관리할 필요가 없어졌다는 걸 기념하려고 세운 것이었다! 그 시절엔 그것만으로도 엄청난 이벤트였다. 청일 전쟁 이후로 청나라는 한반도에서 완전히 아웃 당했으니까..
뭐, 조선이 대한제국으로 간판을 바꿔 봤자 나라가 돌아가는 방식이 실질적으로 바뀐 건 별로 없었다. 애초에 자기 힘으로 청나라를 몰아낸 것도 아니었으니 말이다.

독립문이 세워지기 전에는 동일 장소에 '영은문'이란 게 있었다. 이름부터가 "은혜로운 대국의 사신을 영접한다"라는 뜻이며, 명나라와 청나라의 사신이 이 문을 통과하여 개선장군처럼 들어왔었다.
그랬는데 독립협회가 주축이 되어 영은문은 헐어서 주춧돌 기둥만 남겼으며, 그 대신 독립문이란 걸 1896~97년에 걸쳐서 건립했다.

사용자 삽입 이미지

저 시절에 서 재필은 독립문, 독립협회에다가 독립 신문까지.. '독립'이라는 단어를 유난히도 좋아했다. 당시에 조선의 미래를 걱정하던 개화파 지식인들이 보기에는 청나라의 영향력에서 벗어나는 게 당장 최대의 과제이고 염원이었던가 보다.

이런 맥락을 모르고서 '독립문'이 일본으로부터의 독립을 기념하는 조형물인 줄로 아는 사람은 아마 성경에 나오는 '사자'가 lion인지 messenger인지 분간 못 하는 것과 비슷한 지적 능력의 소유자이지 싶다. (마침 위치도 서대문 형무소 근처이다 보니 일본과 관계가 있는 것으로 더욱 오해하기 쉽긴 함.ㄲㄲㄲㄲㄲㄲㄲ)

물론 옛날 개화파 사람들 중에는 중국의 대안이 오로지 일본이라고.. 일본을 과신하는 착오를 범한 사람도 있었다. 하지만 그런 사람들의 친일 성향은 훗날 등장하는 매국노 반민족행위자의 친일 성향과는 성격이 다른 것이었다.
그래서 그런지 일제는 훗날 조선의 주권을 침탈한 뒤에도, 과거에 조선이 영은문을 허물었던 것처럼 '독립문'을 또 철거한다거나 하지는 않았다. 경성 시내의 재개발을 위해 서대문(돈의문)을 헐었을 뿐이지..

일제는 오히려 그 시절에 내팽개쳐지고 방치됐던 삼전도비를 재정비했으며, 이것도 그래도 역사 유물이라며 문화재로 등재하고 관리· 보존했다. 참으로 역설적인 노릇이다. 과장 좀 보태면, 다 부서진 채 방치돼 있던 경주 석굴암을 조사하고 어설프게나마 복원 공사를 한 것과 과정이 비슷했다.

한편, 그렇게 몰락했던 청나라는 조선이 멸망한 것과 비슷한 시기에(1912) 같이 멸망했으며, 그 뒤 중화민국을 거쳤다가 나중에 공산당 중화인민공화국 & 대만로 갈라져서 현재에 이르고 있다.

중국은 땅덩이가 워낙 넓어서 몽땅 정복하기가 곤란했던 덕분에, 아편 전쟁이라든가 청일이고 중일이고 각종 전쟁에서 참패하고도 대놓고 멸망하고 외세 식민지가 되지는 않았다. 단지 홍콩 같은 몇몇 지역을 잃었으며, 덕분에 문화가 이질화돼 버려서 수복 후에도 반쯤 특별구역처럼 됐을 뿐이다.

2. 일본: 1980년대 초반, 1990년대 중반의 반일

우리나라야 해방 이래로 지금까지 원초적인 반일 감정이 없던 적이 없었지만.. 1980년대 5공 시절에는 유난히 더했던 것 같다.
다음은 1982~84년에 발간된 무려 30권짜리 '동아 원색 세계 대백과사전'의 화보에서 '일본'을 찾아보면 나오는 설명이다.

사용자 삽입 이미지

우리는 예로부터 일본에게 선진 문물을 전해 주고 일본을 근대화(?)시켜 줬건만, 걔들은 은혜를 원수로 갚고 우리에게 상처와 피해만 끼쳤다는 뿌리깊은 배신감과 피해의식, 투철한 반일의식을 엿볼 수 있다.
"일본은 이러한 잘못을 깊이 뉘우치고, 우리의 진정한 우방이 되어 세계 평화에 기여하기를 바란다"

코멘트 훈수까지.. ㅋㅋㅋ 관찰자가 아니라 전지적 작가 시점의 극치다.
얘는 지금의 두산 세계 대백과사전, 두피디아의 전신으로, 집필진을 보면 온통 스카이 대학 교수들이 즐비하다. 나름 당대 최고의 석학들이 모여서 편찬한 백과사전이다.

그런데 일면 이해가 된다. 동아 원색 세계 대백과사전이 출간되었던 저 때는 일본의 역사 왜곡 발언으로 인해 반일 감정이 전국적으로 극에 달했었다.

정 광태의 그 유명한 "독도는 우리땅" 노래가 발표된 게 1982년.
박 영희 할머니가 자신이 조선어 학회 사건의 발단이 된 그 일기장의 주인이었다고 늘그막의 나이로 언론에 커밍아웃을 하며 일본을 공개적으로 규탄한 때도 1982년 여름이었다.

어디 그 뿐이랴? 국민 성금을 모아서 독립기념관을 건립하기 시작한 때도 1982년이다! (87년에 완공, 개관)
그러니 이 정도면 저 시기에 편찬된 민간 백과사전에 "일본은 각성하길 바란다" 같은 말이 본문은 아니어도 화보에 들어가고도 남지 않았겠는가?

1970년대에 북괴의 연이은 도발 때문에 전국민이 북괴를 규탄했었고,
1983년에 대한항공 007편 격추 사건 때문에 반소 감정이 폭발했었고..
2002년에 오노 금메달 사건과 여중생 장갑차 사건 때문에 반미 감정이 치솟았던 것과 동일한 맥락이다.

누가 저질렀건 같은 수준의 잘못에 대해 같은 강도로만 규탄하면 된다. 그럼 문제될 것 없다. 그렇지 않고 진영을 가려가며 편파적으로 선동질 하는 놈들이 매우 불순하고 나쁜놈일 뿐이다.
저 책은 북한 쪽을 찾아봐도 역시 5공 시절답게 '북괴' 운운하면서 노골적인 경계심과 적개심을 표현한다. 일본이고 북괴고 다 공평하게 모두 깐다. 그러면 편파적이지 않고 차라리 낫다.

한편, 저 때로부터 10~15년쯤 뒤인 김 영삼 때도 유독 일본의 정치인들이 위안부나 독도와 관련해서 망언을 자주 했고, 반일 감정이 상승했던 것 같다. 그러니 대통령이 "쟤들 버르장머리를 고쳐 주겠다" 발언까지 하면서 조선총독부 청사를 헐어 버렸다.
정확한 근거나 출처를 찾기는 어렵지만 아마 저 때 새마을호 열차(= 국영 철도청 관할)의 안내방송에서 일본어를 잠시 빼 버린 적도 있었다고 한다. 그게 사실이라면 어지간히도 소심한 방법으로 저항했었다.;;

일본 문화가 정식으로 개방되지도 않았고 일본에 대한 피해의식과 컴플렉스가 훨씬 쩔어 있었던 옛날이야 그럴 수도 있었다고 치지만 지금은 무려 2020년이다. 1982년이 아니다. 우리가 지금 북괴로부터 6· 25 시절 같은 군사력 위협을 느끼는 건 전혀 아니듯이, 일본에 대해서도 쟤들이 지금 같은 상황에서 무슨 태평양 전쟁 시즌 2 같은 짓거리를 하면 어쩌나 걱정을 할 필요는 없다.

그럼에도 불구하고 아직도 지적 수준이 일제 쇠말뚝이니 아베 노부유키의 저주(?) 나부랭이에 머물러 있는 반일은 좀 망상 정신병을 의심해야 하지 않나 싶다. 지금 우리나라에는 악성 친일파보다 더 나쁜놈들도 수두룩하기 때문이다.

3. 한국: 꼬인 근현대사 비극의 근원

내가 분명히 말하는데, 한국 현대사의 비극들 중 상당수는
과거에 자국민의 힘으로 조선 왕조를 직접 끝장내지 못한 것에서 유래되었다고 보면 된다.
무슨 광복군이 제대로 참전하지 못한 것? 그건 별 의미 없으며 신경 쓸 필요 없다.

19세기에 조선은 말이 좋아 고요한 아침의 나라이지, 지금으로 치면 아프리카의 어느 못 사는 나라와 비슷하게 가난하고 굶주리고 비위생적이고 다른 나라와 제대로 교류하지도 않으면서 최악의 막장 고인물 썩은물이 되어 있었다.
오죽했으면 차라리 이전의 고려가 조선보다는 더 개방적이고 국제 인지도가 더 높았기 때문에 한국의 영어 명칭조차도 ‘코리아’로 굳어지지 않았던가? 조선의 입김이 닿았다면 choose의 과거분사와 비슷한 단어가 됐을 텐데 말이다.

생각해 보면 조선은 시작과 끝이 은근히 잔혹하고 악랄했다. 건국 초기부터(1400년 초) 특별히 반역이나 역모 혐의가 없었는데도 고려 왕씨들을 집요하게 색출해서 참수하거나 바다에 던져넣는 식으로 학살했다. 나중에 홍 경래의 난이 진압됐을 때는(1812) 수괴와 간부는 그렇다 치더라도 단순 가담자까지.. 10살 이상 남자는 전원 무려 1917명을 한 치의 자비심 없이 몽땅 처형했다.

하물며 말기에 개화파는..?? 그야말로 삼족이 멸해지는 참화를 당했다. 김 옥균은 외국 망명 중에도 왕이 직접 보낸 자객에게 암살 당한 게 마치 북한 김 정남이 암살 당한 것과 비슷해 보인다. 저 사람뿐만 아니라 서 재필도 가족은 조부모, 부모, 자녀, 손주 세대까지 깔끔히 순삭 당했다.
고종과 민씨 일가는 외교와 경제· 군사는 등신 같았지만, 자기 밥그릇 지키는 데는 귀신이었다. 임오군란, 갑신정변, 동학 같은 건 외국 군대까지 끌어들여 자근자근 밟아 줬다. 그리고 그 외세는 조선의 무덤을 파는 부메랑이 되어 돌아왔다.

그때가 되니 조선 정부도 일본에게 내정간섭 좀 그만 하라며 뒤늦게 손사래를 치기도 했지만.. 이미 일본군이 경복궁 주위에까지 쫙 깔렸고 때는 너무 늦었다.
이걸 가만히 생각해 보면 당시 쟤들은 거의 싸이코패스가 아닌가 생각이 들 법도 한데 국뽕 국사 교과서는 이 과오를 진지하게 다루지 않는다. 특히 민비가 워낙 적절한 타이밍 때 잘 암살 당하기도 했으니 말이다.

그리고 청산리니 봉오동이니 하는 1920년대 독립군의 전과가 많이 부풀려지고 왜곡된 것으로 밝혀졌듯이, 20여 년 뒤의 광복군도 비록 취지는 훌륭하지만 그 규모와 전투력은 정말 보잘것없는 수준에 지나지 않았다.

군대는 오로지 소비밖에 안 하는 집단이다. 지금 북한이 중국 없이 못 지내는 것 이상으로 그때 임시정부니 광복군이니 다 장 제스의 지원이 없었으면 뭘 더 할 수 있었을까? 겨우 그거 갖고 당당히 일본을 무찌른 전승국 대접을 받고 미국· 소련의 입김에서도 벗어난다? 남북 분단도 안 되었을 거라고? 꿈도 참 야무지다.

내가 예전에 몇 번 언급한 적이 있었지만.. 일본이 핵폭탄 맞고 일찍 항복하고 허겁지겁 도망간 것은 걔네들한테나 우리한테나.. 특히 우리나라 입장에서도 득이 훨씬 더 많았다.
일제가 한반도에서 뭔가를 수탈하고 망쳐 놓은 것을 논하기 전에, 구한말 때 이 헬조선 땅에 더 수탈하고 망가뜨릴 게 그렇게 많이 있기나 했는지를 이성적으로 진지하게 생각할 필요가 있다.

Posted by 사무엘

2020/07/11 19:35 2020/07/11 19:35
, , ,
Response
No Trackback , 3 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1772

1. 오일러 상수: 감마

초등학교 시절에 반비례 함수라고 배웠던 1/x는 조화급수를 나타내며, 궤적은 쌍곡선이고 부정적분은 ln x (+C)가 되는 꽤 독특한 함수이다.
그런데 1/x의 값을 1 간격으로 n까지 구한 조화수열의 합과(즉, 1 간격의 구분구적법), 아예 해당 함수의 정적분(실제 면적)은.. 일단 명목상으로 값이 비슷할 것 같은데 정확하게 얼마나 차이가 날까? n이 무한대로 근접할수록 값이 어떻게 될까?

먼저, 이런 발상이 왜 나왔겠는지부터 생각을 해 보자.
1/x라는 문제의 함수는 x가 무한대로 갈 때 함수값 자체는 0으로 수렴하여 한없이 줄어들지만, 급수의 무한합 내지 정적분은 무한대로 발산한다는 아주 기괴한 특징이 있다. 보통은 다 발산하거나 다 수렴하지, 저렇게 되는 건 몹시 드물다.

1/x 말고 그냥 x라든가 1/x^2 같은 주변의 다른 함수에 대해서  급수의 무한합과 정적분의 차이를 구해 보면 구하는 게 무의미한 trivial한 결론이 나와 버린다. 그냥 무한대로 빠지거나, 아니면 한쪽이 그냥 0이 돼 버려서 차를 구할 필요가 없어지는 식이다.

하지만 1/x는 그렇게 trivial하게 빠지지 않는다. 더구나 무한급수와 정적분의 차이가 “무한대 - 무한대” 꼴의 극한이 되는지라, 극한값이 유한하게 나온다는 게 직관적으로 보장도 되지 않는다. 그러니 이 극한값은 수학적으로 파고들 명분과 의미가 있으며, 옛날 천재 수학자의 관심을 끌게 되었다.

일단 1/x에 대해서 무한합과 정적분의 차이가 특정값으로 수렴한다는 것 자체가 증명되었으며, 그 값은 대략 0.577215… 형태로 빠진다. 이 수는 관례적으로 오일러-마스케로니 상수라고 불린다.

자연상수 e야 미적분과 밀접한 관계가 있으니 중등교육 수준에서도 이과의 최종 테크 한정으로 배운다. 하지만 저 상수는 쓰이는 곳이 너무 난해한지라, 수학과에서 해석학을 전문적으로 배우는 정도가 아니라면 딱히 접할 일이 없다.

더구나 쟤는 특성이 밝혀진 것도 별로 없다. 사칙, 삼각함수, 지수, 로그만으로는 저 수를 나타낼 수 없다. 그러니 정황상 초월수 무리수인 것이 99.999% 확실해 보이긴 하지만.. 수학자들이 수긍할 수 있는 완벽 엄밀한 논리 전개만 동원해서는 초월수는커녕 무리수인지도 정확하게 증명이 못 돼 있다고 한다. 의외의 일이다.

다만, 이 수는 e^(-x) * ln(x)라는.. 비교적 친근한(?) 초등함수 조합을 0부터 무한대까지 이상적분을 해서 얻을 수 있다(음수 버전이 나옴). 친근해 봤자 쟤는 이미 부정적분은 초등함수 형태로 나타낼 수 없는 수준이지만 말이다.

그리고 이 수는 팩토리얼의 대수적 확장 버전인 감마 함수와도 관계가 있다. GAMMA(x)-1/x라는 함수에서 x=0의 극한값이 이 수의 음수 부호 형태이다. 신기하지 않은가? 그래서 이 오일러 상수는 그리스 문자 ‘감마’(γ)를 써서 표기하곤 한다. 상수는 소문자 감마이고 함수는 대문자 감마이다.
이 글의 전체 내용을 수식으로 요약하면 다음과 같다.

사용자 삽입 이미지

2. 오메가 상수와 람베르트 함수

고등학교 수학에서는 지수와 로그를 다루고, 이과 및 자연계에서는 이에 대한 미· 적분까지도 다룬다.
그럼 다음으로 지수함수 e^x에다가 x를 또 곱한 x*e^x라는 함수를 생각해 보자. 취급하기가 약간 더 복잡해졌지만.. 그래도 얘는 부분적분을 통해 부정적분을 온전한 형태로 구할 수는 있다.

그런데 x*e^x = 1 이라는 방정식의 근을 구할 수 있을까? (뭔가 오일러의 항등식과 살짝 비슷하게 생겼는데.. 그냥 기분 탓임..)
양변에 로그를 씌워서 식을 정리하면 x= -ln x까지는 나온다. 하지만 이 이상 식을 정리하는 건 무리이고, 이 시점에서 발상을 전환하여 뭔가 새로운 개념이나 용어를 창조해야 할 것 같다. 그리고 그걸 1700년대에 이미 실제로 한 사람이 있다.

일단 저 식을 실수 범위에서 만족하는 근 x는 대략 0.567143…으로 전개되는 값이다. 앞서 다뤘던 오일러 상수와 얼추 비슷한 크기라는 게 흥미롭다. 물론 특성과 의미는 전혀 다르지만 말이다. 더 나아가 x에 대해 t*e^t = x를 만족하는 t를 되돌리는 함수가 바로 고안자의 이름을 딴 “람베르트 W” 함수이다.

고안자인 요한 하인리히 람베르트는 수학, 물리, 천문학, 철학 등 다방면에서 가히 레오나르도 다 빈치 급으로 불세출의 천재였다고 전해진다. 오일러와도 같은 국적의 동시대 사람이었으나.. 좁은 세상에 태양이 둘일 수는 없어서 그런지 람베르트는 오일러보다야 인지도가 낮다.

오일러 상수에 그리스 문자 ‘감마’가 부여되어 있다면, 앞서 언급한 W(1)에는 관례적으로 그리스 문자 ‘오메가’가 부여되어 있다. 오메가가 w와 비슷하게 생겨서 두 문자가 저렇게 섞여 쓰이는 것 같다.
W(1)은 오일러 상수보다는 분석하기가 아무래도 더 용이한지, 초월수라는 것은 간편하게 증명되어 있다.

하지만 W라는 함수도 기존 초등함수의 형태로 나타낼 수 없으며 절대로 만만한 물건이 아니다. 그럼 정체를 알기 위해 만년 수치해석 근사값에만 의지해야 하느냐 하면.. 그렇지는 않다. 해석학적인 의미를 지닌 형태로 나타낼 수는 있는데, 그게 감마 함수가 들어간 무한급수이다. 이 역시 공대 수준의 숫자 공부만 한 사람이라면 그냥 포기하는 게 속 편할 것 같다..;;

하지만 이런 물건이 왜 존재하느냐 하면.. 그게 존재함으로써 더 복잡한 문제를 풀 수 있고 다른 복잡한 개념을 간결하게 표현할 수 있기 때문이다.
x*a^x뿐만 아니라 아예 x^x=b 같은 방정식의 근도 Lambert 함수 형태로 표현 가능하다.

단적인 예로 x^x = e의 근은.. 저 오메가 상수의 역수.. 1/W(1), 대략 1.763222…이다. e에다가 단순 가공을 한 게 아닌, 뭔가 차원이 다른 수가 튀어나온 셈이다. x^x 정도면 적분도, 방정식 근도 모두 통상적인 방법으로는 못 구하는 난감한 물건이니 말이다.
어쩐지 뭔가 메이플 같은 수학 패키지로 지수함수가 섞인 복잡한 방정식 풀이를 시켜 보면.. LambertW 이러는 식으로 답이 나오곤 하던데 그게 저런 뜻이었다.

Lambert 함수는 양의 실수에서는 ln x보다도 더욱 느리게 증가하는 별볼일 없는 함수이다. 하지만 수학 전공자들은 이런 함수를 실수도 모자라서 복소수 영역에서 갖고 논다. 도함수나 부정적분을 구하면 이 함수 자체가 포함된 더 복잡한 형태가 나오는 게 지수/로그함수 계열과 비슷해 보인다.

얘는 음수 -1/e부터 0 사이에서는 마치 제곱근처럼 값이 2개가 나온다는 특징이 있다.
비록 중등 교육과정에서 가르치지는 않지만.. 해석적으로 분석을 못 하는 것도 아닌데, 관점에 따라서는 얘도 초등함수의 범주에 넣을 수 있어 보인다.

사용자 삽입 이미지

참고로 람베르트 함수 말고 삼각함수 중에도..
우리가 0 극한값을 배울 때 써먹었던 sin(x)/x처럼.. 하필 sin(x), cos(x), sinh(x), cosh(x)를 x로 나눈 물건의 정적분 함수를 따로 Si(x), Ci(x), Shi(x), Chi(x) 요렇게 표기한다. 기존 초등함수들의 조합으로 나타낼 수 없는 새로운 특성을 갖지만 그래도 수학적으로 다른 의미가 있기 때문이다.

저렇게 써 놓으니 무슨 한어병음 표기처럼 보인다만=_=;; 어쨌든 저런 건 초월함수들 중에서 적분함수라고 따로 불린다.

Posted by 사무엘

2020/07/09 08:35 2020/07/09 08:35
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1771

1. 그레이스케일

이건 이미지를 흑백 형태로 바꾸는 것이 핵심이다. 색을 구성하는 정보량의 차원이 줄어들고(3차원 → 1차원으로) 결과적으로 전체 색깔수가 줄어들기는 하지만(수천~수십만 종류의 색상 → 256단계의 회색), 그래도 아예 B&W 단색으로 바꾸는 건 아니다.
각 픽셀들은 색상과 채도가 제거되고 명도만 남아서 흑부터 백 사이에 다양한 명도의 회색으로 기계적으로 바뀐다. 같은 색의 픽셀이 인접 픽셀이 무엇이냐에 따라서 다르게 바뀐다거나 하지는 않는다.

그런데 그레이스케일 공식이 딱 하나만 존재하는 게 아니다. RGB 세 성분의 산술평균을 주면 될 것 같지만, 그렇게 그레이스케일을 하면 그림이 굉장히 칙칙하고 탁하게 보이게 된다.

똑같이 최대값 255를 주더라도 빨강(255,0,0), 초록(0,255,0), 파랑(0,0,255) 각 색별로 사람이 인지하는 명도는 서로 일치하지 않는다. VGA 16색 팔레트를 다뤄 본 사람이라면, 밝은 빨강이나 밝은 파랑을 바탕으로는 흰 글자가 어울리지만, 밝은 초록은 그 자체가 너무 밝아서 흰 글자가 어울리지 않는다는 것을 알 것이다.

사용자 삽입 이미지

그러니 공평하게 33, 33, 33씩 가중치를 주는 게 아니라 거의 30, 60, 10에 가깝게.. 초록에 가중치를 제일 많이 부여하고 파랑에는 가중치를 아주 짜게 주는 것이 자연스럽다고 한다.

이런 공식은 누가 언제 고안했으며 무슨 물리 상수처럼 측정 가능한 과학적인 근거가 있는지 궁금하다. 옛날에 흑백 사진을 찍으면 색깔이 딱 저 공식에 근거한 밝기의 grayscale로 바뀌었던가?
본인은 저 그레이스케일 공식을 태어나서 처음으로 접한 곳이 아마 QBasic 내지 QuickBasic의 컬러/팔레트 관련 명령어의 도움말이었지 싶다.

2. 디더링

이건 그레이스케일과 달리, 색깔수를 확 줄이는 것이 핵심이다. 그 대신 반드시 단색 흑백이어야 할 필요가 없다. 가령, 트루컬러를 16색으로 줄이는 것이라면 RGB 원색이 살아 있는 컬러라 할지라도 디더링의 범주에 든다. 이런 것처럼 말이다.

사용자 삽입 이미지

디더링에서는 어떤 원색을 직통으로 표현할 수 없을 때 근처의 비슷한 여러 색들을 고르게 흩뿌려서 원색과 비슷한 색감을 나타낸다. 팔레트까지 임의로 지정이 가능하다면 256색 정도만 돼도 생각보다 그럴싸하게 원래 이미지를 재현할 수 있다. 그런 게 아니라 그냥 흑백 단색 디더링이라면 아까 그레이스케일처럼 어떤 공식에 근거해서 명도를 추출할지를 먼저 결정해야 할 것이다.

0부터 1까지 가중치별로 점을 골고루 균등하게 뿌리는 공식은 이미 다 만들어져 있다. 이건 Ordered dither이라고 불리며, 보통 8*8 크기의 64단계 격자가 쓰이는 편이다. 그 구체적인 방식에 대해서는 수 년 전에 이 블로그에서 이미 다룬 적이 있다.

그레이스케일은 RGB(0.4,0.2,0.6)라는 색을 0.3이라는 명도로 바꾸는 것에 해당하는 기술이고, 단색 디더링은 이 색이 0.3, 0.3, 0.3 … 이렇게 쭉 이어지는 것을 1, 0, 0, 1, 0, 0 이런 식으로 이산적으로 표현하는 것과 같다.
그런데 원색을 기계적으로 이런 격자로 치환하기만 하면 보기가 생각보다 굉장히 좋지 않다.

원래 0.3을 표현해야 하는데 지금 지점에서 부득이하게 1을 찍어 버렸다면 0.7이라는 오차의 여파를 인접 픽셀에다가 떠넘겨서 거기서 계속해서 감당하게 해야 한다. 즉, 그레이스케일은 그냥 인접 픽셀을 신경 쓰지 않고 픽셀 대 픽셀 변환만 했지만 디더링은 그렇지 않다. ordered dither 내지 더 단순무식한 nearest color 찍기 신공이 아니라면.. 이전 픽셀에서 발생한 오차를 수습하는 error diffusion을 동원해야 부드러운 결과물이 나온다.

사용자 삽입 이미지

그런 지능적인(?) 디더링 알고리즘은 196~70년대에 컴퓨터그래픽이라는 분야가 등장한 초창기부터 연구되어 왔으며, 고안자의 이름을 따서 Floyd-Steinberg, Burks, Stucki 같은 알고리즘이 있다. 이미지를 다루는 사람이라면 포토샵 같은 그래픽 편집 프로그램에서 저런 명칭들을 본 적이 있을 것이다.
이런 알고리즘들은 ordered dither와 달리, 점들이 일정 간격으로 산술· 기계적으로 단순 투박하게 찍힌 게 아니라 뭔가 한땀 한땀 손으로 입력된 것 같고 부드러운 느낌이 든다. 그리고 무엇보다 색깔이 크게 바뀌는 경계 영역이 훨씬 더 선명하다.

3. 하프톤 (망점)

그레이스케일이 색깔 표현에 제약이 없는 아날로그 영상물(특히 흑백 필름 사진) 같은 느낌이 나고, 디더링은 초기에 해상도와 색상이 부족했던 디지털 영상과 관계가 있다면.. 하프톤은 색상이 부족한 대신 해상도가 높은 '인쇄물'과 관계가 깊은 기법이다.

사용자 삽입 이미지

하프톤은.. 일정 간격으로 망점을 두두두둑.. 찍고, 그 망점의 크기/굵기만으로 명도를 조절한다. 깨알같은 점들의 양과 배치 방식을 고민하는 통상적인 디더링과는 문제 접근 방식이 약간 다르다.
출력물의 해상도가 영 시원찮은 데서 하프톤을 동원하면 망점이 너무 커서 눈에 거슬릴 수 있다. 회색을 만들려고 했는데 하양과 검정이 그대로 눈에 띄게 된다는 것이다.

그러나 아무 인쇄물에서나 흑백이든 컬러든 문자 말고 음영(색깔 배경)이나 사진을 하나 보시기 바란다. 전부 촘촘한 점들로 구성돼 있다.
컴퓨터용 프린터나 전문 출판물 인쇄기들이 무슨 수십~수백 종에 달하는 물감을 갖고 있지는 않다. 잉크는 3원색에다가 검정 이렇게 4개만 갖고 있고, 나머지 색은 전부 얘들을 적절한 배율로 섞은 망점의 조합만으로 표현한다.

컴퓨터에서 보는 사진 이미지를 프린터로 출력하기 위해서는 가산 혼합 기반인 RGB 방식의 색을 감산 혼합 CMYK 방식으로 변환하고, 색 축별 망점 배합을 계산해야 할 것이다. 이건 디더링과는 다른 영역이다. 프린터 드라이버가 하는 일 중의 하나가 이것이며, 전문적인 사진이나 출판 프로그램 역시 색 축별로 인쇄 형태의 저수준 데이터를 export하는 기능을 제공한다.

그래도 요즘은 사진조차 필름 현상이 아니라 디지털 카메라로 찍은 뒤에 포샵질을 하고 고급 인화지에다 '인쇄'해서 만들어 내는 세상이니.. 컬러 인쇄 기술도 예전보다 굉장히 많이 좋아지고 저렴해졌다. 1990년대까지만 해도 가정용으로 컬러 레이저 프린터라는 걸 생각이나 할 수 있었겠는가?

유니코드 문자 중에 U+2591 ~ U+2593은 단계별 음영을 나타낸다.
굴림· 바탕 같은 통상적인 Windows 글꼴은 이를 하프톤 형태로 표현한 반면, 함초롬바탕은 디더링 형태로 표현했음을 알 수 있다.
윤곽선 글꼴의 래스터라이즈 방식의 특성상 디더링보다는 하프톤이 부담이 덜하기도 하다. 윤곽선 글꼴은 뭔가 덩어리· 군더더기가 늘어날수록 출력 성능이 급격히 떨어지기 때문이다. 그래서 매끄러운 곡선 말고 오돌토돌, 쥐 파먹은 효과 같은 걸 표현한 글꼴은 크기도 크고 처리하기 버거운 편이다.

사용자 삽입 이미지

이상이다. 이 사이트에도 그레이스케일, ordered 디더링, 기타 휴리스틱-_- 디더링, 그리고 하프톤까지 다양한 사례가 소개되어 있으니 관심 있는 분은 참고하시기 바란다.

우리가 사는 공간이 3차원일 뿐만 아니라, 시각 정보의 기본 단위인 색깔이라는 것도 어떤 방식으로 분류하든지 결국 3개의 독립된 축으로 귀착된다는 게 신기하지 않은가? 신학에서는 이게 하나님의 속성인 삼위일체와 관계가 있다고도 말하는데, 그건 뭐 결과론적인 해석이며 물증이라기보다는 심증 수준에서 만족해야 하지 싶다.

음악에서는 음계, 음정, 화성학 같은 이론이 수학과 연결되어 오래 전부터 연구돼 왔다.
그럼 일명 color theory라고 불리는 분야는 언제부터 누구에 의해 연구돼 왔을까? RGB, CMY, HSL 사이를 변환하는 공식 같은 것 말이다. 더디링은 컴퓨터그래픽 영역이겠지만 순수하게 색에 대한 수학적인 분석은 미술과 전산학 어디에도 딱 떨어지지 않아 보인다. 내가 알기로는 전파로 영상을 주고 받는 텔레비전 기술이 개발된 20세기 초쯤에야 이런 분야가 개척됐다.

본인이 색과 관련하여 감이 오지 않고 아직도 좀 알쏭달쏭한 걸 얘기하면서 글을 맺도록 하겠다.
모니터 화면 같은 건 그 본질이 빛이며, 빛은 색을 태생적으로 지니고 있다. 그러나 다른 세상 만물들, 특히 인쇄물은 색만 있지 빛은 지니고 있지 않다. 조명을 받아야만 자기 색을 비춰 보일 수 있다.

빛은 원색이 RGB라고 여겨지며, 섞일수록 더 밝아져서 최종적으로 white에 도달하는 ‘가산 혼합’을 한다. 그러나 빛이 없는 나머지 사물의 색들은 섞일수록 더 어두워져서 최종적으로 black에 도달하는 ‘감산 혼합’을 한다. 이 정도는 이미 초등학교 미술 시간에 배운다.

다만, 빛의 3축과 색의 3축은 노랑/초록 말고 빨강/파랑 부분도 서로 미묘하게 차이가 난다는 것부터는 고등교육 이상의 영역으로 보인다. 본인도 그걸 대학 이후에나 접했기 때문이다.
왜 그런 차이가 나는지에 대해서는 논외로 하더라도, 빛이건 색이건 두 색상을 물리적으로 섞지는 말고 디더링 하듯이 오밀조밀 가까이 배치시켜 놓으면 멀리서 볼 때 정확하게 어떤 혼색이 나타날까? 이걸 잘 모르겠다.

이건 빛이든 인쇄된 색이든 차이가 없어야 하지 않을까? 그리고 만약 차이가 없다면 그 결과는 감산 혼합이나 가산 혼합 중 어느 것과도 정확하게 일치하지 않을 것이다. 그 관계가 무엇일까? 마치 산술/기하/조화 평균처럼 서로 비슷하면서도 미묘하게 다른 결과가 나오지 않을까 싶다.

이해를 돕기 위해 아래 그림을 살펴보자. 반드시 확대/축소 왜곡되지 않은 원래 크기 형태로 볼 것!! (화면 확대 배율도 96DPI/100%로 맞춰야 함)
왼쪽은 그냥 RGB(0,0,0) 검정과 RGB(255,255,255) 하양을 1:1로 섞은 것이고, 오른쪽은 RGB(0,0,255) 파랑과 RGB(0,255,0) 초록을 섞어서 청록을 만든 것이다. 여기서 섞었다는 것은 픽셀 디더링을 말한다. 그리고 우측 하단에는 본인이 보기에 이들과 가장 비슷하다고 생각하는 순색(solid color)을 칠해 보았다.

사용자 삽입 이미지

255짜리 순색 파랑 255짜리 순색 초록을 섞어서 가산 혼합이 됐다면 RGB(0,255,255) 밝은 cyan 청록이 나타나야 하겠지만 실제로는 전혀 그렇게 되지 않았다. 그런데 그렇다고 해서 평균인 128짜리의 훨씬 어두운 색이 된 것도 아니다.
본인이 보기에 가장 비슷한 순색의 명도는 대략 180~184 정도이다. 생각보다 제법 밝다. 아.. 그래서 옛날에 VGA 팔레트도 어두운 색을 무식하게 128로 지정하지 않고 170 정도의 값을 줬는가 싶은 생각이 든다. 다른 색과 섞었을 때 재현 가능한 색이 나오라고 말이다.

이런 현상을 설명하기 위해 감마 보정 등 다른 어려운 이론들도 나온 게 아닌지 추측해 본다. 디더링 알고리즘이란 게 내가 생각했던 것보다 여러 단계로 나뉘고 더 어렵고 복잡할 수도 있겠다는 생각이 든다. 적절한 명도 추출하기, 적절히 대체색으로 분비하기, 오차 보정하기 등..;; 시각 디자인의 세계는 오묘하다.

Posted by 사무엘

2020/07/06 08:35 2020/07/06 08:35
, , , ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1770

도로와 철도의 터널과 교량

1. 자동차용 터널과 교량

도로나 철도를 만들다가 산 같은 장애물을 정면돌파 하려면 터널을 뚫게 되고, 기존 도로를 입체 교차하거나 강을 건너기 위해서는 교량을 건설하게 된다. 이런 시설들은 구불구불 우회해서 가야 할 경로를 굉장히 곧게 해 준다.

기술이 발달한 덕분에 요즘은 옛날에 상상하기 어려웠던 매우 크고 길고 넓은 터널과 교량이 많다.
산 하나를 통째로 관통하는 건 일도 아니고 도시 시가지를 통째로 지하로 통과한다. 2차로를 넘어 4차로 광폭 터널도 어렵지 않게 볼 수 있으며, 제한적이나마 바다를 건너는 터널(아래로)이나 교량(위로..)도 있다. 아무래도 고가(교량)보다는 지하도(터널..)가 만들기 더 어려운 것이 주지의 사실인데, 하저· 해저터널 같은 건 참 경이롭다.

다만, 이런 곳을 자동차로 운전해서 갈 때는 좀 주의해야 한다. 터널을 드나들 때는 주변의 밝기가 갑자기 변하기 때문에 운전자의 시야가 교란될 수 있으며, 교량은 바람이나 온도가 일반 평지와는 달라서 길이 미끄러울 수 있다.
그리고 둘은 형태는 다르지만 길 밖으로 벗어날 곳이 딱히 없기 때문에 비상 대피나 탈출이 취약하다는 공통점이 있다. 특히 터널은 화재라도 났다간 질식의 위험까지 있다.

이런 이유로 인해 우리나라에서는 터널이나 교량에서는 한 치의 예외 없이 차선들이 실선으로 그어졌으며, 차로 변경과 추월이 금지돼 왔다.
하지만 모든 교통사고가 오로지 과속과 추월 때문에 발생하는 것도 아닌데, 저건 현실과 안 맞는 너무 규제 위주의 악법이라는 지적이 끊이지 않았다.

사용자 삽입 이미지

요즘은 시종일관 한 차로로만 달리기에는 너무 길고 큼직한 터널도 많다. 그리고 강을 건너는 교량 말고 강과 수 km째 나란히 가는 교량도 많은데 거기도 차로를 몽땅 실선으로 틀어막아야 하는지에 대한 형평성 문제도 있다.

국도 20호선과 4호선이 만나는 북건천 분기점은 긴 건천 터널을 통과하자마자 분기점이 뿅 나타난다. 경주에서 20을 이용해서 달리다가 저기서 4로 갈아타서 영천? 대구 방면으로 가려면 아예 터널에 진입하기 전부터, 한참 전부터 맨 오른쪽 n차로로 차로를 바꿔야 한다. 터널 안에서 차로를 바꾸는 건 실선 차로와 각종 차단봉으로 막혀 있기 때문이다. 이런 문제도 있다.

그래서 요즘은 특정 조건을 만족하는 형태로 새로 만들어지는 터널에 한해서 터널 안도 점선 차선이 그어지고 차로 변경을 허용하는 추세이다. 교량 쪽은 소식을 못 들었다만, 거기도 좀 더 합리적이고 개방적인 쪽으로 변화가 생겼으면 좋겠다.

2. 철도용 터널과 교량

자동차가 다니는 터널과 교량은 그렇고.. 그럼 이제부터는 철도의 터널과 교량에 대해서 얘기하도록 하겠다.
요즘 만들어지는 큼직한 터널은 도로용이나 철도용이 외관이 크게 차이가 나지 않는 듯이 보인다. 그러나 옛날 초창기에, 특히 철도가 다들 단선 비전철 위주이던 시절에는 그렇지 않았다.

철도 차량은 레일 근처 하부의 폭과 중상부의 폭이 차이가 많이 나는 교통수단이다. 이는 제한된 레일 궤간에서 최대한 큼직한 차량을 굴리기 위한 노력의 결과이다. 그래서 한국의 경우, 법적 차량 한계가 1250mm 이하의 낮은 부위와 그 이상 높은 부위의 폭이 서로 다르게 명시돼 있다.

철도 차량은 자동차와 달리 레일을 한 치도 벗어나지 않고 정밀 정확하게 다니니.. 터널도 그야말로 차량 한계가 허용하는 한계치까지 작게 만드는 게 가능하다. 그래서 터널의 단면조차 차량의 단면과 비슷하게 하부가 상부보다 더 작으며, 단면이 말발굽 모양처럼 돼 있다. 이것은 철도 터널이 자동차용 터널과 결정적으로 다른 특징이다.

사용자 삽입 이미지

뭐, 언제까지나 옛날에 그랬다는 것이다. 요즘은 한 터널 안에 복선 선로를 집어넣고 위에 전차선도 집어넣고.. 또 고속 주행을 위해 공기가 드나들 틈을 더 내기도 하니 철도 터널도 옛날보다야 더 큼직하게 만든다.

그리고 철도는 교량도 좀 특이했다.
옛날에는 철교의 상부에 딱히 난간이나 트러스 같은 게 없었고 생긴 게 참 단촐(?) 소박했다. 뭐, 어차피 레일이 있으니 단순히 통과 차량의 안전을 위한 난간이나 가드레일 따위는 없어도 될 것이다.

과거의 단선 비전철 철길은 선로의 좌우에 아무 인공물이 보이지 않아서 좌우의 창 밖을 보면 자동차를 탈 때보다 자연의 정취랄까 그게 더 강하게 느껴졌다. 반대편 선로라는 것도 없고 전차선 전봇대도 없고.. 침목과 레일이 놓인 자갈밭이 끝인데 그건 양 옆의 시야로는 어차피 보이지 않는다. 열차가 교량을 통과할 때면 그냥 강물 위로 공중에 떠 있기라도 한 것 같다.

그리고 이런 교량은 딱히 ‘도상’이란 게 없어서, 레일 밑에 깔린 침목 아래로 곧장 강물이 출렁출렁 내려다보였다. 자갈밭조차 없었다는 뜻이다. 옛 수인선의 소래철교처럼 말이다.

사용자 삽입 이미지

하지만 오늘날은 이런 식으로 철교를 만들지 않는다. 레일 밑에 아무 지반이 없으면 열차가 지나갈 때 소음과 진동이 주변에 너무 크게 전해지기 때문이다. 궤도 아래에 침목과 자갈 같은 걸 괜히 만드는 게 아니다. 물론 요즘은 나무 침목이나 자갈조차도 안 쓰고 싹 다 콘크리트 땜빵이지만..
자동차 도로도 고속도로 같은 건 옛날처럼 아스팔트를 안 쓰고 이제 시멘트 포장을 하니, 철길 노반과 도로 노반이 생긴 모습이 다 허옇게 비슷해졌다.

내 기분상 도로 교량보다는 철도 교량이 상부에 이렇게 철골 구조물이 치렁치렁 솟아 있는 경우가 많다. 삼각형 그물 모양의 뼈대 구조이다 보니 무슨 3차원 그래픽 와이어프레임을 보는 것 같은데..
단순히 잉여 미관 때문이 아니라 교량을 안정적으로 지탱하기 위해 일부러 만들어 넣은 거라고 한다. 한강 최초의 교량인 한강 철교도 이런 형태로 만들어졌었다. 110여 년 전에 처음 만들어졌을 때부터 말이다.

사용자 삽입 이미지


Posted by 사무엘

2020/07/03 08:35 2020/07/03 08:35
, , , ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1769

추억의 옛날 수필

1. 비는 반드시 옵니다 (주 기운) (☞ 전문)

요즘은 전반적으로 장마나 태풍의 위력이 예전 같지 않고.. 기계에다 비유하자면 비구름들의 출력(?)이 좀 시원찮은 것 같다. 비는 대부분 일기예보보다 늦게 적게 내리고 금방 그치는 편이다.

지난 6월, 이례적인 이상 고온이 계속됐을 때 본인은 중학교 시절에 접했던 이 수필이 문득 생각났다.
‘사후약방문’, 그리고 ‘패연히’라는 단어가 인상적이어서 기억에서 잊혀지지 않는다. 비가 패연히 내리는 건 ‘태연히’가 아니라 콸콸 세차게 내린다는 뜻이다.

글쓴이인 주 기운(1928~2007. 3. 16.)에 대해서는 검색해 보니 광주/호남 지방의 유명 문인이었던가 보다.
기일을 보니 내가 곧바로 떠오르는 건.. “아, 이분은 공항철도 1차 구간 개통 딱 1주일 전에 돌아가셨구나.”이다. 철도님이 계시해 주셨다.

그런데 정작 저 글이 언제 쓰여졌는지, 우리나라에 언제 저런 기록적인 폭염과 가뭄이 들었었는지는 알 길이 없다. 카더라 통신에 따르면 1970년대에 신문에 기고된 글이라는데.. 저게 교과서에 소개되어 실린 건 1994년부터라고 한다.
“은전 한 닢”이라든가 “방망이 깎던 노인” 같은 유명 수필도 글이 발표되고 공개된 때는 거의 1970년대이고, 그 글이 다루는 배경은 1930년대인 걸로 난 알고 있다.

2. 아버지의 손 (오 천석) (☞ 전문)

올해 초에는 어느 현직 의사가 “내과 박원장”이라는 자전적인(?) 웹툰을 연재해서 의료인뿐만 아니라 의대 지망생 내지 일반인들에게도 큰 호응과 인기를 얻었다. 그림이며 스토리며 모두 전문 웹툰 작가 뺨치는 퀄티리였다. IT업계에만 월화수목 금금금이 있는 게 아니었군..

특히 몸집 크고 나이 많고 동기들에 비해 어리버리하지만.. 성격은 정말 순진하고 착하던 소대광이라는 의사를 소개한 에피소드는 정말 대박이었다. (☞ 보기)
물론 저건 의사로서 최악에 최악의 상황만 골라서 발생한 예외에 가까운 사례이겠지만.. 실화와 각색의 비율이 어느 정도 되는지 궁금하다. 이 정도면 단편영화 한 편 찍어도 될 것 같다.

요즘은 버스 기사를 폭행하는 것도 가중 처벌되고, 심지어 신호 대기 중이라도 운전 중과 동급으로 취급되도록 법이 바뀌었다. 하물며 밤에 고생하는 의료진을 술 쳐먹은 인간쓰레기들로부터 더 강력하게 보호할 수 없나 안타깝다. 삼청교육대 같은 거 만들어서 삼청의 삼짜만 나와도 무서워서 술이 확 깨는 지경 정도는 돼야 할 텐데.. 뭐 그건 그렇고.

소대광은 처자식을 먹여 살리려고 개원 후에 지나치게 과로하다가 돌연사하고 말았다.
심장에 문제가 생겨서 쓰라린 흉통이 느껴질 때 약을 먹으려고 했는데.. 좁쌀만 한 알약을 실수로 책상과 벽 사이의 좁은 틈새로 톡 떨어뜨려 버렸다. 그걸 큰 손으로 미처 건져내지 못하고 그만..

내과 박원장을 읽으면서 졸피뎀이라는 약이 있다는 걸 알게 됐고, 니트로글리세린이 폭약일 뿐만 아니라 협심증에 대처 가능한 의약품이기도 하다는 걸 알게 됐다.;; 뭐, 의사 약사 지망생들에게는 기본 상식이겠지만 말이다.
그런데.. 생각해 보니 비슷한 스토리가 나오는 옛날 수필이 하나 더 있었다. 바로 “아버지의 손”.

저 글에서는 글쓴이의 선친이 똑같은 협심증 지병 때문에 똑같은 니트로글리세린 약을 처방 받았다.
그러나 선친은 문맹이었던지라, 위급한 상황이 됐을 때 약병을 개봉하지를 못했다. 그래서 병을 돌로 쳐서 깰 생각까지 했지만 미처 그러지 못하고 통증을 호소하다가 최후를 맞이했다. ㅠㅠㅠㅠ

“어린이가 함부로 건드릴 수 없는 안전 뚜껑. 눌러서 돌리면 열립니다”를 읽지 못해서.
게다가 저 글에서도 아버지는 힘 세고 손이 크고.. 영락없이 소대광 같은 캐릭터였다고 묘사된다.

다만, 니트로글리세린은 혈관 문제나 심장병을 근본적으로 치료해 주는 약은 아니다. 글에서도 “그 약만 먹었으면 아버지는 도움을 청할 때까지 버틸 수 있어 안 돌아가셨을 것”이라고 쓰여 있다.
저 약은 환자가 의료진에게 도착할 때까지 생존할 수 있도록 잠시 시간을 벌어 주는 역할만 한다. 하지만 그것만으로도 충분히 매우 유용하며 사람 생명을 구할 수 있다. 마치 심폐소생술처럼 말이다.

저것도 완전히 낯설지는 않게 느껴지는 글인데.. 학교에서 접한 적이 있었나 모르겠다. 교과서 아니면 문학 시험지 지문으로..?? 잘 모르겠다.
참고로 저 글은 초판이 1977년에 나온 책에 소개되었고, 저자의 생몰년도가 1901-1987년이다. 그러니 저 글 역시 다루는 시점은 1930~40년대 정도이지 싶다. 문맹률이 아직 굉장히 높던 시절 말이다.

Posted by 사무엘

2020/07/01 08:35 2020/07/01 08:35
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1768

1. 멀티스레드

지난 날개셋 한글 입력기 9.9에서는 어쩌다 보니 스레드와 관련된 작업이 좀 있었다. 각종 대화상자에서 스레드 작업이 행해지던 것을 중단시켰을 때 프로그램이 멎던 문제를 해결했으며, 반대로 사용자의 타이핑에 실시간으로 반응하며 동작하는 일부 입력 도구에다가는 랙 없이 깔끔하게 동작하기 위해 스레드를 사용하게 하는 옵션을 추가했다. 뭐, 요즘 컴퓨터에서 랙을 걱정할 지경은 아니긴 하지만 말이다.

오랜만에 멀티스레드를 다루다 보니 지금까지 이론으로만 알고 있던 동기화라는 것도 구현해야 했다. worker 스레드는 입력 데이터를 지지고 볶고 열심히 건드리고 있는데, 그걸 표시하는 UI 스레드도 아무 통제 없이 같이 돌아가게 놔 두면 프로그램은 당연히 메모리 에러가 나고 뻗는다.

Critical section을 생성하고 소멸하는 부분은 클래스의 생성자와 소멸자에다 담당시켰는데, 이걸 이용해서 실제로 락을 걸고 푸는 것까지 또 다른 클래스의 생성자와 소멸자로.. 그렇다고 기존 오브젝트의 포인터를 명시적으로 지정하지 않고 자동 구현하는 것은 C++에서 안 되는가 보다. Inner class에서 바깥 class의 non-static 멤버에 접근 가능하지 않으니까..

임계 구간을 잘 설정해야 crash를 막을 수 있다. 하지만 그렇다고 개나 소나 마구 락을 걸어 버리면 성능만 떨어지는 게 아니라 반대로 deadlock이라는 부작용이 발생해서 프로그램의 응답이 멎어 버릴 수 있다. Crash는 사람이 다쳐서 의식을 잃는 것이고, deadlock은 사람이 수술 후 마취에서 못 깨어나는 것과 비슷하다..;;

주로 어떤 상황이냐 하면, UI 스레드가 worker 스레드의 실행이 끝날 때까지 기다리게 됐는데 worker 스레드는 데이터를 고친 뒤 UI 스레드의 응답이 필요한 요청을 해 버릴 때 이렇게 된다. 특히 윈도우에다가 메시지를 보내고 응답을 기다리는 API 함수를 호출하는 것 말이다.

이런 주의 사항을 염두에 두면서 프로그램의 주요 구간에 스레드 동기화를 시켜 놓았다.
당연한 말이지만 한 critical section 오브젝트는 단일 구간이 아니라 코드의 여러 구간에다가도 배치해서 이들 모든 구간을 통틀어서 한 순간엔 한 스레드만이 접근 가능하게 제약을 걸 수 있다. 이게 강력한 면모인 것 같다.

그러고 보니 Windows 프로그래밍 헤더에 들어있는 구조체들 중에는 내부 구조가 딱히 공개돼 있지 않은 것이 좀 있다.
가령, PAINTSTRUCT 내부에 있는 rgbReserved 같은 멤버, STARTUPINFO 내부에 있는 cbReserved2, 그리고 CRITICAL_SECTION도 어쩌면 CPU 아키텍처별로 크기와 내부 멤버가 제각각 다를 가능성이 높은 비공개 구조체이다.
이런 예가 무엇이 더 있을 텐데 궁금하다. 당장은 떠오르지 않는다.

2. C 라이브러리와 스레드 생성 함수

Windows에서 스레드를 생성하는 제일 저수준 API는 잘 알다시피 CreateThread 함수이다.
요즘이야 C++도 C++11부터 언어 라이브러리 차원에서 플랫폼· 운영체제를 불문하고 멀티스레드 기능을 std::thread, std::mutex 같은 클래스로 제공하고 있지만, 이들도 내부적으로는 물론 Windows API 같은 운영체제 API를 호출하는 형태로 구현된다. 심지어 native handle값을 되돌리는 멤버 함수도 제공한다.

(그러고 보니 C++ 라이브러리는 C 라이브러리와 달리 라이브러리의 소스가 공개돼 있지 않은 듯하다.. 소스가 어쩔 수 없이 공개되는 템플릿 쪽은 도저히 알아볼 수 없는 난독화 급으로 작성돼 있고, 내부적으로 호출하는 함수 같은 것은 구현체 소스가 딱히 보이지 않는다.)

C++11 이전에는 Visual C++의 경우, 언어 확장 명목으로 CreateThread (와 ExitThread)를 얇게 감싼 _beginthread[ex] (+ _endthread[ex]) 함수를 독자적으로 제공해 왔다.
얘는 Windows SDK의 헤더와 라이브러리를 끌어들이지 않고도 스레드를 사용할 수 있게 해 주는 동시에.. C 라이브러리가 자체적으로 스레드별로 사용하는 메모리를 제때 할당하고 제때 해제하는 역할도 담당했다.
그렇기 때문에 C/C++ 함수도 사용하면서 Windows용 프로그램을 개발한다면 일반적으로는 운영체제 직통 함수 대신 이런 상위 함수를 사용해서 스레드를 생성해야 한다.

C 라이브러리가 스레드별로 무슨 메모리를 사용하는가 하면.. 전역변수로 단순하게 구현되었지만 지금 관점에서 보면 스레드별로 값이 제각기 보존되어야 하는 정보들 말이다. errno라든가, strtok 함수의 내부 state 및 context.. 비록 이런 것에 의존하는 함수가 그리 많은 건 아니지만 어쨌든 그런 예가 있다.

C 라이브러리에서 제공하는 begin/end 함수는 ex가 붙은 놈과 그렇지 않은 단순한 놈으로 나뉜다. ex는 받아들이는 인자가 Windows API의 그것과 완전히 동일하다. 그에 비해 단순한 놈은 평소에 잘 쓰이지 않는 인자들을 과감히 생략했기 때문에 사용하기가 더 쉽다.

예를 들어 begin 버전은 SECURITY_ATTRIBUTES 정보를 입력받는 부분이 생략됐고, 거의까지는 아니어도 잘 쓰이지 않는 thread ID를 받아들이는 부분도 생략됐다. 또한, 프로세스도 아니고 스레드의 실행 후 리턴값은 거의 쓰이지 않는다는 점을 감안하여 end 버전은 리턴값을 지정하는 부분도 생략되고 그냥 0으로 고정됐다.

참고로 Windows NT의 경우 ReadFile/WriteFile에서 실제로 읽거나 쓰는 데 성공한 양을 받는 DWORD 포인터, 그리고 CreateThread에서 생성된 스레드의 ID를 받는 DWORD 포인터를 생략하고 NULL을 줄 수 있다. 굳이 그 정보들이 필요하지 않다면 말이다. 하지만 과거의 Windows 9x는 저들 함수의 인자에서 NULL을 줄 수 없으며, 그냥 잉여 변수라도 하나 반드시 선언해서 넘겨줘야 한다는 차이가 있었다(NULL 주면 실행 실패).

뭐 그렇긴 한데.. C 라이브러리의 간편 버전은 사용법을 간소화하기 위해 여러 인자들을 생략했을 뿐만 아니라, 스레드 핸들을 CloseHandle로 닫는 것까지 임의로 자동으로 해 버린다. 스레드가 아주 잠깐 동안만 실행됐다가 종료돼 버리는 경우, _beginthread 함수의 리턴값이 나중의 시점에서 유효하다는 보장을 할 수 없다.

이건 사족에 가까운 간소화 조치로 여겨지며, 이 때문에 _beginthread는 사용이 그리 권장되지 않고 있다. 아니면 정말 뒷감당 할 필요 없고 한번 생성된 뒤에 “나는 책임 안 지니 니가 알아서 모든 뒷정리 하고 곱게 사라져라” 급인 스레드를 간편하게 생성하는 용도로나 적합하다. 그게 아니면 번거롭지만 결국은 _ex 함수를 쓰고 핸들은 내가 수동으로 닫아 줘야 한다.

3. 실행 주체들의 종료 리턴값

프로세스와 스레드는 종료될 때 정수를 하나 되돌릴 수 있는데, 아무 일 없이 잘 끝났으면 간단히 0을 되돌리는 게 관행이다. 옛날에 도스에서는 프로세스의 종료 코드를 ERROR_LEVEL이라는 독특한 명칭으로 불렀었다.

프로세스의 종료값은 그럭저럭 고유한 용도가 있으며, 여러 프로그램들을 순차적으로 실행하는 배치 파일이나 스크립트 같은 데서 많이 쓰인다. 종료되고 없는 먼젓번 프로그램의 실행이 성공했는지 여부를 밖에서 알 필요가 있기 때문이다.

하지만 앞에서 잠시 언급한 바와 같이.. 스레드의 종료값은 내 경험상 거의 쓰이지 않는다. 스레드의 종료값을 읽고 판단할 수 있는 코드 자체가 동일 프로세스 안의 다른 스레드밖에 없고.. 한 프로세스 안에서는 굳이 종료값 말고도 스레드의 실행 결과를 알 수 있는 방법이 매우 많기 때문이다. 애초에 같은 메모리 주소 안이니..

또한 스레드는 애초에 여러 작업을 동시에 수행하라고 만들어지지, 배치 파일처럼 여러 스레드를 순차적으로 실행하면서 실행 결과를 확인하는 식으로 운용되지도 않는다. 그럴 거면 그냥 한 스레드에서 순차 실행을 구현하고 말지.. 프로세스와는 여러 모로 사용 여건이 다르다.

그 와중에 Windows에서는 259라는 값이 STILL_ACTIVE라는 의미로 예약돼 있는지라.. 프로세스나 스레드의 종료 리턴값으로 쟤를 돌려주는 것이 금지되어 있다. 자기는 종료되어서 없는데 GetExitCodeProcess/Thread 함수가 저 값을 되돌리고 있으면 다른 프로세스나 스레드는.. 없는 프/스의 실행이 끝날 때까지 기다리면서 데드락 같은 상태에 빠지기 때문이다.

4. 타 프로세스에다 내 스레드 생성하기: CreateRemoteThread

Windows API 중에는 기본적인 기능을 제공하는 함수가 있고, 거기에 덧붙여 어느 프로세스 문맥에서 그 기능을 수행할 것인지를 지정하는 인자가 추가로 붙은 Ex버전이 또 존재하는 경우가 있다.
예를 들어 ExitProcess(현재 프로세스만)의 상위 호환인 TerminateProcess, 그리고 VirtualAlloc에다가 HANDLE hProcess가 추가된 VirtualAllocEx처럼 말이다.

그것처럼 스레드를 생성하는 CreateThread에 대해서도 target 프로세스를 지정할 수 있는 CreateRemoteThread 함수라는 게 있다.
이런 함수들은 내부적으로 구현도 하위 함수를 호출하면 상위 함수에다가 GetCurrentProcess()의 리턴값을 붙여서 호출하는 형태로 돼 있다.

그런데 남의 프로세스 주소 공간에다가 스레드를 생성한다니??
이건 물론 디버거나 보안 솔루션 같은 특수한 프로그램에서나 필요하지 자기 일만 하는 일반적인 프로그램에서 쓰일 일은 없는 기능이다.
그리고 이 함수는 각종 명령 인자들을 준비하는 게 몹시 까다롭기 때문에 타 프로세스에서는 매우 제한된 형태로밖에 활용을 할 수 없다.

제일 근본적으로는.. 실행되어야 할 스레드 함수 코드와 실행에 필요한 데이터들이 몽땅 그 target 프로세스의 메모리에 마련돼 있어야 하며 주소값 또한 걔네들의 문맥으로 줘야 하기 때문이다. 잘못되면? 요청을 한 우리가 아니라 멀쩡히 돌아가던 저 프로세스가 뻑나게 된다.
global hook 프로시저를 지정할 때처럼 DLL에다가만 분리해서 구현해 놓으면 그 DLL이 알아서 거기로 주입.. 그런 것도 없다.

다만, CreateRemoteThread를 이용해서 타 프로세스에다 내 코드를 주입하려면 어차피 DLL을 만들기는 해야 한다. 그 이유는 이렇다.
CreateRemoteThread로 할 수 있는 게 사실상.. 스레드 callback 함수로 LoadLibrary를 지정하는 것밖에 없기 때문이다.

이게 가능한 이유는 (1) LoadLibrary(+ FreeLibrary도)가 machine word 하나를 받아서 word 하나를 되돌린다는.. 스레드 callback 함수와 prototype이 일치하기 때문이며, 그리고 (2) LoadLibrary가 소속된 kernel32.dll은 어느 프로세스에서도 메모리에 load된 주소가 동일 고정이기 때문이다. 즉, LoadLibraryA/W 함수의 주소도 고정이며 예측 가능하다. 요게 핵심이다. 요즘 보안을 위한 대세인 ASLR에서 열외된 영역인 듯..

물론 LoadLibrary에다가 주는 인자는 평범한 숫자가 아니라 포인터이므로 넘겨줄 때 조심해야 한다.
VirtualAllocEx를 이용해서 그 프로세스 문맥에서의 메모리와 포인터 주소를 얻은 뒤, 문자열을 그쪽으로 써 넣을 때도 WriteProcessMemory를 호출해서 하면 된다. 얘는 그야말로 memcpy를 fwrite처럼 구현한 상위 호환 버전이라고 생각하면 되겠다.

LoadLibrary에다가 넘겨주는 문자열 인자로 바로 상대방의 프로세스에서 수행할 작업(뭔가 엿보기, 훅킹하기 등등)이 구현된 내 DLL의 주소를 넣으면 된다. DllMain 함수에서 돌아가는 것이니 몸 사리면서 조심해서 실행돼야 한다.
혹시 내 DLL을 실행하면서 다른 DLL을 읽어 놓아야 한다면 내 DLL을 주입한 것과 동일한 테크닉으로 그런 dependency DLL들을 미리 읽어 놓는 게 좋을 것이다. LoadLibrary를 수행하던 스레드가 실행이 끝났다고 해서 그 DLL들이 해제되는 건 아니기 때문이다.

주입되었던 내 DLL을 빼내는 것도.. 간단하다.
스레드 함수에다가 FreeLibrary를 주고, 인자로는 저 target 프로세스에서 되돌려진 내 DLL의 핸들값을 주면 된다.
target 프로세스에서 되돌려진 핸들값이야 내 DLL의 DllMain 함수의 인자로 들어왔을 테니 모를 수가 없을 테고, 그걸 호스트에다가 전하는 건 어려운 일이 아닐 것이다.

이런 식으로 저 함수에다가 LoadLibrary 대신 ExitProcess를 주면 그 프로세스가 알아서 자기 자신을 강제 종료하게 되니, 사실상 TerminateProcess와 같은 효과도 낼 수 있다.

16비트 시절에는 단일 address 공간에서 시스템 전체에 영향을 끼치는 프로그램을 만드는 게 용이했기 때문에 그에 상응하는 점프/복귀 주소 변조, x86 어셈블리어 삽입 같은 꼼수 테크닉이 유행했었다. 그러나 32비트 이후부터는 방법론이 이렇게 더 고차원적으로 확 바뀌었다.;;

Posted by 사무엘

2020/06/28 08:36 2020/06/28 08:36
,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1767

4색 정리, 정사각형 분할 문제

일명 “4색 정리, 4색 문제”는 개념 자체는 마치 페르마의 마지막 정리처럼 초등학생도 이해할 수 있을 정도로 아주 단순하다. 하지만 엄밀한 증명은 20세기의 현대 수학자조차도 감당하지 못할 정도로 난해했던지라, 1976년이 돼서야 컴퓨터의 brute-force 계산 능력의 도움을 받아 간신히 증명됐다.

사용자 삽입 이미지

요즘으로 치면 무슨 머신 러닝을 돌리듯이 당대의 슈퍼컴 두 대를 장정 50일을 돌리면서 가능한 모든 지도 모델에서 증명이 성립함을 확인했다고 한다.
물론 저건 오늘날의 머신 러닝에 비할 바는 못 된다. 내 기억이 맞다면, 1970년대 중반의 크레이 슈퍼컴퓨터는 20여 년 뒤에 등장하여 클럭 속도가 GHz급에 도달한 펜티엄 3~4급 PC와 얼추 비슷한 성능이었다. 요즘 PC라면 GPU 세팅만 잘 하면 하루는커녕 길어야 수십 분~몇 시간이면 시뮬레이션이 끝나지 싶다. 그만치 세상이 많이 변했다.

하지만 1976년은 지금으로부터 무려 45년 가까이 전의 과거이다. 증명할 지도 모델을 설계하고 계산량을 그 시절 컴퓨터로 감당 가능하게 최소화한 것만으로도 독창적인 학술 공로이며, 대학교 수학과 교수 급의 전문가가 아니면 할 수 없는 일이었다.
또한 극도로 비싸고 귀하신 몸이던 슈퍼컴을 당장 실용적으로 필요하던 일기예보나 모의 핵실험, 탄도 예측(?)이 아닌 학술 연구용으로 끌어들여 온 것 역시 해당 연구자의 행정력과 근성과 로비 덕분이었던 셈이다.

한편, 정사각형을 크기가 서로 다른 작은 정사각형들로 분할하는 문제(lowest-order perfect squared squares)도 굉장히 난해하지만 답이 존재는 하는 굉장히 기묘한 문제인데.. 4색 정리와 비슷하다면 비슷한 시기인 1978년에 길이 112짜리 사각형을 21개로 분할하는 해법이 발견됐다. 이 역시 컴퓨터를 동원하여 찾아낸 것이었다.

사용자 삽입 이미지

또한, 저 정사각형들도 최대 4개의 색만으로 서로 경계를 구분하여 칠할 수 있을 테니, 이것도 4색 문제하고 관계가 있다고 볼 수 있겠다. =_=;;
1982년에는 동일 연구자의 후속 연구를 통해 저게 이론적으로 존재 가능한 optimal 내지 lower bound라는 것도 증명됐다. 즉, 20개 이하의 서로 다른 정사각형으로 큰 정사각형을 꽉 채우는 방법은 존재하지 않는다는 것이다.

다만, 가장 작은 정사각형 분할의 하한은 112가 아니라 110이라고 한다. 분할 개수는 21개보다 딱 1개 더 많은 22개이다. 참으로 신기한 노릇이다.

사용자 삽입 이미지

그 전에 컴퓨터의 도움 없이 사람이 찾아낸 가장 단순한 정사각형 분할은 175를 24개로 분할하는 것이었다. (81, 55, 39, …) 1946년에 데오필루스 윌콕스(1912-2014)라는 영국 사람이 발견했다.

사용자 삽입 이미지

정사각형을 서로 다른 정사각형으로 분할하는 방법 자체는 다양한 크기별로 무한히 존재하기라도 하는지? 이게 증명돼 있기라도 한지는 모르겠다. (자명한 닮은꼴은 물론 제외. 작은 정사각형의 크기값들이 모두 서로 소인 것으로 한정)

단지 2차원이 아니라 3차원에서 정육면체를 서로 크기가 다른 정육면체로 꽉 맞게 채운다거나 그 이상의 차원에서 같은 방법으로 hypercube를 채우는 방법은 아예 존재하지 않는다고 한다.
직관적으로 생각해도 명확한 것이... 3차원만 생각해 봐도 그런 정육면체가 있다면 여섯 면이 다 제각기 크기가 서로 다른 정사각형으로 거대한 정사각형을 이룬 모습을 기본적으로 하고 있어야 할 것이다. 하지만 정육면체만으로 그렇게 되는 것은 불가능하기 때문이다.

벡터의 외적이 딱 3차원 벡터에 맞게 존재하는 이항연산이듯이, a^n+b^n=c^n의 정수해가 존재하는 n의 상한이 딱 2인 것처럼.. 정사각형 분할은 딱 2차원 평면에서 존재 가능한 절묘한 문제인 것 같다.

Posted by 사무엘

2020/06/25 19:38 2020/06/25 19:38
, ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1766

« Previous : 1 : ... 55 : 56 : 57 : 58 : 59 : 60 : 61 : 62 : 63 : ... 221 : Next »

블로그 이미지

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

- 사무엘

Archives

Authors

  1. 사무엘

Calendar

«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31        

Site Stats

Total hits:
3046549
Today:
1769
Yesterday:
1972