1.
인텔 CPU의 역사를 살펴보자면.. 1971년에 무려 4비트짜리로 나온 4004가 최초의 상업용 마이크로프로세서라고 여겨진다. 그 뒤 72년에 8비트 8008이 나오고, 1978년의 16비트 8086이.. 오늘날까지 이어지는 x86 아키텍처의 서막을 열었다.

8086, 80186, 80286은 모두 16비트 CPU이다. 186은 PC에서는 거의 쓰이지 않았고, 286은 이론적으로는 보호 모드와 멀티태스킹까지 지원하는 물건이었지만 구조적인 한계 때문에 소프트웨어에서 실제로 제대로 활용되지는 못했다.

086에서 286으로 넘어가는 과정에서는 그냥 CPU의 클럭 속도만 올라가고, IBM PC 규격 차원에서 XT/AT의 차이가 더 컸던 것 같다. 가령, 하드디스크 탑재라든가 고밀도 디스켓 지원 말이다. 키보드의 반복 속도 조절 기능도 내 기억이 맞다면 AT부터 지원되기 시작했다.

무려 1985년, 아직 VGA도 없던 시절에 80386 CPU가 개발되어서 IA32라는 아키텍처가 완성되긴 했다. 하지만 이때는 컴퓨터의 가격이 너무 비싸서 32비트가 가정용으로 보급되기는 곤란했다.
나중에 외부 데이터 버스를 32 대신 16비트로 줄여서 가격을 좀 낮춘 보급형 386SX라는 게 등장했다. 훗날 등장한 펜티엄은 반대로 그 버스의 크기가 64비트로 머신 워드 크기보다도 더 커졌으니, 386SX와 좋은 대조를 이룬다.

또한 386 때부터 슬슬 캐시 메모리가 쓰이기 시작했으며, 486에서부터는 부동소수점 프로세서(FPU)가 기본 내장으로 들어가기 시작했다. 클럭 속도의 증가는 덤이다.
486 이후로는 인텔이 숫자 명칭 대신 '펜티엄'이라는 자체 브랜드명을 사용하기 시작했고, 펜티엄 다음으로는 코어.. 코어 안에서는 네할렘, 샌디브릿지 같은 세부 공정이 달라질 때마다 새로운 명칭을 붙여서 제품을 구분하고 있다.

2.
2000년대 중반, 딱 Windows XP와 IE6이 장수하던 05~06년 사이에 멀티코어와 64비트가 도입되면서 PC의 환경이 20세기 시절과는 크게 달라진 듯하다. 둘은 도입 시기가 완전히 일치하지는 않지만 미묘하게 비슷하다. 펜티엄 4의 후기형을 거쳐서 펜티엄 D에서부터 싱글코어 기반의 x86-64가 정착했으며(정확히는 2003~04년 사이), 반대로 Core 1 Duo는 32비트 전용의 첫 멀티코어 프로세서였다.

그러다가 둘이 합쳐져서 Core 2 Duo가 64비트 + 2개짜리 멀티코어 시대를 열었다. 운영체제는 Windows Vista/7부터 말이다.
사실 Core 1 Duo는 PC용으로는 출시도 되지 않고 모바일용으로 나왔는데, 애초에 x86이 모바일에 적합한 구조의 아키텍처가 아니다 보니 존재가 모순적이었다. 그러니 별 재미를 못 보고 단종됐다.

CPU가 그렇게 바뀐 동안 모니터는 LCD와 와이드가 도입되었다. 옛날에는 4:3 비율의 액정 모니터도 있었지만 2000년대 중후반쯤에 자취를 감춘 듯하다.
요즘은 형광등이 처음 켜질 때 깜빡거리는 걸 볼 일이 없어진 것처럼.. CRT TV나 모니터를 처음 켤 때 화면이 예열과 함께 천천히 fade in 되는 모습도 볼 일이 없어졌다.

또한 기분 탓인지는 모르겠지만, 예전에는 모니터의 테두리 색깔이 흰색이 많았는데 와이드 화면 모니터는 검은색이 주류가 된 것 같다.

3.
인텔 CPU가 초창기에 저렇게 발전해 온 동안, 우리나라는 역사적으로 국가에서 나서서 전국민에게 PC를 보급한 적이 딱 두 번 있었다. 전자는 그 말 많던 "교육용 PC" 사업이고(1980년대 말), 후자는 그로부터 10년쯤 뒤, IMF에다 세진 컴퓨터 랜드가 아직 있고 인텔 펜티엄 2, 셀러론 이러던 시절의 국민 PC 사업이다.

전자의 사업 때 이미 많이 보급돼 있던 MSX니 SPC니 하는 8비트들을 싹 배제하고 과감하게 16비트 IBM 호환 PC를 지정한 것은 마치 철도 표준궤와 220V 전압만큼이나 미래를 내다본 굉장한 선견지명이었다. 결국은 그 PC 계열이 천하를 평정했기 때문이다. 1980년대 당시에 정보통신부나 과학기술처의 담당 관료가 중대한 결정을 잘 내렸다.

뭐, 8비트 컴터들은 대체로 화면 해상도가 낮고 성능도 떨어져서 당장 한글· 한자 처리에 애로사항이 너무 크긴 했다. 그 문제 때문에 한국· 일본은 16비트 컴에서 비디오 카드조차도 허큘리스에서 거의 곧장 VGA로 갈아탔지, 서양처럼 CGA/EGA를 진지하게 경험하지는 않았으니 말이다.

지금이야 PC는 너무 흔해 빠지고 기업의 입장에서는 이윤도 별로 안 남아서 하나 둘 철수할 지경이 돼 있다. 남사스럽게 PC에 연연할 필요 없이 폰이 다 보급돼 있고.. 당장 돈이 없어도 온갖 할부 제도를 이용해서 뿌리다시피하고 있다. 저 시절의 컴퓨터와는 비교할 수 없을 정도로 더 성능 좋고 작은 컴퓨터를 전화기에다가 얹어서 들고 다니는 게 경악스럽게 느껴질 지경이다.

4.
지금까지 CPU 얘기가 나왔으니 말인데,
문자 인코딩을 CPU 명령의 인코딩에다 비유하자면, UTF-8은 CISC에, UTF-16이나 32는 RISC에 딱 대응하는 것 같다.
원래 UTF-8은 그 구조상 5~6바이트까지도 늘어나서 U+10FFFF보다 더 큰 코드값도 기록이 가능은 하다. 하지만 언제부턴가 인코딩 규칙이 개정되어서 5~6바이트짜리는 현재로서는 고이 봉인하고, 1~4바이트까지만 사용하기로 했다.

오늘날 국내외의 컴덕이나 프로그래머들 중에는 UTF-8을 완전 만능으로 칭송하는 한편으로 UTF-16은 거의 사회악 쓰레기 수준으로 싫어하는 사람이 종종 눈에 띈다. 프로그래밍 배경이 Windows가 아닌 유닉스 계열인 사람, 그리고 특히 wchar_t의 플랫폼별 파편화 때문에 삽질과 고생을 단단히 한 사람일수록 그런 성향이 더욱 강하다.

본인은 주장의 논지는 이해하지만 그 정도까지 부정적인 견해에는 공감하지 않는다.
컴퓨터에서 어떤 데이터를 주고받기 위해서는 결국은 값을 그대로 전하든지, 아니면 좀 덩치가 큰 데이터는 별도의 메모리에다가 저장해 놓고 그 메모리 주소만 전하든지.. 둘 중 하나를 선택해야 한다. 32비트니 64비트니 하는 건 그 컴퓨터의 CPU가 한번에 취급하는 그 정보의 크기 단위이다.

문자 하나를 전하기 위해서 일일이 메모리 할당해서 문자열을 만들고 포인터를 전달하느냐, 아니면 그 문자의 코드 포인트 값만 간단하게 전하느냐.. 이게 얼마나 큰 차이인지는 프로그램 좀 짜 본 사람이라면 누구나 공감할 것이다.

그 와중에 옛날 사람들이 UTF-16이라는 계층의 존재를 예상 가능했던 것도 아니고, 1990년대에 메모리가 지금만치 풍부하고 저렴했던 것도 절대 아니고, 그저 모든 글자의 크기를 2바이트로 균일하게 늘리는 것만으로도 메모리를 너무 많이 잡아먹네 하던 시절에.. UTF-8도 아니고 UTF-32도 아닌 적당한 절충안인 UTF-16 내지 그 전신 UCS-2가 과연 그 정도로 태어나지 말았어야 한 존재인 걸까? 그게 아니라는 것이다.

내가 보기에 이건 유니코드에 현대 한글 글자마디 11172자가 일일이 다 등록된 게 잘못된 거라고 비판하는 것과 비슷해 보인다. 그렇게 등록을 안 했으면 글꼴을 만들기가 훨씬 더 복잡하고 어려워지고, DB 문자열 필드나 파일명 같은 데에 집어넣을 수 있는 한글 글자 수가 크게 감소했을 텐데 말이다.

문득 Windows가 오로지 65001 UTF-8만으로 천하통일이 이뤄지고.. 심지어 9x 시절처럼 W가 아닌 A 함수가 주류로(그 대신 UTF-8 기반으로!) 회귀하는 엉뚱한 상상을 해 본다. 물론 실현 가능성은 사실상 0일 것이다. =_=;;
Windows의 WCHAR뿐만 아니라 macOS의 NSString, Java의 Char과 jstr, COM의 BSTR 등 많은 운영체제와 프레임워크들은 2바이트를 문자의 기본 단위로 사용하고 있으니 어차피 이걸 쉽게 벗어날 수 있지도 않다.

5.
컴퓨터에서 일상적으로 볼 수 있는 보조 기억 장치는 결국 (1) 자기 디스크, (2) 플래시 메모리, (3) 광학 디스크 이 세 범주 중 하나로 귀착된다. 또 완전히 새로운 범주가 개발될 여지가 있으려나 모르겠다.
용량과 속도 가성비가 "전반적으로" 제일 뛰어난 건 역시나 자기 디스크이다 보니, 얘를 기반으로 한 '하드디스크'는 가히 유구한 역사를 자랑한다. 기계식, 물리적인 요소가 존재하는 장치임에도 불구하고 오늘날까지도 컴퓨터에 여전히 건재하다.

플래시 메모리는 PC에서는 USB 스틱 아니면 SSD의 형태로 요긴하게 쓰이고 있다. 동작 중에 일체의 소음과 진동이 없는 순수 전자식이며, RAM과 보조 기억 장치의 경계를 허물 차세대 주자로도 각광받는 물건이다. 하지만 가격 때문에 하드디스크를 완전히 대체하는 건 여전히 무리이다.

마지막으로 광학 디스크인 CD/DVD/블루레이는 매체의 외형부터가 빛을 반사하는 새끈한 재질인 게 굉장히 간지 나고 미래 지향적으로 보인다. 하지만 20여 년 전에 40배속인가 뭔가에서부터 읽기 속도가 한계에 달했으며, 쓰기를 마음대로 할 수 없다는 치명적인 한계 때문에 쓰임이 반쪽짜리가 됐다.

USB 메모리와 초고속 인터넷 파일 전송, 가상 디스크 마운트 기술에 밀려서 광학 디스크를 사용할 일이 예전에 비해 극히 드물어진 것이 사실이다. 이제는 부팅조차도 USB 메모리만으로 가능해질 정도가 되기도 했고 말이다.

옛날에는 레이저를 사용하는 컴퓨터 주변 기기들이 굉장히 비쌌다. CD 라이터라든가 레이저 프린터 말이다. 이런 것들이 개인이 쉽게 보유할 정도로 흔해진 건 이르게 잡아도 1990년대 말이고 21세기에 와서부터이다.
또한 얘들은 다 열을 많이 가하는가 보다. 레이저 프린터만 해도 종이를 고온 고압을 가해서 토너가루를 붙이는 식으로 인쇄하는데(그래서 타 인쇄 방식에 비해 전기도 많이 씀), 광학 디스크에다 기록하는 것도 한국어· 영어 공히 '굽다/BURN'이라고 표현할 정도로 비슷한 메커니즘을 동원하는 듯하다.

여담이지만, 자기 디스크는 영어 철자가 disk이고, 광학 디스크들은 철자가 disc라는.. 미묘한 차이가 있다.

6.
터치스크린은 기존 키보드와 마우스를 완전히 대체하지는 못하지만, 그래도 모니터를 출력 장치뿐만 아니라 입력 장치도 겸하게 해 주는 깔끔하고 참신한 인터페이스임이 틀림없다. 단순히 버튼을 콕콕 찍어서 선택하거나 간단한 필적을 그리는 용도로 아주 좋다.

터치스크린을 구현하는 방식은 크게 감압식과 정전식으로 나뉜다. 감압식은 물리적인 압력을 감지하는 방식이고, 정전식은 그게 아니라 표면의 전기 신호의 변화를 감지하는 방식이다.
이게 마우스로 치면 제각기 볼 마우스와 광 마우스에 대응하는 것이나 마찬가지로 보인다. 전자가 좀 기계식이고 후자는 말 그대로 전자식이다.

처음에는 전자와 후자가 장단점이 서로 호각인 지경인데, 기술적인 구현 난이도는 후자가 더 높았다. 하지만 세월이 흐르면서 지금은 결국 기술적인 한계가 극복되고 후자의 장점이 더 부각된 덕분에, 후자 방식이 주류 대세가 되었다. 이런 변화 양상도 마우스와 터치스크린이 서로 동일하다.

엘리베이터 버튼 중에도 오로지 사람의 생 손가락만 인식하고 타 물체 내지 장갑 낀 손가락은 인식하지 않는 게 있는 게 개인적으로 신기한 한편으로 잘 이해가 되지 않았다. 마치 광 마우스는 유리판 위에서는 좀체 동작하지 않는 것처럼 말이다.
그렇게 생 손가락만 인식하는 센서들은 다 정전식이다. 감압식이라면 무슨 물체를 쓰든지 버튼을 누른 건 다 인식돼야 할 것이다.

정전식은 감압식보다 터치를 더 부드럽게 인식할 수 있으며 특히 마우스가 결코 흉내 내지 못하는 멀티터치를 구현하는 게 더 유리하다.
Windows 98에서 마우스 휠이 정식 지원되기 시작했다면 지난 Windows 7에서 터치 장비가 정식으로 지원되기 시작했다. 안 그래도 7은 그림판이 크게 개선되어서 초보적이나마 브러시 엔진까지 도입됐는데, 여러 손가락으로 동시에 태블릿을 긁으면서 그림을 그리던 시연 모습이 인상적이었다.

하지만 본인은 데스크톱/노트북급에서 화면이 터치스크린을 지원하는 장비는 10년째 한 번도 못 봤다. 장비를 주위에서 쉽게 접할 수 있었다면 날개셋 한글 입력기에도 멀티터치 같은 걸 연계한 입력 도구를 구현할 생각이라도 했을 텐데 그건 지금까지도 그냥 장기 계획으로만 머물러 있다.

그러고 보니 이런 터치 장비는 좌표뿐만 아니라 압력 정보까지 전할 수 있다.
다만, 얘들은 올록볼록 입체적인 점자를 표현하지 못하니 터치스크린 기반 UI는 장애인과는 그리 친화적이지 못한 인터페이스이다. 이건 뭐 어쩔 수 없는 귀결이다. 시각 장애인 내지 손가락을 자유롭게 움직이지 못하는 사람은 스마트폰도 여전히 버튼식 폴더 형태로 된 기기를 써야 한다.

7.
자동차에 경차라는 차급이 있고 총기 중에도 제일 작은 권총이라는 게 있듯, 컴퓨터계에서 제일 작은 놈은 넷북이지 싶다. 정말 작고 아담해서 들고 다니기 편하며 값도 저렴하다. 부담 없이 인터넷과 문서 작업만 하는 용도로는 참 좋다.

하지만 얘는 그만큼 CPU의 성능이 매우 뒤떨어지고 화면 해상도도 너무 낮으며, 키보드 역시 적응이 힘들 정도로 너무 작은 편이다. 그렇기 때문에 얘로 단순히 글자판떼기 치기 이상으로 다른 생산적인 일을 하기에는 애로사항이 많다. (프로그래밍, 그래픽 디자인 등등..) 아니, 사람에 따라서는 키보드의 구조 때문에 단순 글자판떼기 치기조차도 불편하게 느껴질 수 있다.

게다가 2010년대부터는 PC가 아닌 스마트폰 운영체제에 기반을 둔 각종 태블릿 판떼기들이 급속히 발전한 덕분에, 단순 휴대용 인터넷 단말기 및 게임기라는 수요는 사실상 거기로 다 흡수됐다. 그러니 단순히 노트북 PC를 경차급으로 줄인 넷북이라는 건 사실상 존재 의미를 상실하고 오히려 그 태블릿들이 필요에 따라서는 키보드를 연결해서 쓸 수도 있는 형태가 됐다.

물론 터치스크린은 기존 키보드와 마우스를 결코 완전히 대체할 수 없으며, 정보의 소비와 열람이 아니라 정보를 생산하는 도구로서 PC의 지위는 예나 지금이나 변함없다. 또한, 넷북이 없어진다고 해서 넷북의 용도 내지 걔들이 수행하던 작업 자체가 없어지는 건 아니다. 휴대용 컴퓨터는 좀 더 모바일 기기와 결합한 형태로 변모하고, 전통적인 PC는 자기 역할에 특화되는 쪽으로 가는 듯하다.

8.
1990년대 초반

  • 86키 키보드는 이제 거의 도태하고 101키 키보드가 대세가 됐다. 옛날 키보드는 F11, F12가 없으며, 기능 키 F1~F10이 맨 왼쪽에 2열 5행으로 세로로 배치돼 있었다. 지금의 capslock 자리에 ctrl이 있고 capslock은 지금의 우alt/ctrl 자리에 있었다. 키패드에서 우측 하단인 지금의 엔터 자리에 더하기가 있었다.
  • 옛날에 키보드는 정체를 알 수 없는 이상한 전용 포트에다 꽂았으며 마우스는 모뎀과 같은 COM.. 직렬 포트에다 꽂았다. 프린터는 병렬 포트에 꽂았고.. 모뎀과 마우스의 충돌은 정말 대표적으로 골치아픈 문제였다.
  • Plug & play도 없고 USB도 없던 시절이니, 외장 하드디스크를 연결해서 인식시키는 것만 해도 바이오스 설정을 바꾸는 등 정말 고도의 컴터 지식이 필요한 작업이었다.

1990년대 중반

  • 좋은 그래픽 카드를 사용하면 화면이 바뀌는 곳에서도 마우스 포인터가 깜빡거리지 않기 시작했다. 단, 흑백 기본 포인터 한정으로. custom 포인터는 여전히 깜빡거렸다.
  • 486쯤부터는 컴퓨터 본체가 모니터 밑받침으로 까는 형태가 아니라 모니터 옆에 세워 놓는 형태로 거의 정착했다. 하지만 Windows의 '내 컴퓨터' 아이콘은 XP에 가서야 이 모양을 반영하는 형태로 바뀌었다.
  • 486/펜티엄급 컴에서 WinAMP로 128kbps급 mp3를 하나 재생하면 CPU 점유율이 10~20%가량 올라가곤 했다.

1990년대 후반

  • 시스템 종료 후에 컴퓨터가 자동으로 꺼지기 시작했다. "이제 컴퓨터를 끄셔도 안전합니다"라는 주황색 글자를 사용자가 직접 볼 일이 없어졌다.
  • Windows 98쯤부터 멀티웨이브가 가능해졌다. 지금으로서는 정말 믿어지지 않지만, 원래 옛날에는 한 프로그램에서 사운드를 출력하기 시작하면 다른 프로그램에서 사운드를 사용할 수 없었다!

1999~2000 사이

  • 컴퓨터 규격이 크게 바뀌었다. 그 이름도 유명한 USB 포트라는 게 등장했고, 키보드와 마우스용 초록색-보라색 PS/2 포트도 등장했다.
  • 전원을 3초 이상 꾹 눌러야 꺼지는 관행도 이때부터 정착했다.
  • 사운드카드의 스피커가 이제 컴터 본체에 내장되지 않기 시작했다.
  • 가정에서도 모뎀 대신 인터넷 전용선이 슬슬 보급되기 시작했다.

2000년대

  • 이제 custom 마우스 포인터도 깜빡이지 않기 시작했다. 사실 Windows 2000은 9x와 달리, 16색 VGA 구닥다리 안전 모드에서도 마우스 포인터가 깜빡이지 않는 게 개인적으로 굉장히 신기했다.
  • 컴퓨터에서 오디오 CD의 음원을 추출하는게 옛날에는 쉽지 않았는데 이제는 손쉽게 가능해졌다.
  • USB 메모리가 디스켓을 확실하게 골로 보냈으며, 무선 인터넷과 합세하여 CD의 지휘조차 위협한다. 호각인 라이벌은 엄청난 용량을 자랑하는 하드디스크뿐..
  • PS/2포트조차 한물 가고 키보드와 마우스도 그냥 USB 기반으로 나오기 시작했다.
  • Windows Vista부터는 동영상 화면도 일반 화면과 아무 차이 없이 print screen으로 캡처 가능해졌다.

Posted by 사무엘

2018/12/06 08:34 2018/12/06 08:34
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1562

1. COM

1970년대 중반, MS-DOS의 전신격인 CP/M 때부터 있었던 완전 초창기 실행 파일 포맷이다. 고안자는 개리 킬달.
엄밀히 말해, 얘는 파일 포맷이라 할 것도 없는 쌩 메모리 이미지 덤프였다. 그 어떤 고유한 헤더나 메타데이터도 없이 그냥 곧장 기계어 코드와 데이터가 쭉 이어질 뿐이었다. 코드와 데이터는 모두 64KB 단일 세그먼트에 묶여 있었고, 메모리 주소의 첫 256바이트는 시스템 용도로 예약되어 있어서 프로그램이 사용할 수 없었다.

확장자가 com인 실행 파일은 그냥 명령 프롬프트에서 돌아가는 간단한 유틸밖에 없을 것 같지만, 그래도 겨우 몇만 바이트 남짓한 com 형태로 그래픽 모드에서 실행되는 게임도 많이 나왔었다. 그래픽이라고 해 봤자 320*200 4색 CGA 수준이긴 했지만.. Alley Cat처럼 말이다.
1980년대에 들어서 컴퓨터의 대세가 8비트에서 16비트로 넘어가고 성능과 메모리 용량이 향상되자, 이 형식은 큰 프로그램을 만들기에는 너무 비좁아졌다. 그래서 확장할 필요가 생겼다.

2. MZ EXE

1983년, MS-DOS 2.0에서 최초로 도입됐다. 그 전의 1.0은 파일 시스템에 서브디렉터리라는 게 지원되지 않았으며 실행 파일도 아직 COM밖에 없었다.
EXE는 단일이 아닌 다중 세그먼트(특히 코드 영역과 데이터 영역의 분리)를 도입하여 64KB 공간 한계를 얼추 극복했다. 메모리 모델이니, far near 포인터니 뭐니 하면서 일이 굉장히 복잡해지긴 했지만 말이다.
또한, 멀티태스킹 환경에 대비해서 재배치 정보도 도입했다. 이제 좀 운영체제에서 파일을 있는 그대로 메모리에 올리는 게 아니라 최소한의 가공과 상대 주소 보정을 하는 loader가 필요해졌다.

오늘날 모든 EXE들은 앞부분에 MZ라는 문자로 시작하는 간단한 헤더를 갖추고 있다. MZ는 EXE 파일 포맷의 설계자인 당시 마소의 프로그래머 Mark Zbikowski의 이니셜이다! zip 압축 파일의 식별자인 PK (개발자 필립 카츠)만큼이나 세계에서 제일 유명한 파일 포맷 식별자 이니셜일 것이다. MZ 저분은 미국 토박이라고 하지만, 이름으로 보아하니 러시아 계열 이민자의 후손인 듯하다.

비록 도스는 역사 속으로 사라졌지만, Windows용 실행 파일들은 지금까지도 과거 호환을 위해서 앞부분에 최소한의 MZ EXE 헤더 껍데기는 넣어 놓는 게 관행이 돼 있다.
한편, 32비트 이후부터는 프로그램들이 옛날처럼 다시 단일 세그먼트 기반 flat 모델로 돌아갔다. 단지, 그 세그먼트의 이론적 최대 크기가 꼴랑 64KB이던 것이 4GB로 왕창 커졌을 뿐이다.

3. NE (new)

1985년에 발표된 Windows 1.0과 함께 등장한 포맷이다. 도스와는 다른 방식의 API 호출, exe와 dll의 구분, 표준화된 리소스와 버전 정보 데이터, 함수의 import와 export 내역처럼 도스용 exe에는 없던 추가적인 정보가 많이 필요해진 관계로 새로운 실행 파일 포맷을 또 제정한 것으로 보인다. 단, 맨 앞부분은 그냥 도스 EXE처럼 시작하고, 새로운 방식은 다른 오프셋에서부터 시작된다. 이는 NE 다음의 PE도 마찬가지이다.

사실, NE는 Windows뿐만 아니라 마소에서 1986년경에 잠시 만들다 말았던 일명 '멀티태스킹 MS-DOS 4.0'(일반적인 그 MS-DOS 4.0 말고)용 실행 파일 포맷으로도 쓰였다고 알려져 있다. 하지만 도스는 텍스트 기반 환경이지만 Windows는 GUI 환경이고, 16비트 Windows에는 딱히 콘솔(명령 프롬프트)이라는 서브시스템이 존재하지 않았다. 그러니 바이너리 수준에서의 파일 포맷만 일치할 뿐, 양 플랫폼의 실행 파일을 딴 데서 원활하게 실행할 수는 없었을 것이다.

Windows 1.x부터 3.x까지 16비트 시절에 실행 파일 포맷은 크게 바뀌지 않았다. 단, 2에서 3으로 넘어가던 시절에는 Windows에 386 확장 모드라는 게 도입되었기 때문에, 이 프로그램은 종전의 리얼(real) 모드뿐만 아니라 보호(protected) 모드에서도 잘 실행된다는 보증 플래그가 추가되었다. 평범한 Windows API만 쓴 프로그램이 여기에 큰 영향을 받지는 않았겠지만, 그래도 혹시나 해서 말이다.

1980년대 왕창 옛날에 만들어졌던 일부 Windows용 프로그램들은 95뿐만 아니라 3.x에서 실행해도 "구버전용임. 여기서도 일단 실행은 되지만 케바케이기 때문에 온전하고 정상적인 동작을 기대할 수 없음. 최신 버전을 구해서 쓰셈.." 이런 주의 메시지가 뜨는 게 있었는데, 바로 이 플래그가 없이 옛날 방식대로 빌드된 프로그램이어서 그렇다.
특히 대화상자가 캡션(제목 표시줄)이 없이 표시되는 프로그램을 보면 옛날 냄새가 풀풀 난다. 캡션은 popup 윈도우가 아닌 overlapped 윈도우의 전유물이던 시절이 있었기 때문이다.

NE 방식의 실행 파일에는 각종 코드와 데이터, 리소스들이 resident, non-resident, discardable 이런 식으로 속성 구분이 있었다. 컴퓨터에 메모리는 왕창 부족한데, CPU와 운영체제 차원에서의 가상 메모리 지원이 없고, 그 열악한 환경에서 멀티태스킹을 구현하려다 보니, 돌아가는 방식이 가난함과 처절함 그 자체였다.
읽어들인 데이터는 언제든지 주소가 재배치되거나(단편화를 막고 연속된 많은 영역의 메모리를 확보할 수 있게), 삭제되어서 디스크로 되돌아갈 수 있었다. 반드시 메모리에 언제나 불러들여 놓는 데이터는 성능 차원에서 정말 중요한 것에만 한해서 지정해야 했다.

4. PE (portable)

1993년에 등장한 Windows NT 3.1에서 첫 도입된 또 다른 새로운 실행 파일이다. AT&T에서 오래 전에 개발한 COFF라는 오브젝트/라이브러리 파일 포맷의 연장선상에 있다. 아마 Windows NT 개발진의 출신 배경이 그쪽 계열 연구소여서 이런 포맷에 친숙하지 않았나 싶다. 덕분에 마소에서 개발하는 개발툴들은 16비트 시절에는 obj/lib의 포맷으로 인텔에서 개발한 OMF 방식을 썼지만 32비트부터 COFF로 갈아탔다. 그리고 exe/dll을 로딩하는 방식도 쿨하게 memory mapped file 방식으로 바꿨다.

PE는 현대적인 32비트 가상 메모리 환경에 맞춰졌기 때문에, 16비트 NE처럼 수동 메모리 관리를 염두에 둔 지저분한 속성이 존재하지 않는다. 세그먼트 대신에 코드, 데이터, 리소스 등의 용도별 섹션이 있고, 이들 섹션은 간단한 문자열 태그로 구분하기 때문에 섹션의 추후 확장이 가능하다. 그리고 헤더에 CPU 식별자도 넣어서 굳이 x86뿐만 아니라 다른 CPU 아키텍처의 실행 파일도 이 방식으로 기술 가능하게 했다.

훗날 64비트 CPU가 등장하면서, 아니 정확히는 1990년대 말에 IA64의 출시를 염두에 두고 PE의 기본 틀은 동일한데 메모리 주소나 몇몇 size 필드만 4바이트에서 8바이트로 확장된 일명 PE+ 규격이 나왔다. 그래도 기존 32비트에서도 얘를 알아보고 최소한 "에러 메시지+실행 거부" 정도의 대처는 할 수 있다.
리소스는 64비트도 32비트와 바이너리 차원에서 포맷이 완전히 동일하다. 이게 무슨 기계어 코드도 아닌데 필드 크기가 굳이 64비트 크기로 확장됐다거나 한 건 없다. 문자열이 유니코드 기반으로 바뀌었으니 16비트 방식과는 호환되지 않지만 32와 64비트끼리는 호환된다.

오늘날은 재래식 네이티브 코드뿐만 아니라 닷넷 기반(가상 머신), 그리고 UWP용(일명 metro) 앱 같은 것도 나왔지만, 이들도 실행 파일들의 기본 골격은 PE로 동일하다. 그 안에서 읽어들이는 시스템 DLL과 구동 방식이 서로 차이가 날 뿐이다.

Posted by 사무엘

2018/06/17 08:36 2018/06/17 08:36
, , ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1501

PC방 생각

PC방이란 게 우리나라의 경우 20여 년 전 IMF로 인해 늘어난 퇴직 중년층들의 창업 수요, 비슷한 시기에 보급된 인터넷 전용선 인프라, 그리고 스타크래프트 같은 초대박 게임 같은 요소들이 한데 맞물린 덕분에 크게 각광받았다.
그러나 2010년대부터는 고속 무선 인터넷 기반의 개인 노트북과 스마트폰의 보급으로 인해, 단순 인터넷 서핑을 위해서 커다란 데스크톱 PC, 그것도 공공 PC를 이용할 일은 사실상 없어졌다. 어지간한 게임도 폰으로 다 한다.

그럼 이제 PC방이라는 업종은 꿈도 희망도 없이 완전히 망했느냐..??
그건 또 아니다. 요즘도 아무 PC방이나 아무 때나 가 보면 손님들 생각보다 많다.
제아무리 모바일 게임이 인기를 끌고 폰에서도 3D 그래픽이 나온다 해도, 스마트폰이 넘보지 못할 기계 성능과 정교한 키보드· 마우스 컨트롤이 필요한 PC용 게임들도 부지기수이다. PC용 게임 개발사들도 절대로 그냥 놀고만 있지는 않다.

그리고 안 그래도 요즘 게임들은 혼자 하는 게 없으며 이윤 창출을 위해서는 온라인· 네트워크화가 필수인데, PC방은 여러 애들이 한데 모여서 다같이 최신 사양의 컴터를 쓰면서 팀플을 하기 좋다. 이런 게 집에서는 가능하지 않다. 오늘날 PC방은 게임 폐인(!)들 덕분에 그럭저럭 먹고 살고 있다고 해도 과언이 아니다.

다만, 그럭저럭 먹고 산다는 거지 PC방 산업은 엄연히 레드 오션이다. 창업 진입장벽이 꽤 낮은 축에 들어서 가게 간 출혈 경쟁이 난무하며, 이용 요금이 유지비 대비 지나치게 하향평준화된 감이 있다. 이래 갖고 대박은커녕 건물 임대료와 알바 인건비, PC 관리· 업그레이드 비용, 게임 개발사들에 지불하는 로얄티는 어찌 충당하겠나 싶은 생각이 들 정도이다.
요 몇 년 전부터 시행된 전면 금연 조치도 본인 같은 비흡연자에게야 환영할 일이지만, 골초 폐인 고객들을 맞이해야 장사가 되는 업주의 입장에서는 뒷목 잡을 일이었을지도 모르겠다.

결국 요즘 PC방들은 다른 방법으로 수익을 창출하기 위해.. 반쯤 식당으로 바뀌어 가고 있다.
겨우 과자 내지 컵라면 정도나 팔던 건 옛날 이야기이고, 김밥천국 수준으로 밥이 가미된 정식 식사나 온갖 기름진 야식류를 그 자리에서 주문해서 먹을 수 있다. 만화방은 자체 조리 음식보다는 배달 음식과 연계를 강화하는 식이었지 싶은데 PC방은.. 자체 조리 쪽이다. 이젠 PC방에서 담배 냄새가 사라진 대신 곳곳에서 음식 냄새가 자욱해지지는 않으려나 모르겠다.

옛날에는 PC방 요금에 몇백 원 단위도 있었던 걸로 기억한다만, 요즘은 물가가 물가이다 보니 1시간 1000원이 최소 단위인 듯하다. 그리고 햄버거 가게들과 마찬가지로 PC방도 자리 배당과 요금 지불 같은 단순 업무는 전부 기계화· 무인화가 됐다. 오랜만에 PC방을 다시 찾아가 보니 이런 풍경이 신기해 보였다.

개인적으로 PC방에서는 모든 가정이 구비하기에는 귀찮은 장비를 제공하여 소규모로 사용하는 요금을 받는 것도 필요하다고 생각한다. 스캐너라든가 뭔가 독특한 프린터 같은 거 말이다. 독특한 인쇄가 무슨 말이냐 하면, 고퀄 컬러 사진이라든가, 커다란 A3 용지 출력 같은 거. 그렇다고 전문 출력소를 찾아가기에는 너무 귀찮고 인쇄 분량이 적을 때 말이다.
이런 시스템이 잘 돼 있어서 인쇄 주문만 원격으로 내린 뒤, PC방에 들러서 PC 이용료 없이 인쇄비만 내고 결과를 찾아갈 수도 있으면 더 좋다.

물론 프린터를 구비한 PC방은 마치 세차 시설을 갖춘 주유소처럼, 이윤은 별로 안 나는데 장비의 유지 관리비만 더 들어서 귀찮은 상황이 발생할 수도 있다. 그리고 현실적으로 국내의 PC방들은 인쇄고 뭐고 그딴 거 필요 없이 게임 위주로만 돌아가고 있다는 것도 감안할 점이긴 하다.

뭐 아무튼, PC방은 게이머들의 수요 덕분에 2010년대에도 용케 살아남았다. 하지만 스마트폰과 개인 노트북, 무선 인터넷의 보급으로 인해 확실하게 없어져 버린 것들도 있다.
첫째, 전문적인 PC방 말고 버스 터미널, 지하철역 내부, 심지어 찜질방이나 열차(새마을· 무궁화호급) 카페 같은 곳에 어설프게 비치되어 있던 "비게임용" 공중 PC(인터넷 라운지)들은 전멸했다. 왜, 500원짜리 동전 넣고 10분인가 15분 동안 쓸 수 있던 유료 공중 PC들 말이다.

하다못해 재래식 공중전화는 군인이나 외국인들을 위해서라도 소수나마 필요하겠지만, 게임용이 아닌 그런 컴퓨터들은 이제 쓰는 사람이 없으며 수익보다 유지 보수 비용이 더 커졌을 것이다. (아, 다시 생각해 보니 공중전화가 필요할 정도일 사람은 긴급한 메일 확인을 위해서 이런 PC도 필요하긴 하겠다만.. 수요는 어차피 극소수일 것이다)
뭐, 전자기기 서비스 센터나 자동차 정비소 같은 데서 대기 고객의 편의를 위한 PC들은 예나 지금이나 남이 있지만 그건 애초부터 영리를 목적으로 운영되는 게 아니다.

옛날엔 종로3가 역 환승 통로 같은 곳에 의자도 없이 아예 선 채로 무료로 잠깐 사용하는 인터넷 연결 PC가 있었다. 하지만 요즘은 PC를 비치하느니 차라리 무선 인터넷 중계기나 스마트폰 고속 충전 단자를 설치하는 게 추세이다.

둘째, 그 다음으로.. 10여 년 전만 하더라도 매일 아침 지하철역 출입구에서 무료로 배부되던 찌라시들이 완전히 망하고 없어졌다. AM7이던가 Metro던가 이런 것들.
차라리 "교차로", "벼룩시장"처럼 처음부터 소규모 광고를 목적으로 무료로 배부되는 생활정보지들은 컴퓨터· 인터넷이나 지하철하고는 무관하게 존재해 왔다. 그런데 뭔가 연예· 스포츠 신문 같아 보이는 찌라시가 뭘로 어떻게 먹고 살려고 저렇게 무료로 뿌려지는지 개인적으로 좀 신기해 보였다.

뭐, 뉴스 기사의 분량과 퀄리티가 유료 종이 신문과 같은 급일 수는 없었겠지만, 거기에는 글과 광고만 있는 게 아니라 스도쿠 퍼즐 같은 것도 있고 연재 만화도 있었다.
승강장에서 지하철 기다리면서, 혹은 지하철 안에서 시간 때우기 용으로 괜찮기 때문에 집어가서 읽는 사람들이 제법 됐으며, 지하철 안에서는 "읽고 난 찌라시들은 선반 위에 놔두지 말고 제발 승강장 안의 수거함에다가 버려 주세요"라고 안내 방송이 지겹도록 나오곤 했다.

그런 찌라시들은 차내에 가만히 놔 두면 폐지 수집하는 어르신들이 알아서 잽싸게 가져가긴 했다. 그러니 청소 문제는 걱정할 필요가 없긴 했다만.. 거동이 불편한 노인이 높은 선반으로 팔을 무리해서 뻗느라 민폐가 발생하는 게 문제였다.

그랬는데.. 이제 시대가 바뀌어 사람들이 지하철 안에서는 DMB를 시청하고 스마트폰 게임을 한다. 그 무엇을 하건 요 손바닥만 한 기기에서 눈을 떼질 않는 시대가 됐다. 찌라시 따윈 이제 아오안. 그래서 저건 답이 없는 지경으로 전락하여 사라졌다.
참고로 본인은 예나 지금이나 지하철 안에서는 그냥 자거나 내 노트북 PC를 꺼내서 작업하거나 성경을 읽는 편이다. 그렇기 때문에 찌라시가 있던 시절에도 그걸 딱히 읽지는 않았었다.

Posted by 사무엘

2018/05/05 19:36 2018/05/05 19:36
,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1486

시스템 복원

Windows에는 시스템 자신의 소프트웨어적인 유지 보수와 관련하여 (1) 업데이트와 (2) 시스템 복원이라는 두 기능을 제공한다. 전자는 프로그램을 최신 상태로 유지하는 기능으로, 개념적으로 미래로 나아가는 것에 대응한다. 후자는 그와 반대로 과거로 돌아가는 기능이다.

옛날에는 프로그램의 업데이트/패치라는 게 오프라인 상으로 동작하는 기능에 버그가 발견되어 고쳐졌거나, 아니면 작게나마 새로운 기능이 추가됐을 때 이를 반영하기 위해 시행되었다. 그러나 2000년대부터는 굳이 그런 게 아니라 '보안 취약점'을 수정하는 업데이트의 비중이 커졌다.

보안 취약점은 세상의 컴퓨터들이 인터넷에 한데 연결돼 있지 않거나 아주 제한된 시간 동안 잠시만 연결된다면 별로 심각한 문제가 아닐 것이다. 하지만 그렇지 않고 컴퓨터가 상시 네트워크에 연결되어 있으며 아무나 아무 컴퓨터로 패킷을 보낼 수 있고, 그 패킷이 해석하는 방식에 따라서 특정 지시를 수행하고 코드를 실행할 수 있기 때문에(편의라는 명목 하에) 보안 문제가 불거지는 것이다. 엑셀· 워드 문서가 그냥 데이터뿐만 아니라 매크로가 추가됨으로써 보안 위험이 커졌듯이 말이다.

이러면 최악의 경우 나도 모르는 사이에 악성 코드가 원격 조작으로 실행될 수 있으며, 내 컴퓨터에 있는 데이터와 내가 키보드로 입력하는 문자가 나의 동의 없이, 나도 모르는 사이에 내 컴퓨터 밖으로 새어 나갈 수 있다. 내 데이터가 날아가고 내 개인 정보가 유출될 수 있다. 내 컴퓨터가 주변의 컴퓨터로 악성 코드를 퍼뜨리는 좀비가 될지 모른다. 한 마디로 요약하면, 컴퓨터 시대에서 상상할 수 있는 모든 끔찍한 재앙이 벌어질 수 있다.

보안 업데이트는 프로그램의 그런 허점들을 막아 준다. 정적 분석 기술로 컴퓨터 프로그램이 취급하는 모든 데이터의 처리 양상을 원천적으로 분석해서 보안 취약점을 자동으로 찾아낼 수는 없다. 그러니 그때 그때 취약점이 발견되면 해당 소프트웨어의 제조사에서 패치와 업데이트를 내는 식으로 "사후 약방문" 식 대응이 어쩔 수 없이 통용된다.

소프트웨어는 굳이 새로운 기능을 추가하기 위해서가 아니라 지금의 현 상태를 안전하게 유지하기 위해서라도 계속해서 유지 보수를 해야 하고 업데이트를 해야 하는 반제품이 되었다. 첫 버전 출시를 한 것은 전반부 종료일 뿐이고, 그 다음부터가 후반부의 시작이다.

업데이트라는 게 평범한 기능 개선과 추가에 지나지 않는다면.. "난 그런 기능 없어도 지금 프로그램 쓰는 데 아무 불편 없어요" 이런 사용자는 굳이 업데이트를 받을 필요가 없다. 하지만 보안 업데이트는 마치 예방접종과 비슷한 구석이 있어서 내가 안 받으면 남에게 피해를 줄 수도 있다. 그렇기 때문에 모든 사용자들이 반드시 받을 필요가 있다. 일단은 말이다.

업데이트에 대한 얘기가 좀 길어졌는데, 다음으로 시스템 복원은 위급한 상황에서 굉장히 유용한 기능이다. 개발자의 입장에서 이런 기능은 구현하고 테스트· 디버깅 하는 게 굉장히 엄청나게 어려웠을 것이다.

본인의 경우 꽤 오래 전(거의 2009~2010년경.. Vista 시절), 언제부터인가 집 컴퓨터가 인쇄가 안 되기 시작했다.
프린터가 USB 포트 상으로 인식은 분명히 되고, 인쇄 명령을 내리면 프린터가 이를 받아서 예열 작업까지는 한다(레이저임).

그런데 그 후로 프린터는 아무 반응이 없이 인쇄가 전혀 진행되지 않으며, 도리어 인쇄를 내린 응용 프로그램만 응답 불능 상태에 빠진 채 멎어 버리는 것이었다.
멎은 프로그램은 CPU를 사용하지는 않으며, 다른 프로그램들은 정상 동작했다. 하지만 그 멎은 프로그램은 작업 관리자로 아무리 죽여도 사라지지 않았다.

하드웨어 문제라면 이거 프린터를 수리 받아야 하는데, 무슨 충격을 받은 것도 아니고 멀쩡한 프린터가 갑자기 고장 날 리가 없으니 무척 난감한 상황이었다.
만약 소프트웨어 문제라면 프린터 드라이버를 다시 설치하거나 최악의 경우 운영체제를 새로 설치해야 할 것이다. 최근에 부모님께서 내가 없는 동안 이 컴퓨터로 이것저것 ActiveX도 깔고 인쇄를 하긴 하셨는데 도대체 어쩌다가 프린터가 이렇게 됐는지 몰라 답답하기 그지없었다.

하지만 다행히 문제는 비교적 간단하게 해결할 수 있었다. 혹시나, 설마 해서 ‘시스템 복원’을 해 봤는데 이게 날 살렸다.
부모님께서 컴퓨터를 건드리기 전인, 약 1주일 전으로 복원을 시켰다. 그 사이에 컴퓨터에 생긴 변화는 운영체제 업데이트 몇 개가 자동으로 설치된 것 정도가 떠 있었다.

시스템 복원을 하고 나자 프린터는 거짓말처럼 인쇄가 되기 시작했다. 아까는 무엇 때문에 안 됐는지 모르겠지만, 어쨌든 시스템 복원 기능을 이용해서 만족스러운 결과를 잘 얻었다.
Vista보다 더 옛날, XP 시절에도 본인은 시스템 복원으로 여러 하드웨어/소프트웨어 문제를 딱 해결한 적이 있었다. 이런 기능이 없었으면 영락없이 운영체제를 재설치해야 했을 터이다. 물론 이제는 운영체제를 재설치할 일 자체가 거의 없어지다시피했지만 말이다.

이런 Windows 업데이트와 시스템 복원은 하는 일이 완전히 다르지만 그래도 서로 한데 맞물려서 돌아간다. 업데이트를 설치하는 것부터가 시스템 복원 지점을 만든 뒤 진행되기 때문이다.
이런 일이 있어서는 안 되겠지만, 업데이트를 설치한 뒤에 운영체제에 오히려 부작용이 발생할 수가 있다. 상태가 예전보다 나빠졌다면 시스템 복원을 실행해서 원상복구를 시킬 수 있다.

시스템 복원은 Windows 2000도 아니고 ME에서 첫 도입된 정말 얼마 안 되는 기능 중의 하나이다. 이 기능으로 인해 Windows는 평시에 차지하는 하드디스크 용량이 본격적으로 크게 늘어나기 시작했다.

본인은 가끔 갖고 놀 목적으로 Windows ME 가상 머신을 갖고 있다. 여차여차 하다 보니 하드의 파일 시스템을 FAT32가 아닌 FAT로 잡아 버려서, 주 파티션의 용량이 겨우 2GB가 됐다. 거기에다가 MS Office와 날개셋 정도만 설치하면 하드 용량을 딱 절반인 1GB 남짓 차지했는데..

그렇게 날개셋 한글 입력기를 테스트 하고 빌드 스냅샷을 설치하고 지우기를 반복했던 가상 머신은 나도 모르는 사이에 여유 공간이 갈수록 줄어들더니, 겨우 50~60MB 남짓밖에 안 남는 사태가 벌어졌다.
뜨악 하다가 정신을 차리고 시스템 복원 기능을 완전히 끄고 기존 스냅샷들을 모두 삭제한 뒤 재부팅을 하자.. 사라졌던 1GB 남짓한 공간이 다시 거짓말처럼 나타났다. 용량 쳐묵쳐묵의 범인은 시스템 복원 기능이었다.

얘는 마치 휴지통처럼 최대 몇백 MB까지만 공간을 사용하라고 옵션을 지정하는 게 분명히 있음에도 불구하고, 그걸 훨씬 더 초과하여 디스크를 잡아먹고 있었다. ME의 복원 기능만 그런 문제가 있었는지는 모르겠다.

오늘날의 Windows 10은 브랜드 이름과 주 버전은 이제 더 안 고치고, 찔끔찔끔 업데이트만으로 보안 패치와 서비스 팩, 버전업을 모두 겸하게끔 배포 방식을 바꿨다. 이제 날짜와 빌드 번호가 사실상 버전 번호가 된 셈이다.
그런데 시도 때도 없이 너무 자주 업데이트가 발생해서 CPU 잡아먹고, 시간이 흐를수록 하드디스크 용량 소모가 너무 심하며, 컴퓨터를 원하는 때에 제대로 끄지도 못하게 만드는 등 민폐가 너무 심하다.

게다가 업데이트 설치 후에 부팅이 안 되고 컴이 먹통이 되는 현상을 집과 회사에서 두 번씩이나 겪은 뒤부터 본인은 학을 떼 버렸다. 인터넷 연결망이 종량제 기반이니 니 멋대로 업데이트 받아서 설치하지도 말고 알리지도 말라고 레지스트리를 조작해서 넣었다.
Windows 10만 그런 게 아니라, 구닥다리 7을 굴리는 작업실 컴도.. 하는 일 없이 CPU 잡아먹으면서 열받고 팬을 돌아가게 만드는 주범이 update 서비스인 걸 보고는 이거 nProtect만 욕할 처지가 아니라는 걸 알게 됐다. 서비스 다 내리고 업데이트 따위 꺼 버렸다.

아무리 보안과 안전이 중요하다지만 나는 최소한의 보안 관념이 있고 내 컴퓨터 통제를 스스로 할 줄 알며, 대부분의 보안 결함은 여느 교통사고나 범죄 사건과 마찬가지로 정말 극단적이고 예외적인 막장 상황에서나 발생하는 것들이다. 위험이 너무 과장되고 부풀려진 면모가 있다. 그리고 이 정도의 횡포는 가성비를 따졌을 때 강제 업데이트를 justify하지 못한다는 결론을 내렸다.

컴퓨터 자원은 무한한 게 아니다. 아무쪼록 시스템의 안정성을 관리하는 기능들이 지금보다 자원을 좀 아껴 쓰고 민폐 안 끼치며 동작했으면 좋겠다는 생각이 들었다. 이상.

Posted by 사무엘

2018/04/28 08:30 2018/04/28 08:30
,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1483

1. HTTP 통신 고수준 API

오늘날 운영체제의 GUI API가 qt 같은 별도의 프레임워크가 나온 걸 제외하면 통일된 게 없고(통일이 될 수가 없음..) 그래픽 API가 통합된 게 없듯이..
네트워크, 특히 HTTP/HTTPS 기반 통신 API 역시 내가 아는 한 뭔가 ANSI/ISO 차원의 표준이 나온 게 없이 운영체제마다 다 파편화돼 있는 것 같다. 물론 제일 저수준의 소켓 API는 그럭저럭 표준화가 돼 있지만 오늘날 그것만 써서 밑바닥부터 프로그램을 짜는 건 클라이언트건 서버건 무리이니 말이다. 그러니 Windows, 안드로이드, iOS/macOS마다 또 API를 새로 익혀야 하는 게 다소 번거롭다.

요즘 인터넷에서 다른 프로토콜들은 거의 듣보잡이 돼 가고 HTTP/HTTPS만 남은 것 같다.
통합 라이브러리는 스위치만 달리해서 동일한 정보를 (1) GET와 POST 두 방식으로 간편하게 보낼 수 있으며, 이때 argument를 적절히 배치해 줘야 한다. 전자는 주소에다가 넣고 후자는 헤더에다가 넣어야 할 것이다.

URL 인코딩 처리를 적절히 해 줘야 하며, 바이너리 데이터 덤프를 base64로 인코딩, 또는 디코딩 하는 라이브러리도 덤으로 제공하면 좋을 것이다. post로 보낼 때는 Content-Type, Content-Length 같은 상투적인 헤더를 당연히 자동으로 넣어 줘야 한다.

Windows에서는 메시지를 보내는 방식이 post의 반대가 send인데, HTTP에서는 요청을 보내는 방식이 post의 반대가 get이다. get은 어떤 정보를 얻어 오기만 하는 read-only operation용으로 쓰고, post는 서버다 데이터를 등록하고 변경하고 삭제하는 등 side effect가 남는 요청을 할 때 쓴다. 일단은 그러하지만 홍보가 부족해서 현실에서는 그 용도가 엄밀하게 지켜지지는 않고 있다.

POST 방식 요청의 연장선이겠다만 (2) 다운로드뿐만 아니라 업로드도 당연히 지원해야 할 것이고, 받는 데이터 내지 올리는 데이터는 (3) 메모리, 파일명, 임의의 스트림 형태로 자유롭게 공급이 가능해야 한다. 전부 다 필요하기 때문이다.

그리고 아주 짤막한 파일을 주고받을 때나 UI 없이 그냥 명령줄 프로그램을 만들 때를 대비해서 그냥 (4) 동기형(응답이 올 때까지 block) + 짧은 대기 시간 형태도 지원하고, 별도의 스레드에서 돌아가는 비동기 방식도 자체 지원해야 한다.
비동기 방식은 결과가 왔을 때 콜백 함수 호출 하나로 끝나는 간편한 것을 생각할 수 있고, 한편으로 수십~수백 MB짜리 파일을 주고 받으면서 전송 상태를 늘 확인할 수 있는 복잡한 형태도 생각할 수 있는데, 둘 다 가능해야 한다.

단순 업데이트 체크 같은 건 굳이 지금 당장 안 돼도 상관 없는 것이니 프로그램의 반응성에 영향을 줘서는 안 될 것이다. 스마트폰 앱 같은 게 비행기 모드이거나 3G 데이터를 사용하고 있을 때는 괜찮은데 외부의 희미한 와이파이에 접속해 있을 때는 굼뜨는 경우가 있다. 이런 일이 없어야 할 것이다.

Java는 네트워크 쪽 클래스를 사용할 때는 try catch로 예외 처리가 무조건 돼 있어야 하고 안 그러면 컴파일조차 되지 않는데, 안드로이드에서는 그것도 모자라서 네트워크 통신은 무조건 별도의 스레드에서만 동작하게.. main GUI 스레드에서는 쓸 수도 없게 만들어 놓았다. 네트워크 에러 때문에 프로그램 전체의 동작· 반응이 멎는 걸 원천봉쇄하기 위한 조치인데, 이것 때문에 아주 간단한 소규모 통신에 대해서까지 일일이 스레드 만들고 함수 분리하는 건 번거롭기도 한 게 사실이다.

통신 API로 할 수 있는 일을 생각해 보니 이 정도인 것 같다. 이 모든 상황에 대한 대처가 가능하고 크로스 플랫폼까지 지원한다면 훌륭한 통신 API가 될 수 있을 것이다.

2. Windows CE 프로그래밍

회사에서는 이것저것 찝적대느라 안드로이드, macOS 코코아에 이어 근래엔 골동품인 Windows CE까지 만질 일이 있었다.
도스 기반의 Windows (1985), NT 커널(1993)에 비해 CE는 임베디드 환경을 타겟으로 나름 제일 나중에(1996) 등장했다. PC용 OS들과는 달리 CE는 리얼타임 OS이다.

그리고 CE는 훗날 Embedded Compact라고 이름이 미묘하게 바뀌었다가 2010년대부터 Windows Mobile, Windows Phone 등 스마트폰 OS로 변모하는가 싶더니.. 그냥 Windows 10 자체가 ARM용으로도 나오면서 모바일 분야를 흡수해 버렸다. 또한, 스마트폰 OS는 마소가 판단하기에도 Windows가 안드로이드와 iOS의 적수는 도저히 안 되겠다고 여겨져서 그냥 접어 버리고.. 그 대신 Visual Studio가 무려 안드로이드 개발도 지원하는 식으로, 완전히 다른 형태로 변화하게 되었다.

아무튼, 이 와중에 지금으로서는 좀 구닥다리가 된 Windows CE를 만져 봤다.
GUI에서 본인에게 아주 익숙한 Windows API로 프로그래밍 가능한 건 좋은 점이었다. 단, 같은 API를 사용하더라도 헤더 파일 구조가 PC와 동일하지는 않았으며, 생성되는 바이너리도 비록 PE 방식의 실행 파일이긴 하지만, PC Windows 버전처럼 kernel32, gdi32, user32 이런 DLL들 import가 있지는 않는 게 흥미로웠다.

그 대신 coredll.dll에 API들이 한데 몰빵돼 있는 듯하다. 그리고 메모리 절약을 위해 함수들은 이름이 아니라 ordinal 번호만으로 import하게 돼 있다.

static 라이브러리 같은 걸 만들어도 PC용과 CE 기기용을 동일 소스 기반으로 유지하기는 좀 어려워 보였다. 글쎄, #include 부분에 조건부 컴파일 로직을 정교하게 잘 짜 놓으면 불가능하지 않을지도 모르겠지만.. 거기까지는 잘 몰라서 말이다.
그러고 보니 옛날엔 Visual C++도 Embedded VC++라고 CE 개발 전용 에디션이 있긴 했다가 그건 2000년대 이후로 본가에 흡수됐다.

Visual Basic 6이라든가 Visual C++ 6은 후대 버전과 단절이 커서 꽤 오랫동안 쓰인 개발툴이라는 공통점이 있다. 그런데 Visual C++ 2008도 비슷한 면모가 있다. 바로 Windows Phone이 등장하기 전의 Windows CE 시절, 더 정확히는 Embedded Compact 6.5와 그 이전 버전 레거시 플랫폼의 개발을 지원하는 마지막 버전이라는 것이다.

자동차 내비에서도 이 운영체제가 돌아가는 경우가 있다.
PC용으로 오랫동안 개발되어 온 C++ 코드를 이 플랫폼과 컴파일러용으로 포팅하려다 보니 지금까지 편하게 써 왔던 auto와 lambda, nullptr 따위를 몽땅 구 문법으로 다시 써 주는 불편을 감수해야 했다. 저기서는 2010년대 이후에 새로 도입된 C++ 문법이 지원되지 않기 때문이다.

이 특정 플랫폼만 그런지는 모르겠지만, 인상적인 점은 뭔가 듬성듬성 빠진 함수가 많다는 것이었다. 덩치를 줄이기 위해서 다른 우회 대체제가 있다 싶은 건 몽땅 빼 버린 모양이다.
일례로, DrawText와 ExtTextOut은 있지만 더 단순한 함수인 TextOut은 없다(더 범용적인 대체제가 있으니까..). 파일 시스템도 도대체 뭐 어찌 되는지 GetCurrentDirectory, GetWindowsDirectory 이런 거 없다.

칼질은 C 함수 쪽도 예외가 아니다. 엄연히 ANSI 표준인 time 함수가 없어서 Windows API를 이용한 대체 함수를 직접 만들어야 했다.
그리고 어찌 된 일인지 FILE* 스트림 기반의 fopen 계열 말고, 정수형 파일 핸들을 주고 받는 저수준 _create, open 계열의 함수도 없더라. PC에서 쓰던 코드를 곧장 포팅하는 건 생각만치 쉽게 되지는 않았다.

제일 압권인 건 CE는 9x와는 정반대로.. API 함수에 W 버전만 있고 A 버전이 깔끔하게 없다는 것이었다.
9x는 W 버전도 껍데기는 있지만 실행이 에러 코드와 함께 몽땅 실패한다. CE에서는 A 버전이 컴파일은 되지만 링크가 되지 않고 곧장 실패했다. 1996년에 첫 개발된 신흥 OS이다 보니, 안 그래도 용량도 부족한데 구닥다리 레거시인 A는 처음부터 배제해 버린 셈이다.

Windows CE에는 W만 있고 A가 없다는 건 먼 옛날에 제프리 릭터 아저씨의 책에서 처음으로 봤는데 실제로 그렇다는 걸 그로부터 10수 년 이상 뒤에야 실제로 확인할 수 있었다.

3. 라이브러리의 형태의 변화

도스 시절에 프로그래밍에 필요한 무슨 라이브러리, API, 또는 SDK라 하면..
어셈블리어로 도스의 각종 특이한 인터럽트를 호출해서 하드웨어를 직통으로 건드리는 마우스 라이브러리, 그래픽/사운드 라이브러리, 심지어 한글 라이브러리 같은 게 많았다. 이런 기능들은 타 언어도 지원했지만 가장 먼저는 C/C++용 헤더와 라이브러리의 형태로 제공되었다.

Windows로 넘어가서는 어지간한 하드웨어 제어는 운영체제의 자체 API만 써도 커버가 되니 3rd party 라이브러리 같은 건 필요가 크게 줄어들었다. 그냥 MS Office와 Visual Studio의 외형을 흉내 내 주는 GUI 툴킷이라든가.. 이미지 파일 처리 라이브러리가 쓰였다. 아 그리고, DirectX SDK는 Windows 플랫폼 SDK로부터 완전히 분리 독립했기 때문에 따로 설치해야 하는 물건이 되긴 했다.

그런데 요즘은 그런 API, 라이브러리라는 게 용도와 형태가 바뀌고 있다. PC 안에서 혼자 돌아가는 프로그램이 고차원적인 하드웨어 제어 기능을 사용하는 게 아니라.. 웹사이트나 스마트폰 앱에서, (1) 특정 대규모 서버에서 제공하는 각종 실시간 정보 제공 기능(날씨, 버스와 지하철 위치, 지리 정보 등등)이라든가 (2) 아주 고수준의 인공지능 내지 패턴 인식 기능(이미지에서 사람 얼굴 인식, 음성 인식 등) 같은 것을.. 1인 개미 개발자가 자기 앱이나 사이트에서 곧장 사용할 수 있게 해 준다.

방대한 인공지능 데이터 검색과 계산은 서버가 담당한다. 바둑 AI로 치면 AI 코드가 라이브러리에 담겨 있는 게 아니라, 별도로 돌아가는 알파고 서버와 통신만 하는 거다.
언어는 무겁고 부담스러운 C/C++과는 거리가 멀며, JavaScript, Go, 파이썬 같은 것을 곧장 지원한다.
과금 체계도 과거의 전통적인 형태와는 사뭇 다르다. 비싼 라이브러리 제품을 몇만~몇십만 원 일시불로 지불하고 사 오는 게 아니라.. 월 얼마씩~ 사용 트래픽이 얼마까지는 무료이고 그 뒤부터는 한 건당 얼마 이런 식으로 매겨진다. 구매 대신 임대 형태이다.

기술은 그 자체는 온통 오픈소스다 뭐다 하면서 무료에 가깝게 공개되고 상향평준화되고 있다. Google API의 기능들을 개발하는 데 컴퓨터공학· 전산학 박사들이 얼마나 많이 투입됐을까..? 그 다음으로는 그냥 자본과 데이터 물량전이 대세이다.
그리고 소프트웨어로 돈 버는 건 기술과 기능 자체가 아니라 그걸로 온통 사용자들의 감성을 사로잡고, 자아· 정체성을 표현해 주는 대가로 형태가 바뀌는 것 같다. 온라인 게임의 부분 유료화라든가 각종 아바타가 대표적인 예이고 말이다.

아, 그렇다고 지금이 전통적인 형태로 판매되는 라이브러리· 미들웨어가 전멸했다는 얘기는 아니다. 게임에서 주로 쓰이는 네트워크 라이브러리, 동영상 캡처 라이브러리 같은 것들은 전적으로 로컬에서 기능이 동작하며, 단품 또는 출시되는 제품 타이틀의 규모 단위로 판매되고 있다. 돈을 한번 지불하고 끝인 것도 있고, 일정 주기로 꼬박꼬박 로얄티를 내는 형태인 것도 있다.

4. 함수 호출과 금융의 관계

좀 엉뚱한 생각을 하자면, 금융을 프로그래밍에다가 비유하고 돈이 오가는 걸 함수 호출과 데이터 전달에다가 비유할 수 있을 것 같다.

현금이 오가는 건 데이터 실물을 통째로 스택에다 얹는 call by value이다. 제일 단순하고 확실하지만 거액의 현금을 매번 들고 다니는 건 번거롭고 귀찮고 위험하다.
그러니 현금, 또는 가까운 미래에 들어올 예정인 돈에 대한 포인터가 등장하는데, 이것들이 바로 수표나 어음이다. call by reference가 현실 세계에도 존재하는 셈이다.
부도는 당연히 잘못된 포인터 접근으로 인한 page fault 내지 access violation과 직통 대응이다.

컴퓨터에는 지금 물리적으로 실제로 있는 메모리보다 더 많은 주소 공간을 끌어다 쓰고, 공간이 없으면 디스크 스와핑이라도 하는 '가상 메모리'라는 개념이 있다. 이게 어찌 보면 '대출'과 비슷한 개념으로 보인다.
물론 컴퓨터의 동작에 경제 용어인 '신용'과 정확히 대응하는 개념이 존재하지는 않는다. 그러니 완전 동일하게 대응하지는 않겠지만.. 그래도 이런 식으로 비유해 보면 생각보다 그럴싸하고 씽크가 맞아 보인다.

참고로, 성경이 말하는 헌금 원칙은 철저하게 리얼 모드이다. 빚을 내거나, 없는 돈을 미리 작정하는 보호 모드 가상 메모리 같은 개념은 없다.

5. 보안 정책

오래된 일이긴 하지만 마이크로소프트는 회사가 돌아가는 방식이 2002년 1월 1일 이전과 이후가 서로 싹 달라졌다. 더 엄격한 보안 정책과 더 일관성 있는 제품 지원 주기 정책이 적용되기 시작했기 때문이다.
일단, 이때를 기점으로 해서 모든 마소 제품에서 이스터 에그가 사라졌다. 문서화되지 않은 특정 동작을 하면 프로그램 개발자 명단이 나타나고 숨겨진 게임이나 애니메이션이 뜨는 것 말이다.

그리고 (a) 일반 지원은 제품 출시 후 몇 년 또는 차기 버전이 나온 지 몇 년 중 짧은 기간까지, (b) 연장 지원은 일반 지원이 종료된 뒤부터 5년간.. 요런 식의 규정이 추가되어 오늘날에 이르고 있다. 그와 함께 도스, Windows 3.x/95 같은 구닥다리 제품들은 지금까지 알음알음 지원되어 오던 것이 2001년 12월 31일을 끝으로 지원이 완전히 끊겼으며, 공식적으로 abandonware 판정을 받았다.

인터넷 덕분에 소프트웨어를 배포하는 데 물리적인 장벽은 사라지다시피한 반면, 원격으로 프로그램을 조종하고 컴퓨터를 장악할 수 있는 보안 위협이 커졌기 때문이다. 결국 소프트웨어라는 건 한번 만들고 나서 끝인 게 절대 아니라 계속해서 지원을 해야 하며, 굳이 기능과 컨텐츠에 아무 변화가 없더라도 보안 취약점이 발견되면 계속해서 고치고 지원해 줘야 하는 반제품 정도로 위상이 바뀌었다.

그러니 소프트웨어의 판매 비용에는 이런 잠재적 사후 지원 비용도 포함되어야 하며, 지원을 한도 끝도 없이 영원무궁토록 해 줄 수는 없으니 구체적인 기간이 법적으로 명시될 필요도 생긴 것이다.

그 뒤로 마소에서는 2000년 중반에 프로그램 개발의 기본 블록이라 할 수 있는 C 라이브러리에서부터 보안 결함을 잡겠다고 strcpy, gets 같은 함수들에 칼질을 가했으며, Visual Studio 2005에서 이를 최초로 적용했다. 컴파일러에 /GS 같은 검사 기능도 추가했다.
재래식 도움말인 WinHlp32 엔진은 너무 구닥다리 기능으로 전락한 데다, 이런 새로운 보안 기준을 충족하게 개량을 하기에는 가성비가 너무 안 맞는 관계(시간과 예산..)로 Windows Vista에서부터는 짤렸다.

Vista에서는 프로그램의 실행 주소를 그때 그때 랜덤화하는 보안 기능이 추가되기도 했다. Windows는 애초에 position independent code를 쓰지도 않는 운영체제인데, 오로지 보안을 위해 그 이념을 근본적으로 부정하고 성능을 희생하는 기능을 넣은 것이다.

이렇게 런타임 환경에다가는 예측 불가능한 요소를 일부러 가미한 반면, 빌드 타임 환경에는 정반대의 정책이 적용되고 있다. 같은 소스 코드를 같은 컴파일러 제품으로 빌드했으면 시간과 장소를 불문하고 바이너리 수준에서 언제나 완전히 동일한 실행 파일이 나오게 하자는 것이다. 이름하여 Reproducible builds이다.

단순히 생성되는 기계어 코드를 넘어서 객체들의 명칭과 배열 순서라든가 내부적으로 생성되는 각종 시그니처까지 완전히 똑같게.. 상상할 수 없을 정도로 다양한 환경에서 실행되는 프로그램을 테스트· 디버깅 할 때 문제를 재연하기 어렵게 만드는 변화 요인를 털끝만치라도 줄이겠다는 의도로 보인다.

시범타로 예전에 실행 파일의 헤더 차원에서 timestamp가 들어가던 곳에도 Windows 10에 들어가는 바이너리들은 그냥 고정된 hash값으로 바뀌었다고 한다. 시간이야 난수의 씨앗으로도 쓰일 정도로 매번 달라지는 값이니 말이다.
이렇게 21세기 들어서 세월이 흐를 수록 마소는 내부의 보안 정책이 갈수록 엄격해지는 것이 느껴진다. 그 와중에 실행 주소 랜덤화와 Reproducible builds라는 두 이념이 본인이 보기에 흥미로운 대조를 이루는 듯해 보였다.

Posted by 사무엘

2018/04/13 08:25 2018/04/13 08:25
,
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1478

코딩을 하다 보면 한 자료형(타입)에 속하는 값 내지 개체를 다른 타입으로 변환해야 할 때가 있다. 아주 직관적이거나(C에서 정수와 enum), 작은 타입에서 큰 타입으로 가기 때문에 정보 손실 염려가 없는 상황에 대해서는 해당 언어의 문법 차원에서 대입이나 함수 인자 전달이 곧장 허용되곤 한다. 바이트 수가 대등하더라도 signed보다는 unsigned가 더 큰 타입이고, 정수보다 실수가 더 큰 타입으로 여겨진다.

그렇지 않고 정보 소실의 여지가 있거나 서로 호환되지 않는 타입에다 값을 집어넣는 건 컴파일러의 경고나 에러를 유발한다. 이 상황에 대비하여 프로그래밍 언어들은 여러 형변환 함수들을 제공하는데, 씨크하게 괄호 안에다가 타입 이름만 달랑 쓰면 형변환 연산자로 인식되는 C/C++이 여기서도 참 유별난 면모를 보이는 것 같다.

형변환이란 건 값의 초기 타입과 목적 타입이 무엇이냐에 따라 내부에서 벌어지는 일이 매우 다양한 편이다.

(a) 없음: LPARAM에서 void*, void*에서 char* 같은 무식한 형변환은 소스 코드상의 의미만 매우 과격하게 바뀔 뿐, 내부적으로 행해지는 일은 전무하다! (클래스 상속 관계를 신경쓸 필요 없는 간단한 것 한정으로)

(b) 그냥 뒷부분 짜르거나 늘리기: int와 char 사이의 변환

(c) 정수와 실수 사이를 변환: 내부적으로 일어나는 일이 분명 간단하지는 않지만, 요즘은 이런 건 CPU 명령 한 줄이면 바로 끝난다. x86 기준으로 cvtsi2sd이나 cvttsd2si 같은 인스트럭션이 있다.

(d) 고정된 오프셋 보정: 단순한 형태의 다중 상속에서 제n의 부모 클래스 포인터를 얻으려면 이런 보정이 필요하다.

(e) 함수 호출: 해당 클래스에다 사용자가 구현해 놓은 operator 함수가 호출된다. 사실, 100과 "100"처럼 숫자와 문자열 사이의 변환도 컴퓨터의 관점에서 보면 이 정도의 cost가 필요한 작업이다.

(f) 상속 계층 관계 그래프 순회: 이건 객체지향 이념 때문에 형변환 연산이 언어 차원에서 가장 복잡해지는 상황이다. 위의 1~5와는 근본적으로 차원이 다르다. 가상 상속 체계에서 부모 클래스를 찾아가거나 dynamic_cast 형변환을 하려면 자기의 타입 정보 metadata를 토대로 주변 클래스 계층 그래프를 O(n) 시간 동안 순회해야 한다(n은 상속 단계).
사용자의 코드가 아닌 컴파일러의 코드만 실행됨에도 불구하고 런타임 가변적인 반복이 발생할 수 있으며, 객체와 메모리 상태가 어떤지에 따라서 형변환 결과가 dynamic하게 달라질 수 있다!

내부적으로 벌어지는 일은 대략 저렇게 분류 가능하고, 겉으로 소스 코드의 의미 차원에서도 형변환의 성격을 몇 가지로 분류할 수 있다.
처음에 C++에는 그냥 C-style cast밖에 존재하지 않았다. 그런데 그랬더니 형변환 연산이 발생하는 부분만 검색으로 뽑아내는 게 어려웠고, 또 단순한 형변환과 좀 위험한 형변환 같은 걸 따져 보기도 어려웠다. 표현 형태가 괄호와 타입 이름이 전부이니까..

그래서 1996~97년경, C++98의 발표를 앞두고 C++에는 길고 굉장히 기괴해 보이지만 용도별로 세분화된 형변환 연산자가 4종류나 추가되었다. namespace, bool, explicit, mutable 이런 키워드들과 같은 타이밍에 도입되었다. C++이 숫자는 몽땅 machine-word int로 어영부영 때우려던 C스러운 사고방식을 탈피하고, 예전에 비해 나름 type-safety를 따지기 시작한 그 타이밍이다. (예: C와 C++에서 sizeof('a')의 값의 차이는?)

새 연산자들은 모두 *_cast로 끝난다. 옛날의 재래식 형변환은 (NEWTYPE)value라고 썼고 C++ 문법으로는 NEWTYPE(value)도 허용되는 형태였다(NEWTYPE이 딱 한 단어 토큰으로 떨어지는 경우에 한해서).
그에 비해 새 연산자들은 *_cast<NEWTYPE>(value)라고 쓰면 된다. < > 안에다가 타입 이름을 쓰는 것은 템플릿 인자 문법에서 유래되었는데 나름 직관적이고 적절한 활용 같다.

1. static_cast

얘는 일상적으로 가볍고 큰 무리 없이 일어나는 일반적인 형변환을 거의 다 커버한다. (1) 큰 타입에서 작은 타입으로(실수에서 정수, UINT에서 int, long long에서 int 등..), 그리고 (2) 범용적인 타입의 포인터에서 더 구체적인 타입의 포인터로(void*에서 타 포인터, 기반 클래스*에서 파생 클래스 포인터) 말이다. 이게 대부분이다.

그리고 형변환 operator 호출이라든가, 다중· 가상 상속으로 인한 포인터 보정도 언어에서 보장돼 있는 메커니즘이므로 알아서 처리해 준다. 정말 대부분의 상황에서 앞서 나열했던 (a)에서 (f)까지, C-style cast를 대체할 수 있는 무난한 연산자이다. 단, f에서 typeid와 RTTI까지 동원되는 제일 비싸고 난해한 기능은 없으며, 이건 나중에 설명할 dynamic_cast가 전담하는 영역이다.

2. const_cast

얘는 값이 아니라 포인터/참조자형에서 C/C++ 특유의 한정자(qualifier) 속성만을 제거해서 더 범용적인 포인터로 만들어 준다. 그러므로 용도가 아주 제한적인 형변환 연산자이다.
C++에서 공식적으로 제공되는 qualifer는 const와 volatile이 있다. 이런 한정자는 가리키는 대상 타입과는 아무 상관 없고, 포인터를 이용해 그 메모리를 접근하는 방식 차원에서 제약을 부여할 뿐이다. 전자는 읽기 전용 속성이고, 후자는 멀티스레드에 의해 값이 언제든 바뀔 수 있음을 대비하라는 최적화 힌트이다.

Visual C++에는 __unaligned라는 확장 키워드도 저것들과 동급인 한정자이다. 이 포인터는 machine word 단위의 align이 맞춰지지 않은 주소가 들어올 수도 있으니 그렇더라도 뻗지 말고 보정하라는 뜻이다(성능 오버헤드 감수하고라도). align 보정을 알아서 너무 잘 해 주고 있는 x86 계열은 전혀 해당사항이 없고, 과거에 IA64를 지원하던 시절에 필요했던 키워드이다. 이것도 포인터 한정자 속성으로서는 굉장히 적절한 예이며, 이런 속성들을 const_cast로 제거할 수 있다.

3. reinterpret_cast

이건 의미론적으로는 제일 무식하고 생뚱맞고 위험하지만 내부 처리는 제일 할 것 없는 형변환 전문이다. (1) 정수와 포인터 사이를 전환하는 것, 그리고 (2) 서로 관련이 없는 타입을 가리키는 포인터끼리 전환하는 것.. 어디 범용적인 함수나 메시지로부터 아주 polymorphic한 데이터를 전달받아서 처리할 때에나 불가피하게 쓰일 법하다.

void*를 char*로 바꾸는 건 static_*으로도 되고 reinterpret_*으로도 된다. 하지만 const char*를 char*로 바꾸는 것은 static_*나 심지어 reinterpret_*로도 안 되고 반드시 const_*로만 해야 한다.
그런데 그래 봤자 reinterpret_*과 const_*는 어떤 경우에도 실질적인 내부 처리는 (a)뿐인(= 없음).. 참 허무한 연산자이다. 실질적인 처리가 없지만 이 숫자값을 해석하는 방식을 변경하는 이유는 분야별로 여럿 존재할 수 있다는 뜻이다.

재래식 C-style cast는 따지고 보면 1~3을 그냥 싸잡아서 구분 없이 수행해 준다. 그런데 가끔 드물게 다중· 가상 상속 관계의 타입 포인터끼리 형변환을 할 때 정석대로 보정 연산을 거친 포인터를 원하는지(static_*), 아니면 이것도 아무 보정 없이 동일한 메모리 주소에서 타입만 바꿔서 해석하고 싶은지(reinterpret_*) 모호해질 때가 있다.

이런 문제도 있고, 또 C++에다가 좀 제대로 된 객체지향 언어의 기능을 뒤늦게 갖추려다 보니 새로운 형변환 메커니즘이 필요해졌다. 이쯤 되니까 형변환 연산자도 별도의 예약어로 도입해서 구분하지 않고서는 도저히 버틸 수 없는 지경이 됐다. 그럼 다음으로 제일 괴물인 형변환 연산자에 대해서 살펴보도록 하자.

4. dynamic_cast

가상 함수를 쓰면 기반 클래스의 포인터를 주더라도 자신이 실제로 속한 파생 클래스에 해당하는 멤버 함수가 알아서 호출된다.
그리고 다중 상속 때 가상 상속을 쓰면, 여러 부모 클래스들이 동일한 조부모 클래스로부터 상속받았을 때 공통 조부모가 한 번만 상속되는 마술(?)이 일어난다. 물론 객체지향 언어에서 유연한 코드 재사용성을 보장하는 모든 마술에는 그에 상응하는 성능 오버헤드가 대가로 따른다는 점은 감안할 필요가 있다.

그런데 과거의 C++은 상속과 함수 호출에서 이렇게 언어 차원의 동적 바인딩이 지원되는 것과 대조적으로, 형변환에는 "동적 바인딩 + 무결성"을 보장하는 메커니즘이 딱히 없었다. 이놈이 A의 파생 클래스이긴 하지만 더 구체적으로 A의 자식들 중에 B가 아닌 C의 파생 클래스가 진짜로 맞는지, 이 멤버에 접근하고 이 함수를 호출해도 안전하겠는지 말이다.

C++은 void*가 있을지언정, 언어 차원에서 모든 클래스들의 공통 클래스(Java의 Object 같은)라는 개념이 없다. 그리고 클래스 내부의 vbtl에 직접 접근한다거나, 가상 함수의 포인터 값을 보고 클래스 종류를 판별할 수 있을 정도로 C++이 ABI가 몽땅 왕창 노출돼 있느냐 하면 그렇지도 않다(겉에는 공통의 썽킹 함수의 주소만 노출돼 있기 때문). 뭔가 어정쩡하다.

그러니 MFC 같은 옛날 라이브러리들은 자체적으로 CRuntimeClass 같은 타입 메타정보를 구비하고, 이놈이 CObject의 파생이긴 하지만 특정 클래스의 파생형이 정말 맞는지 런타임 때 확인하는 함수를 자체적으로 구현해야 했다.
C++이 아무리 C의 저수준 고성능 제어 이념을 계승했다 해도 명색이 객체지향 언어인데 그런 기능조차 없는 건 좀 아니라 여겨졌는지, 훗날 언어 차원에서 타입 식별 정보와 전용 형변환 연산자가 도입됐다. 그 결과물이 바로 dynamic_cast이다.

함수도 virtual, 상속도 virtual인 걸 감안하면 얘의 이름은 기술적으로 virtual_cast라 해도 과언이 아닐 듯하다. 하지만 static_* 이라는 단어가 이미 있으니 그것보다 더 값비싼 형변환이라는 의미로 dynamic_*이라는 이름이 최종적으로 붙었다. static_*은 이놈이 진짜 조상 관계가 맞는지 확인하지 않고 그냥 O(1) 복잡도짜리 기계적인 오프셋 보정만 해서(필요한 경우) 파생 클래스로 형변환해 주는 반면, dynamic_*은 타입 식별자를 직접 확인까지 한다는 것이다.

가상 함수의 구현을 위해서는 함수 포인터 테이블과 테이블의 포인터(멤버)가 필요하고, 가상 상속의 구현을 위해서는 기반 클래스의 포인터(멤버)가 필요하다. 그것처럼 동적 바인딩 형변환을 구현하려면 클래스 이름과 계층 관계를 기술하는 메타데이터와 함께 그놈의 포인터(멤버)가 필요하다.

가상 함수와 가상 상속 지원을 위한 데이터는 비공개로 꽁꽁 감춰져 있는 반면, 동적 바인딩 형변환을 위한 타입 식별자 데이터는 공식적으로 스펙이 공개돼 있고 일반 프로그래머들이 언어 요소를 통해 접근할 수 있다. 일명 RTTI (run-time type info)이다.
#include <typeinfo>를 한 뒤 typeid() 연산자로 const type_info&라는 구조체, 아니 클래스를 들여다보면 된다. typeid는...

  • sizeof와 마찬가지로 오버로딩 할 수 없다.
  • sizeof와 마찬가지로 타입 이름과 일반적인 값을 모두 받을 수 있다. 단, sizeof는 값이 올 때는 괄호를 생략할 수 있는 반면, typeid는 그렇지 않아서 언제나 뒤의 피연산자를 괄호로 싸야 한다.
  • sizeof는 결과값이 무조건 정적 바인딩으로 구해지는 반면, typeid는 바인딩이 정적과 동적 사이에서 어정쩡하다. 피연산자가 타입 이름이거나, 값이더라도 int 같은 primitive type 내지 상속이고 가상 함수고 아무것도 없는 구조체라면.. sizeof와 마찬가지로 수식을 실제로 evaluate하지 않는다. 그러나 뭔가 상속 관계 규명이 필요하다 싶은 개체라면 런타임 계산이 행해진다.

쉽게 말해 typeid는 MFC로 치면 obj->GetRuntimeClass()와 RUNTIME_CLASS(classnam)의 역할을 혼자 모두 수행한다는 뜻이다.
그럼 관련 타입들에 대해 DECLARE_DYNAMIC 내지 IMPLEMENT_DYNAMIC도 어딘가에 행해져야 할 텐데, 그건 C++ 컴파일러가 typeid 연산자가 쓰인 곳을 총체적으로 따져서 알아서 처리해 준다.

이런 RTTI 기능은 대다수의 C++ 컴파일러에서 사용 여부를 옵션으로 지정할 수 있게 돼 있다.
RTTI를 사용한 상태에서 dynamic_cast를 사용하면 실제 타입이 그게 아닌데 그 파생 타입으로 형변환을 시도하는 경우.. 피연산자가 포인터였다면 NULL이 날아오고, null이 가능하지 않은 참조자를 줬다면 예외(exception)를 날리게 된다.
이것도 다중 상속까지 생각한다면 상속 관계 그래프를 타고 오프셋을 보정하는 알고리즘이 가히 판타지가 될 것 같다.

이상.
객체지향 언어라는 게 그냥 구조체에다가 this가 자동으로 전달되는 함수가 같이 딸려 있고, 상속에 다형성 정도 지원되는 게 전부라고 생각하는 게 얼마나 순진한 생각인지 알 것 같다.
PC 환경에서 C를 초월하여 C++이 보급된 게 1990년대 초쯤인데, 이 무렵에 템플릿과 다중 상속도 도입됐다. 처음부터 있었던 게 아니다. 그리고 그때부터 C++은 뭐랄까, 괴물 같은 복잡도를 자랑하는 치떨리는 언어로 변모한 것 같다.

구리고 지저분한 면모가 많지만 그래도 그 정도 객체지향 이념에다가 고성능 저수준 제어까지 이만치 잡은 건 얘가 C++이 가히 독보적인 것 같다.
그러니 형변환 연산자도 언어를 따라 이렇게 복잡해진 것이다. 일례로, Java는 final이 있을지언정 포인터도 없고 const니 volatile 같은 건 신경 쓸 필요도 없는데 뭐 저런 구분이 필요하겠는가? 그러니 지금도 여전히 C-style cast만으로도 충분한 것이다.

Posted by 사무엘

2018/03/25 08:30 2018/03/25 08:30
, ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1471

1. Java 언어가 쓰이는 곳

Java는 C++과 달리 로컬 환경용으로는 그다지 재미를 못 본 언어 및 런타임 환경이다.
콘솔(게임기 말고, 명령 프롬프트..)용 프로그램이 Java로 만들어진 건 거의 못 봤다. 오히려 Java는 한때는 표준 입력으로부터 숫자 한 줄 입력받는 것조차도 온갖 패키지를 import하고 예외 처리 try catch까지 갖춘 뒤에야 가능한 불편한 언어였다.

GUI(프레임워크 이름이 Swing이던가?)로 넘어오면 VirtualBox나 OpenOffice처럼 일부 크로스 플랫폼 프로그램이 GUI 껍데기를 Java로 만든 경우가 좀 있다. (Windows 한정으로는 C#의 Windows Form와 경쟁?) 아, 직장에서 일정 관리 용도로 사용하는 ProjectLibre도 흔치 않은 Java 기반 로컬 GUI 프로그램이다.
하지만 Java가 제공하는 GUI는 외형이 네이티브 GUI에 비해 이질적이고 느리고 런타임 오버헤드가 커서 범용성 말고는 가성비가 좋지 않았다.

한편, Java는 애플릿(Applet)이라는 이름으로 웹(클라이언트)에서 돌아간 적도 있다. 웹에서 꽤 고차원적인 수학 그래픽, 화면 왜곡과 전환 애니메이션, 시뮬레이션(특히 교육용) 등을 출력할 목적으로 플래시와 경쟁하는 구도로 한때 쓰였으나.. 그것도 다 지나간 일이다. 현재는 망했으며, 개발사로부터 지원도 끊긴 지 오래다.

오늘날 Java는 로컬 PC가 아닌 안드로이드 앱, 그리고 jsp 기반 서버사이드 언어(웹 서버)로 회생해 있다.
뭐, Java 런타임을 컴퓨터에 설치해 보면 설치 중에 '전세계 수십억 개의 기기가 Java를 기반으로 돌아가고 있습니다'라고 자랑하는 문구가 뜨기도 하는데, 그것도 임베디드보다는 모바일을 말하는 게 아닌가 싶다.

Java는 오늘날 그렇게 됐고 로컬 PC 환경에서는 지금도 수많은 프로그래밍 언어들이 난립해 있지만, 웹에서는 그야말로 Java에서 이름만 빌려온 JavaScript로 대동단결 천하통일이 이뤄진 게 신기하기 그지없다.

2. 새로운 언어

애플에서 Objective-C에 한계를 느끼고 Swift라는 언어를 만들었더니,
그에 질세라 안드로이드 진영에서도 Java 대신 '코틀린'이라는 완전히 다른 언어를 만들었다. 왕년에 C++ 컴파일러를 열심히 만들었던 사람이 D 언어를 개발했듯, Java 기반 안드로이드 개발 환경을 열나게 만들었던 JetBrains라는 회사에서 전용 언어도 만들고 이를 Google에서도 채택한 것이다.

오늘날 베이직은 마소에서밖에 만들지 않는 언어가 됐고, 파스칼은 그냥 델파이의 전유물이 됐고 이제는 델파이라는 이름 자체가 RAD 툴 겸 프로그래밍 언어의 이름으로 등극했다. 언어가 워낙 많이 마개조됐기 때문이다. 또한 옵씨의 경우는 애초부터 애플이 언어에 대한 일체의 권리를 언어 고안자로부터 사 버려서 사유화했다.
저런 언어들에 비해, C/C++은 너무 심하게 파편화가 됐을지언정--언어 문법 자체보다는 라이브러리나 ABI 계층에서--, 특정 기업에 의해 좌지우지된다는 느낌은 상대적으로 덜 든다.

Visual Studio 등 요즘 IDE들은 프로젝트 전체에 존재하는 클래스들을 쭉 보여주는 기능이야 당연히 기본으로 갖추고 있다. 그런데 클래스들을 namespace 계층 + 이름의 알파벳 순으로만 보여주는 게 아니라, 기반 클래스별로 분류해서 보여주는 기능이 있으면 프로젝트들의 성격을 파악하는 데 더 도움이 될 것 같다. 그러면 그 클래스들의 성격을 파악할 수 있으니 말이다.

3. 간단한 자료형과 복잡한 자료형 or 클래스 객체

오늘날 객체지향을 표방한다는 많은 고급 언어들이 int 같은 (1) primitive type과 (2) 클래스 객체, 혹은 (1) 스칼라와 (2) 복합 자료형(리스트 같은)을 서로 구분해서 다루고 서로 다르게 취급한다. 함수 인자와 리턴값을 주고받을 때 (1)은 값을 그대로 전달하고 (2)는 메모리에 한 인스턴스만 둔 뒤 주소값만 주고받고 레퍼런스 카운팅을 하는 것이 대표적인 차이점 되겠다. Java, 파이썬 등 여러 언어들이 이런 정책을 사용한다.

순수 극한 객체지향 언어 중에는 1과 2의 구분조차 없애고 모든 것을 객체로 취급하여 타입 식별자라든가 기반 클래스 소속을 부여하는 물건도 있다. 하지만 그렇게까지 하기에는 속도와 메모리 오버헤드가 너무 크고 실용성도 떨어지니 많은 언어들이 최소한의 primitive type 정도는 허용하는 타협을 한다.

C++이야 어느 타입이건 어느 방식으로 전달할지(값, 주소/참조)를 몽땅 명시적으로 수동 지정이 가능하다. 그러나 포인터를 노출하지 않는 언어에서는 그런 구분이 프로그래머의 지정 없이 자동으로 행해진다. 포인터가 없는 언어라고 해서 포인터가 원래 하던 일 자체를 안 하는 것은 전혀 아니니까 말이다.

그런데 1과 2의 얼추 중간쯤에 속하는 물건은 문자열, 그리고 단순 구조체 정도다.
문자열이야 언어 차원에서 상수 리터럴도 존재하고 정말 기본 자료형과 복합 자료형의 중간에 속하는 독특한 물건인지라, 각 언어와 라이브러리마다 구현 형태가 제각각이다.

그리고 생성자, 소멸자, 상속, 가상 함수 따위 전혀 없고 레알 int 같은 primitive type의 묶음으로만 이뤄진 레알 C 스타일 구조체를 프로그래밍 용어로는 POD(plain old data)라고 한다. C++의 경우 POD 구조체만이 중괄호 { }를 이용한 멤버 별 값 초기화가 가능하다.
물론 POD도 개당 수십~수백 바이트를 차지하는 덩치 큰 놈이 될 수도 있지만 이는 논외로 하고..

단순 구조체를 클래스와 구분시킨 것은 C#의 신의 한수로 보인다. 사실, POINT, SIZE, COMPLEX(복소수)처럼 숫자 둘로만 달랑 이뤄진 간단한 구조체는 상속이 뭐고 없고 함수 인자도 그냥 값 형태로 전달하고 싶은 그런 물건들이니 말이다.

4. sizeof 연산자가 없음

개인적으로 Java를 쓰면서 C++에 비해 굉장히 특이하다고 오래 전부터 생각했던 점은..
가장 먼저, (1) int를 함수에다 주소/참조로 전달을 할 수 없어서 swap 함수를 구현할 수 없다는 것이다. int는 무조건 값으로만 전달되니 말이다.

(2) 클래스에 소멸자라는 게 없다 보니, 메모리는 GC 덕분에 뒷일 생각할 필요 없이 new만 늘어놔도 되는데 파일은 닫는 코드를 수동으로 매번 써 줘야 하는 게 처음엔 아주 웃기게 느껴졌었다.

(3) 그리고 끝으로.. sizeof 연산자가 없는 것에서 한번 더 경악했다. 수동 메모리 할당이 없고 포인터도 없고, sizeof마저 없다는 건 어떤 개체의 메모리 내부 덤프 같은 건 꿈에도 생각하지 말라는 얘기다.
primitive type이야 개별 크기가 어떤 기기의 JVM에서도 불변 동일하니(가령, int 4바이트, long 8바이트..) 굳이 sizeof에다 요청하지 않아도 되고..

하긴, 특정 개체의 메모리 주소 같은 걸 어디에다 낼름 누설해 줄수록 프로그램의 엔트로피가 커지고 Garbage collector가 추적해야 하는 영역이 넓어지고, 메모리 누수와 잘못된 포인터 에러가 날 확률이 높아지니.. 그런 건 최대한 애초에 할 필요가 없게 만들어야 할 것이다.
하지만 바이너리 덤프를 바로 못 하면 파일을 읽고 쓰는 작업도 좀 불편할 것 같다. 하다못해 과거에 베이직조차도 포인터는 없어도 숫자의 binary dump를 문자열 형태로 얻거나 그걸 디코딩하는 함수는 있었거늘 말이다.

C#은 Java처럼 가상 머신 기반에 자동 메모리 관리가 제공되는 언어이지만 이런 것들이 Java보다는 융통성이 있다. ref라는 키워드가 있어서 primitive type도 call by reference가 가능하다.
그리고 sizeof 연산자가 제한적으로 지원된다. primitive type과 단순 구조체 한정으로만 사용 가능하며, 자동 관리를 받는 자기 객체(managed class)에 대해서는 sizeof를 여전히 할 수 없다.

C#의 초창기 구버전에서는 아예 unsafe 코드 안에서만 sizeof를 사용할 수 있었지만 나중에 제약이 완화되었다.
그래도 C#과 Java 어느 경우든, sizeof라는 건 저수준 메모리 접근과 관계가 있는 기능이지, 가상 머신 고수준 코드와는 어울리지 않는 건 공통인 듯하다.

5. 객체의 배열

그러고 보니 Java는 클래스 객체들이 다 기본적으로 포인터 단위로 취급된다는 특성상, 객체의 동적 배열을 만드는 것도 좀 불편하다.

MyObject[] arr = new MyObject[n];

이건 MyObject들을 담을 배열 객체부터 하나 만드는 것이다. 그 뒤에 arr[0]부터 arr[n-1]에다가 MyObject의 인스턴스를 new로 할당하는 건 사용자가 또 직접 해야 한다.
C++이야 Java처럼 행동하고 싶으면 MyObject **arr을 하면 되고, 아니면 MyObject가 default 생성자만 있다면 곧장 MyObject *arr = MyObject[n]으로 객체의 배열을 만들 수 있으니 융통성이 있다.

그리고 한 객체가 여러 클래스들을 돌아다니면서 쓰이다 보면, 이 오브젝트가 값만 일치하는 게 아니라 본질적으로 그 오브젝트가 맞는지.. C++로 치면 주소값이 같은지, Java조차도 문자열은 equals 대신 ==를 써서 행하는 그 비교 대상값이 일치하는지.. 주소를 직접 확인하고 싶은 경우가 있다.

C/C++은 & 연산자가 있으니 일도 아닌 반면, Java는 디버거 창이 아닌 로그 확인을 위해서는 toString을 일일이 해 줘야 하는 것이 불편한 점으로 남는다.
포인터라는 게 위험하다고 직접 노출을 금기시하다 보니, 포인터로 직통으로 할 수 있는 일까지 좀 불편하게 바뀌는 건 어쩔 수 없나 보다.

6. 클로저

C++은 수동 메모리 관리뿐만 아니라 pointer-to-member(더 나아가 함수 포인터라는 것 자체까지)라는 것도 오늘날의 타 언어들이 제공하는 깔끔한 클로저와는 전혀 관계 없는 원시적이고 지저분한 물건이다. C/C++은(정확히는 C가) machine word를 정말 좋아하는 언어이며, '포인터' 하나만으로 간편하게 구현 가능하지 않은 요소는 쿨하게 제공 안 하는 게 전통적인 설계 철학이었다.

그러니 함수 안에 함수를 만드는 걸 지원하지 않으며(당대의 경쟁 언어이던 파스칼과 달리), C++도 클래스 안에 클래스, 함수 안에 지역 클래스 같은 건 전적으로 접근성 scope 구분만 할 뿐, 자기 밖에 있는 클래스 멤버나 함수 지역변수에 자동으로 접근하는 메커니즘 같은 건 제공하지 않는다. 즉, Java 용어로 말하자면 static class만 지원한다는 것이다.

C++만 쓰다가 Java나 Objective-C 같은 타 언어에서 this 내지 상부 클래스 멤버에 접근 가능한 함수를 스레드 콜백 등으로 자유자재로 넘겨줄 수 있는 걸 보니 아주 신기했다. 요즘은 함수형 언어 영향을 받아서 함수 몸체를 이름조차 안 붙이고 바로 넘겨줘도 되니 더 편하다.

이것도 타 언어들이 더 객체지향 철학에 따른 것이고, C++이 기괴하고 경직된 구조의 언어인 셈이다. 물론, C++의 사고방식은 저런 유연한(?) 함수 포인터(C++ 용어), 셀렉터(옵C 용어), 클로저(???)를 구현하기 위해 내부적으로 부과되는 시공간 성능 오버헤드가 무엇인지 생각하는 데는 도움이 될 듯하다.

C++은 저게 없는 대신에 P2M을 갖고 있는 셈인데.. C++의 또 다른 괴물 기능인 다중 상속과 연계하려다 보니 P2M도 도저히 '날포인터' 하나만으로 간편하게 구현할 수가 없어졌으니 참 아이러니이다. 이건 언어 설계 차원에서의 결함이나 마찬가지라고 봐도 할 말 없을 정도이며, 이와 관련하여 본인이 몇 년 전에 글을 쓴 적이 있다.

그나저나 Java의 상속 "extends A"는 C++로 치면 ": public A", 다시 말해 언제나 public 상속과 같은 것이겠지?
난 부모 멤버들에 대한 접근에 더 많은 제약을 가하는 private, protected 상속은 사용해 본 적이 없다.

7. 자동 메모리 관리, 동적 배열 등, 나머지 생각들

뭐, 직장에서 Java 개발도 좀 해 보니 깔끔한 1파일 1클래스 구조에다 헤더와 소스 구분이 없고 코드 파싱과 빌드가 정말 광속인 것(C++ 외의 타 언어들이 대부분 그렇지만), throw IllegalArgumentException("value must be >=0") 예외 처리가 사실상 assertion failure이나 마찬가지이니 assert(0 && "error") 이런 테크닉이 없어도 되는 것들은.. 마음에 든다.

ABI 계층이 파편화 없이 딱 잘 통합돼 있고, 무식한 헤더 파일 대신 패키지로부터 추출· 복원된 프레임워크 소스와 코딩 컨벤션.. 매번 다시 컴파일 되면서 특정 타입과 완전히 결합해 버리는 C++의 템플릿 대신에, 진짜로 유연한 void*를 캡슐화했다고 볼 수 있는 제네릭.. 이런 건 부럽기도 하고 현대의 프로그래밍 언어와 프로그래밍 환경이 얼마나 발전했는지를 뒷북으로나마 느낀다.

프로그래밍에서 "네이티브 코드 + 메모리 100% 수동 관리"(C/C++) 아니면 "가상 머신 + garbage collector 기반의 메모리 자동 관리"(C#/Java)의 차이는, 자동차 운전으로 치면 수동 vs 자동 변속기와도 비슷할 정도로 큰 차이인 것 같다. 자동차에서는 면허 조건이 달라질 정도로 큰 차이를 만들며, 프로그래밍에서도 뒷일 생각 안 하고 마음대로 new를 남발해도 되냐 그렇지 않느냐의 차이는 매우 크다.

(그럼 메모리가 자동 관리되는 언어에서는... 무슨 서버나 GUI 프로그램이 아니고 일체의 아이들 타임이 없이, 자기 할 일만 일괄 처리하고 끝나는 콘솔(명령 프롬프트)+단일 스레드 프로그램이라면 GC가 언제 어떻게 개입하여 동작하는지 의문이 든다. 뭐 그냥 메모리 할당 부분에서.. 자원 반납 없이 지금까지 메모리를 사용한 양이 도를 넘어선다 싶으면 그때 동작할 수도 있긴 하겠다.)

C++을 까는 사람들의 심정은 이해한다. 하지만 C++이 그렇게 지저분하고 자비심이 없는 대신에 어떤 넘사벽급의 강력한 네이티브 코드를 생산할 수 있는지, 그리고 Java가 편한 대신에 내부적으로 성능을 얼마나 많이 희생했고 런타임 오버헤드가 더해졌는지를 전부 논하지 않고 단편적인 비교만으로 언어 호불호를 논하는 것은 바람직한 태도가 아니라고 여겨진다. 둘 다 장단점이 있고 고유한 주 용도가 있는 법이다.

옛날에 잠깐 써 봤던 파스칼은 포인터도 있고 자유로운 call by reference도 지원했지만 그 포인터가 C처럼 막강하고 배열과 연계되는 건 절대 아니며, 동적 배열을 못 만들었다. 아무리 파스칼이 교육용 언어를 표방하고 만들어졌다 해도 베이직으로도 가능한 기능이 없는 건 말이 안 되니 저건 후대의 파생 언어에서 개선되었지 싶다.

Posted by 사무엘

2018/03/13 08:34 2018/03/13 08:34
, ,
Response
No Trackback , 4 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1467

1.
10여 년 전, 본인이 MMORPG 게임을 개발하던 회사에서 병특 복무를 하던 시절에는 주된 업무 주문이 기존 제품을 WOW와 더 비슷하게 고치는 것이었다. 그 당시에 WOW가 인기가 장난이 아니었기 때문에..
지금은 회사에서 어쩌다 보니 안드로이드 앱을 개발하게 됐는데, 주된 작업이 기존 제품을 다른 유명 SNS 앱과 더 비슷하게 고치는 것이다.

세상은 유행이 변하고 이런 식으로 돌고 도는 것 같다. 20년쯤 전에 Codeguru 같은 사이트에서 MFC CWnd 클래스를 상속받아서 온갖 기발한 UI 컨트롤 내지 MS Office 스타일의 도구모음줄을 만들어 올리고 테크닉을 공유하는 게 유행이었다면.. 지금은 안드로이드에서 더 현란하고 기가 막힌 화면 전환, GUI 컨트롤을 만드는 게 유행의 뒤를 물려받아 있다. 또한 소스포지라든가 다른 사이트들은 다 망하고 개발자 관련해서는 오로지 스택 오버플로우와 github만이 본좌이다..

요즘은 소프트웨어로 엔드 유저로부터 수익을 내는 방식이 "특정 기술과 기능, 컨텐츠 자체에 대한 접근성" 자체는 아니어 보인다. 기술과 기능, 컨텐츠는 이제 너무 넘쳐나고 저렴해진지라, 정말 획기적이고 새로운 게 아니면 그닥 변별이 안 되는 것 같다. id 소프트웨어가 1990년대 옛날처럼 없던 그래픽 기술을 새로 개발하고 선보이면서 주목받지는 않고 있는 것을 생각하면 된다.
그 대신 요즘은 다들 모바일에서 다들 내 자신의 정체성을 표현하는 데 드는 대가로 수익을 내는가 보다. 이모티콘, 아바타 같은 것 말이다.

2.
카카오톡에서 그냥 있으니까 정말 아무 생각 없이 기본 이모티콘들을 지금까지 써 왔다.
라이언인지 뭔지 알 게 뭐야? 난 그냥 개그만화 보기 좋은 날의 '쿠마키치'=_=;; 짝퉁이라고 생각해 왔는데.. 알고 보니 사자였구나. 갈기 없는 숫사자랜다~ ㅋㅋ

카카오프렌즈 이게 사이버 공간을 떠나서 봉제인형과 각종 캐릭터로 로얄티 받으면서 그야말로 돈을 빗자루로 쓸어담고 있다고 한다. 우리나라는 둘리· 호돌이처럼 옛날에 히트 쳤던 걸 제외하면 캐릭터 지지리도 못 만드는 나라인 걸로 본인은 알고 있는데..-_-;; 어째 이걸로도 돈줄을 성공적으로 텄구나.

영화나 만화처럼 뭔가 스토리가 있는 매체에서 유래된 캐릭터가 아니고,
앵그리버드의 동그랗고 눈썹 짙은 빨간 새처럼 인기 게임에서 유래된 것도 아니고..
그저 채팅 앱의 이모티콘에서 유래된 캐릭터가 이렇게 초대박을 치리라고는 난 절대로 예상할 수 없었다.

한낱 아스키 아트에 불과하던 이모티콘이란 게 그 다음으로는 찐빵 얼굴에 기하학적이고 추상적인 기호, 픽토그램 수준이었다가 이제는 거의 움짤 수준으로 변모하고 있다.
그리고 이게 말을 직접 하기 난감할 때, 얼굴 표정과 므흣한 감정을 대신 전달하는 용도로 생각보다 유용하기도 하다.

그리고 유튜브 동영상의 썸네일 이미지도 있다. 그냥 동영상의 임의 구간 스틸 영상을 썸네일로 지정하는 게 너무 당연하다고 생각해 왔고 문제 의식을 하나도 느끼지 않고 지냈는데.. 이것도 글자와 그림을 넣어서 귀신 같이 꾸며 주는 앱이 있다...;;

난 그냥 날개셋이나 우직하게 만들고 논문 써야지, 미래의 시장 판도를 예측하고 유행을 읽고 돈 벌 아이템 찾는 사업가 기질은 정말 아닌 것 같다.. ㅠㅠ

그나저나 애니메이션 이모티콘들은 gif도(꼴랑 256색) 아니고 플래시도 아니고(모바일에서 망함..) 도대체 무슨 기술을 써서 표현하는지 궁금해진다. 애니메이션 png 규격이 완성되기라도 했나..?

3.
프로그래밍 환경 내지 플랫폼을 처음부터 오랫동안 접하면 API나 방법론이 수시로 바뀌는 것 때문에 귀찮고 지저분한 일을 많이 겪게 된다. 안드로이드 내지 Cocoa API에서 숱하게 나오는 deprecated 경고들을 보니 그런 생각이 더욱 절실히 든다. 이러니 인터넷에 굴러다니는 코드들을 아무거나 선뜻 믿고 쓸 수가 없다.

그런 시절을 몽땅 건너뛰고 모든 게 그럭저럭 갖춰지고 안정화된 뒤에 프로그래밍을 시작하면 지저분한 일을 겪지는 않는다.. 하지만 이젠 모든 게 다 갖춰진 너무 방대하고 복잡한 프로그래밍 시스템 속에서 방황하게 된다. 오늘날의 웹 내지 앱 개발 환경과 30여 년 전 GWBASIC 내지 끽해야 도스 API 인터럽트 프로그래밍 환경은 얼마나 극과 극으로 다른가?
Windows는 그나마 초딩 시절부터 내가 오랫동안 써 왔으니까 익숙해진 것이지, 내가 2, 30년쯤 뒤에 늦게 태어났으면 프로그래밍을 진로로 정하지 않았을 가능성이 높아 보인다.

옛날에는 컴퓨터 성능이 빈약하고 각종 소프트웨어 시스템도 지금보다 훨씬 더 단순했겠지만.. 그게 아무 이유 없이 단순한 건 아니었다. 성능 대비 기계값이 지금보다 훨씬 더 비싸고 프로그래밍 관련 정보를 구하기도 더 어려웠다. 금수저 내지 최소한 중산층 집안이 아니면 아무나 컴퓨터를 접할 수 없었으며, 프로그래밍 저변이 지금처럼 널리 확대될 수 없었다. 그때나 지금이나 프로그래밍 여건에 관한 한 일장일단이 있다.

그 최첨단 문명의 이기인 컴퓨터가 그래도 기업· 연구소· 군대· 정부 기관의 전유물이 되지 않고, Google 같은 엄청난 검색 엔진이 부자들의 전유물이 되지 않고, 만민에게 정보 접근성이 주어지고 차별이 사실상 없어진 것은 매우 다행이고 축복이어 보인다. 비록, 그 반대급부로 어린애들에게 음란물과 폭력물에 대한 접근성까지 너무 올라간 것은 좀 심각한 문제이지만 말이다.

이건 컴퓨터가 진지하고 심각한 일뿐만 아니라 엔터테인먼트 도구로도 활용해도 돈벌이가 되니까 자본주의 내지 시장 경제 논리에 의해 대중화가 된 것일 뿐이다. 돈줄을 따라 자연스럽게 이뤄진 현상에 대해서 너무 지나치게 염세주의 음모론적으로 해석할 필요는 없을 것이다.

컴퓨터가 워낙 비싼 기계이니까 소수의 엘리트에게만 기회를 주는 게 아니라, 일단 컴퓨터 자체는 팍팍 뿌린다. 대량생산으로 가격을 낮추고 온갖 교묘한 우회 결제 수단으로(= 일시불로 기계값을 몽땅 내지 않아도 되게 오랫동안 찔끔찔끔..) 소비자 부담을 줄여서 말이다. 그 뒤 온갖 컨텐츠들로 더 많은 수익을 내는 게 소비자에게도 좋고 생산자에게도 좋은 것이다.

4.
미국이나 이스라엘, 인도 같은 나라 말고 유럽에서 나름 세계구급 IT 강국을 꼽자면 노키아와 리누스 토르발스를 배출한 핀란드가 떠오르는 편인데.. 옛날에는 불가리아도 한 끗발 했었다.
우리나라에서는 비슷한 이름의 요구르트 때문에 "생명 연장이라는 게 요구르트만 딥다 쳐먹는다고 되는 게 아닙니다" 같은 개드립의 원산지라는 이미지가 강한 편이다.;;

하지만 저 나라는 요구르트만 개발한 게 아니라, 1980년대에 국가적으로 컴퓨터 교육을 실시하고 나름 고급 IT 엔지니어 육성을 했다.
1989년 5월에 제 1회, 국제 정보 올림피아드라는 게 최초로 개최된 곳도 미국이나 다른 유명한 나라가 아니라 바로 '불가리아'였다. 이 역시 생각할 점이다.

그런데 문제는 그렇게 양성된 컴퓨터 똘똘이들이 자국에서 자기 재능을 좋은 방향으로 쓰면서 돈과 명예를 얻는 인프라가 없었다는 거다.
그래서 불가리아에서 컴퓨터와 관련해서 세계구 급으로 선한 게 개발되어 나온 게 없었다. 그 대신 불가리아 산 컴퓨터 바이러스들이 악명을 떨쳤다.
당장 떠오르는 건 DIR-II, 어둠의 복수자(dark avenger) 바이러스. 이들의 원산지가 바로 저 나라였다. 그러고 나서 1990년대 이후부터 불가리아의 존재감은.. 지금 다들 알려진 바와 같다.

그 먼 옛날에 컴퓨터 바이러스를 만들 정도의 사람이라면 그 비싸고 귀하던 IBM PC의 내부 구조와 도스 API, x86 어셈블리를 다 마스터 했고 컴공이 얼마나 대단했겠는가? 그랬는데 만들어 낸 건 고작 남에게 해를 끼치는 물건뿐이었던 거다.
지금도 북괴에서는 아무리 컴퓨터 똘똘이가 돼 봤자 하는 일은 당의 명령대로 오픈소스들 다 무단으로 베껴서 이상한 프로그램 만들거나, 중국으로 외화벌이 정보전사로 파견 나가서 사이버 범죄, 남조선 종북 여론몰이 같은 지저분한 짓밖에 없다. 그런 것과 비슷한 맥락의 안타까운 상황이다.

5.
전에 얘기한 적이 있던가?
본인은 초딩 저학년 때 8비트 컴, 중· 고학년 때 16비트 IBM 호환 PC, 중학교 때 Windows와 PC통신, 고등학교 때 인터넷, 대학교 때 휴대전화, 대학원 때 스마트폰을 순서대로 접했다.
대학교 때 기숙사에서 10Mbps 유선 랜을 처음으로 접했고, 2003년쯤에 무선 인터넷과 USB 메모리라는 걸 처음으로 접했다.

내가 대학을 졸업할 즈음부터 대학원 연구실을 시작으로 유선 랜의 속도가 100Mbps로 올랐으며, 그로부터 얼마 안 가 교내 네트워크 주소들이 공인 고정 class B ip 대신, 가변 사설 ip로 바뀌었지 싶다.
이제는 그냥 무선 인터넷으로도 신호가 좋은 곳에서는 늘 100Mbps까지는 아니어도 수십 Mbps의 속도가 나오고, 유튜브로 HD급 동영상을 실시간 스트리밍으로 보는 시대가 됐다. 개인적으로는 정말 충격적이다. PC통신으로 사진 한 장 받던 시절의 전송 속도와 지금 속도를 비교하면 말이다.

지금이 우주 정거장 관광 단가가 1억 원 근처까지 내려갔다거나, 스페이스 오딧세이 2001 영화가 묘사하는 세상이 오지는 않았다. 컴퓨터의 클럭 속도가 싱글 코어로 10GHz를 넘어간다거나 하지도 않았다. 하지만 과거에 상상하지 못했던 SNS 미디어, 고화질 동영상과 더 새끈해진 글꼴, 각진 게 아니라 날렵한 외형의 자동차들이 시대의 변화를 대신 말해 주고 있다. 이것 말고도..

  • 휴대전화가 없던 시절엔 오지에서 운전을 하다가 차가 퍼지거나 사고가 나면 보험사 연락을 어떻게 했을까?
  • CCTV와 블랙박스가 없던 시절엔 교통사고 과실 비율 산정이 얼마나 주먹구구식으로 진행됐을 것이며(바퀴가 굴러가는 이상 절대적인 100:0이란 존재하지 않는다.. -_-), 가해자와 피해자가 뒤바뀐 억울한 경우도 얼마나 많았을까?
  • 구글과 riss.kr, dbpia가 없던 시절에 도대체 학술 문헌 검색을 어떻게 하고 논문이란 걸 어떻게 썼을까? (일일이 도서관 찾아다니면서 실물을..)
  • msdn이야 그렇다 치더라도, 스택 오버플로우와 검색 엔진이 없던 시절에는 도대체 생소한 플랫폼에 대한 프로그래밍 자료 검색을 어떻게 하면서 코딩을 어떻게 했을까?
  • 15년 전이나 지금이나 그냥 이더넷 랜선을 꽂는 건 동일한데 랜 카드와 내부 기술 기반이 뭐가 바뀌었길래 인터넷 속도가 옛날보다 10배 이상으로 뻥튀기될 수 있었을까??
  • 전자기파의 물리적인 특성이 100년 전이나 지금이나 바뀐 건 없을 텐데 텔레비전의 화질은 어쩜 이렇게 좋아졌을까?

이런 걸 생각하면 과학 기술, 특히 정보 통신 분야의 기술이 세상을 얼마나 드라마틱하게 바꿔 놓았는지를 알 수 있다.

그 전까지 몇몇 얼리어답터들이나 쓰던 PDA, 휴대용 MP3 플레이어가 휴대전화와 결합해서 스마트폰으로 탄생한 건 정말 2000년대의 혁신 중의 혁신이 아닐 수 없었다. 그냥 크기도 작고 기능도 열악한 특수 목적 컴퓨터의 범주인 '임베디드'로부터 '모바일'이라는 완전히 새로운 범주가 파생돼 나왔다. Windows CE가 Phone, Mobile 등 갖가지 브랜드로 재탄생해야 한 것을 보면 이해하기 쉽다. 지금이야 그냥 10이라는 브랜드로 통합됐고, 스마트폰 OS로는 안드로이드가 지구를 평정했다만 말이다.

물론 냉동 기술이나 플라스틱, 의학· 생명 공학처럼 굳이 IT에 속하지 않는 획기적인 기술도 있다.
기술의 발달 덕분에 쿼츠 시계는 기계식 태엽 시계를 가격과 성능 모든 면에서 쳐발랐고, LED는 백열등은 말할 것도 없고 형광등까지 쳐발라서 인류가 발명한 가장 고효율 광원을 달성했다. 핵 무기는 같은 무게의 재래식 폭탄에 비해 위력 계수에 0을 몇 개 더 붙였다.

이런 정도의 혁신이 앞으로 어느 분야에서건 또 나올 게 있으려나 모르겠다.
무탄피총, 실리콘 반도체를 능가하는 컴퓨터 소자, 자동차에서 현행 기계들의 한계를 극복하는 무단 변속기나 반켈 엔진, 혁신적인 무선 송전이나 2차 전지, 핵융합 발전 같은 것 말이다.

6.
정보 통신 쪽 얘기를 계속하자면..
자동차의 번호를 자동으로 인식하는 무인 단속 카메라와 무인 주차 시스템도 지금이야 너무나 당연하게 여겨지고 있지만, 국내에 본격적으로 도입된 건 1990년대 중후반부터이다. 지금으로부터 20년이 채 되지 않았다. 이런 게 없던 시절에는 과속· 신호 위반 같은 건 경찰관이 숨어 있다가 위반 차량을 강제로 불러다 세우는 식으로 단속을 할 수밖에 없었다.

차량 번호판을 인식하는 기술은 길거리에서 도난· 수배 차량이나 세금· 통행료 상습 체납 차량을 즉시 잡아내는 데에도 아주 요긴하게 쓰이고 있다. 이게 없었으면 컴퓨터와 행정 전산망이 있더라도 사람이 일일이 차 번호를 입력해서 조회해야 했으니 일이 얼마나 불편했을지 모른다. 스피드건이 생각보다 속도를 굉장히 정확히 측정해 주는 것만큼이나 신기한 일이다.

단순히 편해진 것뿐만 아니라 세상 돌아가는 시스템이 더 객관적이고 공정하게 바뀐 것은 얼마나 축복인지 모른다. 모든 것이 전산화되고 컴퓨터가 통제하는 세상에 대해 무슨 666 짐승의 표인 것처럼 공포심만 가질 필요는 없을 것이다.

Posted by 사무엘

2018/03/07 08:26 2018/03/07 08:26
, ,
Response
No Trackback , 4 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1465

1. 도스 시절 명령어

도스에서 파일(과 디렉터리)을 다른 곳에 복사하는 명령은 copy이다. 얘는 명령 셸인 command.com이 자체 지원하는 내장 명령이다.
그런데 도스에는 copy의 일종의 강화 버전이라 할 수 있는 xcopy라는 것도 있으며, 이건 별도의 프로그램을 통해 실행되는 외부 명령이다.

xcopy는 일반 copy와 달리 (1) 서로 다른 드라이브 사이에 서브디렉터리까지 재귀적으로 통째로 복사하는 걸 지원했으며, (2) 복사에 사용하는 메모리 버퍼 크기가 더 크고, 여러 개의 파일을 한꺼번에 읽은 뒤(대략 수백 KB 정도 크기까지) 타겟에다 쓰는 걸 지원했다. xcopy의 전치사 X는 일반적으로 cross라고 발음되었다.

지금 생각해 보면 이 둘은 차별화 요소가 전혀 될 수 없으며 굳이 분리할 필요가 없다. 그냥 copy가 원래 (2)처럼 동작하면 되고, (1)은 /s 같은 옵션을 추가해서 지원하면 될 일이다.
하지만 옛날에 xcopy가 외부 명령으로 존재했던 이유는 겨우 그 정도 고급 복사 옵션/기능마저도 command.com에다 상시 집어넣고 있기에는 메모리가 부족하고 아까웠기 때문이다. 옛날에는 베이직 인터프리터가 Ready 출력할 메모리조차 아까워서 프롬프트를 Ok로 바꾼 시절이 있었다는 것 기억하시는가? (단 3바이트를 아끼려고!) 검색을 해 보니 xcopy는 1987년, MS-DOS 3.2에서 첫 도입됐다고 한다.

하긴, 옛날에는 다단계 디렉터리를 재귀적으로 탐색하면서 뭔가를 하는 일이 쉽지 않아서 외부 유틸리티를 많이 이용해야 했다. 파일 찾기는 말할 것도 없고, 지우는 것도 옛날에는 deltree라는 명령이 따로 있었지 싶다. 그건 지금은 del에 /s옵션으로 통합됐지만 말이다.
유닉스 계열 셸은 내장과 외장 명령어 구분이 어찌 되나 모르겠다. cp, mv, rm은 외부 프로그램인 것 같던데 설마 pwd, cd 이런 것들도 다 외부 프로그램이려나?

그리고 옛날에는 플로피 디스크(일명 디스켓)란 게 쓰여서 파일 시스템 차원에서 완전히 똑같은 디스크 복제를 해 주는 diskcopy라는 외부 명령이 있었다. 얘는 굳이 외부 명령으로 만들 거면 디스크 이미지 파일을 만들거나 이로부터 복제 디스크를 만드는 기능도 같이 있었으면 좋았을 거라는 아쉬움이 남는다.

1.2~1.44MB짜리 디스크를 단일 드라이브에서 복사하려면 source와 target 디스켓을 여러 번 갈아 끼워야 했는데.. Norton Utilities 같은 다른 유틸리티들은 EMS 및 XMS 메모리를 활용하여 한 번만 갈아 끼우고 바로 복사가 된다는 걸 장점으로 내세우곤 했다. 프로그램을 하나 설치하려 해도 "제품의 이제 1~N번 디스크를 넣고 아무 키나 누르세요" 이러던 참 아련한 옛날 추억이다.

2. 16비트 Windows의 실행 모드

예전에 언급한 바와 같이, 1990년대 초중반에 Windows라는 운영체제가 32비트 플랫폼으로 처음 갈아타던 시절에는 Win32 API라는 것의 구현체가 Windows NT, Windows 95, Windows 3.1+Win32s라는 세 계통으로 나뉘었다. NT가 가장 이상적인 레퍼런스 구현체이고, Win32s는 제일 허접하다.

그리고 그 중간의 95는 비록 NT처럼 스레드도 지원하고 도스로부터 많이 독립했다고는 하지만, 도스로부터 완전히 독립했다기보다는 도스를 내부적으로 흡수· 합병한 것에 가깝다. Windows NT의 마이너 축소판이라기보다는 Win32s가 각종 한계 없이 제대로 구현된 형태라고 보는 게 더 타당하다.

그런데 95 계통의 전신이라 할 수 있는 Windows 3.0이 나왔을 때에는 동일 제품· 단일 바이너리 하에서 실행 모드가 세 갈래로 나뉘었다. 바로 8086 리얼 모드, 286 표준 모드, 386 확장 모드이다.
Windows NT 4가 가장 많은 아키텍처를 지원하는 32비트 운영체제였다면, Windows 3.0 (3.1 말고)은 실행 모드가 가장 다양했던 16비트 도스용 운영 환경(?)이었다.

원래 Windows 1과 2에서는 리얼 모드밖에 존재하지 않았으며, Windows는 진짜 도스 위의 덧실행 껍데기 그 이상도 이하도 아니었다. 리얼 모드에서는 메모리가 기본 640K밖에 없었으며, 그 번거로운 16비트 Windows 3.x보다도 프로그래밍 환경이 더 열악했다.

그러다 Windows 2.0의 후속 버전으로 2.1은 286 표준 모드를 도입한 Windows/286과, 386 확장 모드의 전신인 Windows/386 이렇게 두 갈래로 나왔다. 사실, 16비트 80286 프로세서에도 보호 모드가 있긴 했지만 프로그래밍에 애로사항이 많았는지 그걸 사용한 프로그램은 매우 소수였으며 여전히 거의 봉인돼 있었다. 그냥 EMS/XMS 같은 규격으로 메모리를 수백 KB 남짓 더 사용할 수 있다는 것에 의미를 둬야 했다. 386 확장 모드도 첫 버전답게 문제가 많았으며, 2.11이 더 나와야 했다.

그러다가 Windows 3.0은 리얼, 286 표준, 386 확장을 모두 통합하여 출시됐다. 이때는 DPMI 규격도 갓 제정되었기 때문에 2.x 시절보다 보호 모드 지원도 더 개선되었다.
이때는 3.0에다가 멀티미디어 API가 최초로 추가된 확장팩이 나왔으며, 3.1은 네트워크 API가 추가된 Windows for Workgroup 3.11, 그리고 중국어 입출력 지원이 추가된 3.2 이런 식으로 기능 확장팩이 많던 시절이었다. 지금처럼 인터넷을 통한 업데이트가 가능한 시절도 아니었으니 말이다.

Windows 3.1에서는 리얼 모드가 삭제되고 win /2 또는 /3을 통해 286 표준 모드만 지원하지만, 이것은 성능이 굉장히 많이 열화된 모드였다. 그리고 Windows 95부터는 표준 모드도 빠져서 기술적으로 언제나 386 확장 모드로만 동작하는 형태가 됐다.

이렇듯, Windows 3.x는 비록 순수한 32비트 운영체제는 아니지만 보호 모드라든가 도스용 프로그램을 내부에서 구동할 때처럼 일부 기능에서 80386 이상 CPU가 제공하는 가상화 기능을 사용하기 때문에 결과적으로 32비트 CPU가 필요했다. 386 확장 모드가 지원하는 기능이 그런 것이었다.
이는 과거에 존재하던 PIF 편집기가 확장 모드에서는 표준 모드에 비해서 지원하는 옵션이 얼마나 더 다양해지는지를 보면 얼추 짐작 가능하다. 286 표준 모드에서는 요게 전부이던 옵션이..

사용자 삽입 이미지

386 확장 모드에서는 XMS뿐만 아니라 EMS 규격, 그리고 멀티태스킹 우선권 등 다양한 옵션들이 별도의 대화상자와 함께 추가되는 걸 알 수 있다.

사용자 삽입 이미지사용자 삽입 이미지

Windows 95 역시 순수 32비트 프로그램이 아니면 도스용 프로그램에 대해서만 제대로 된 가상 머신과 선점형 멀티태스킹을 지원했다. 16비트 Windows 프로그램을 강제 종료하면 운영체제와 얽혀 있는 스레드 동기화 오브젝트 같은 것이 얼어붙으면서 시스템의 안정성에 심각한 문제가 발생하곤 했다.

LimitEmsPages 이런 함수는 32비트가 아니라 이미 Windows 3.x 시절부터 deprecated돼 있었는데, 아마 리얼 모드 시절의 잔재이지 싶다.
Windows 1~2.x용 실행 파일은 후대 Windows와 실행 파일 포맷은 동일하지만(NE) 내부의 플래그로 구분되어 있어서 3.x에서 실행하면 "제대로 실행되지 않을 수 있음" 경고가 뜨곤 했다. 요컨대 Windows의 역사를 살펴보면, 16비트에서 32비트로 넘어갈 때만치 큰 변화는 아니지만, 같은 16비트 안에서도 1~2.x와 3.x 사이에 보호 모드가 도입되기 전과 후에는 기술적으로 나름 변화와 단절이 있었다고 볼 수 있다.

3. 외주로 제작되었던 보조 프로그램과 게임

Windows에 내장돼 있는 기본 프로그램들 중에는 마소에서 직접 만들지 않은 외주 프로그램도 있다. Windows 95 시절에 잠깐 있었던 하이퍼터미널이라는 터미널 접속 프로그램이 대표적인 예로, 스플래시 화면이라든가 각종 UI 외형이 대놓고 기존 마소 프로그램과는 어울리지 않아서 마소 자체 개발이 아니라는 걸 알 수 있었다.

또한 게임 중에서도 Windows XP에까지 제공되었던 3D 핀볼(시네마트로닉스 개발, 맥시스 유통)은 외부 프로그램이었으며, Vista/7 시절에 그래픽이 쇄신했던 지뢰찾기 등의 기본 게임들도 외주였다(Oberon games).

그런데 더 옛날에 Windows 3.1의 내장 프로그램들을 보면, 굉장히 단순하게 생겼고 이 정도면 자체 제작했을 법도 한 프로그램이 About 대화상자를 보면 외주인 경우가 은근히 더 있었다. 게다가 아래의 목록에서 보다시피 제작자/제작사가 프로그램마다 완전 제각각이었다.

  • 계산기: Kraig Brockschmidt
  • 터미널: Future Soft Engineering 사
  • 레코더: Softbridge 사
  • 지뢰찾기: Robert Donner & Curt Johnson
  • 카드놀이: Wes Cherry

그러니 Vista/7 이전에 제공되던 기본 게임들도 알고 보니 외주였던 셈이다. 특히 지뢰찾기는 Windows 1.0부터 3.0까지 존재하던 Reversi(일명 오델로)를 대체할 목적으로 3.1에서 처음 선보였던 게임이기도 하다.
레코더의 경우 Windows의 역사상 거의 전무후무하게 존재하던 키보드· 마우스 매크로 유틸리티인데 95와 그 이후로는 결코 재등장하지 않았으니 희소성이 크다.

물론 이것들 말고 프로그램 관리자, 파일 관리자, 제어판이라든가 간판 앱인 문서 작성기(오늘날의 워드패드)와 페인트(오늘날의 그림판)은 외주가 아니라 내부 자체 제작이다.

4. 색깔의 미묘한 차이

말이 나왔으니 옛날 추억 회상을 더 해 보자면,
컴퓨터의 주메모리가 아니라 비디오 메모리가 딱 1MB이던 시절에는 Windows 3.1의 비디오 모드를 (1) 꼴랑 640*480 저해상도인 대신에 트루컬러, (2) 800*600에서 적당하게 하이 컬러, 아니면 (3) 1024*768에서 256색.. 셋 중 하나로 골라 쓰는 재미(?)가 있었다.

그리고 Windows 3.1은 원래는 우리에게 익숙한 짙은 파란색으로 윈도우의 굵은 틀을 표현했지만, 하이 컬러 이상부터는 어인 일인지 은은한 하늘색으로 색깔을 바꿔 표시했다. 왜 무슨 근거로 색깔을 바꿨는지는 모르겠지만 개인적으로 아주 신기하게 느껴졌었다.

사용자 삽입 이미지사용자 삽입 이미지

그 이전 3.0은 슈퍼 VGA나 트루컬러로 진입할 일이 있긴 있었는지, 그래픽 드라이버가 개발되거나 3.1 것과 호환되긴 했는지에 대해 본인은 아는 바가 없으며, 이에 대해서 회의적이다.

Posted by 사무엘

2018/03/04 08:30 2018/03/04 08:30
, ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1464

C++ 다중 상속 생각

날개셋 한글 입력기 같은 Windows용 프로그램을 개발하다 보면 여러 개의 COM 인터페이스를 한꺼번에 상속받아 구현한 단일 클래스를 구현하게 된다.

그런데 하루는 이런 의문이 들었다. 각각의 인터페이스들이 다 IUnknown을 상속받았는데 어떻게 어느 인터페이스로 접근하든지 AddRef, Release 같은 공통 인터페이스들은 중복 없이 동일한 함수 및 동일한 숫자 카운터 인스턴스로 연결될까? 데이터 멤버 없이 인터페이스 상속만 하면 this 포인터 보정이 필요 없이 다중 상속과 관련된 문제들이 상당수 깔끔하게 해결될까?

그래서 클래스 A, 이로부터 상속받은 B와 C, 그리고 B와 C를 다중 상속한 D 이렇게 네 개의 클래스가 있을 때 일명 ‘죽음의 다이아몬드’ 현상을 해소하는 방법이 무엇이 있는지를 정리해 봤다. C++의 다중 상속과 관련해서는 이제 더 글을 쓸 게 없을 줄 알았는데 내가 지금까지 생각하지 못하고 있던 요소들이 더 있었다.

1. 가상 상속

클래스에서 상속이라는 건 기술적으로 어떤 구조체에다가 부모 클래스의 컨텐츠(데이터 멤버)들을 앞에 쭉 늘어놓고 나서 그 뒤에 나 자신의 컨텐츠를 추가하는 것과 같다. 그러니 부모 클래스와 자식 클래스 포인터를 형변환 하는 건 그냥 프로그래밍 언어 차원에서의 의미 변환일 뿐, 메모리 주소가 바뀌는 것은 전혀 없다. 아주 쉽다.

그런데 가상 상속은 부모 클래스의 컨텐츠를 그렇게 나 자신의 일부로서 고정된 영역에 배치하는 게 아니라, 포인터로 참조하는 것과 같다.
부모 클래스를 ‘가상’이라는 방식으로 상속한 자식 클래스는 부모 클래스와 자식 클래스가 굳이 메모리 상에 연속된 형태로 있지 않아도 된다. 그러니 동일 부모를 공유하는 다수의 클래스가 다중 상속되더라도 이들이 공통의 유일한 부모 하나만을 가리키게 하면, 한 부모 클래스의 데이터들이 불필요하게 여러 번 상속되는 것을 막을 수 있다.

여기서 중요한 것은, D가 B, C를 ‘가상’ 상속하는 게 아니라는 점이다. 부모인 B와 C가 A를 미리 가상으로 상속해 놔야 한다.
가상 함수도 자식이 아닌 부모 클래스에서 미리 지정해 놔야 하듯 말이다.

그러니 클래스 라이브러리 개발자는 공통 부모를 공유하는 여러 클래스들이 사용자에 의해 다중 상속되겠다 싶으면 그 공통 부모를 virtual로 상속하도록 설계를 미리 해 놔야 한다. 특히 그 클래스(공통 부모 말고)가 순수 가상 함수 같은 걸 포함하고 있어서 상속이 100% 필수라면 더욱 그러하다.
앞의 A~D의 경우, 혹시 A가 default constructor가 없어서 B, C의 생성자에 모두 A를 초기화하는 인자가 들어있었다 하더라도, D의 A는 D의 생성자에서 제공된 인자만으로 딱 한 번만 초기화된다.

가상 상속을 한 자식 클래스는 굉장히 이색적인 특징을 하나 갖게 된다.
자식 클래스의 포인터에서 부모 클래스의 포인터로 형변환을 하는 것이야 너무 당연한 귀결이며, 반대로 부모에서 자식으로 가는 건 좀 위험한 일이긴 하지만 어쨌든 가능하다. 단일 상속에서는 말할 필요도 없고, 다중 상속이라 하더라도 그냥 고정된 크기만큼의 포인터 덧셈/뺄셈만 하면 된다.
그에 반해, 부모 클래스에서 자신을 virtual 상속한 자식 클래스로 형변환은 일반적으로 허용되지 않는다…!

A *pa = new D; //자식에서 부모로 가는 건 당연히 되고
B *pb = new D;
D *pd;
pd = static_cast<D*>(pb); //부모에서 자식으로 가는 건 요건 괜찮지만
pd = static_cast<D*>(pa); //요건 안 된다는 뜻..

그 자식 클래스의 주소와 부모 클래스의 주소 사이에는 컴파일 타임 때 결정되는 관계 내지 개연성이 없기 때문이다.
자식에서 부모로 거슬러 올라가는 게 단방향 연결 리스트를 타는 것과 다를 바 없게 됐는데, 저런 형변환은 단방향 연결 리스트를 역추적하는 것과 같으니까 말이다.

물론, 가상 상속이라 해도 현실에서는 D라는 오브젝트 내부에서 A가 배치되는 오프셋은 고정불변일 것이고 컴파일러가 그 값을 계산하는 게 불가능하지 않을 것이다. 모든 자식 클래스들과 연속적으로 배치되지만 않을 뿐이다.
static_cast를 어거지로 구현하라면 구현할 수는 있다. 하지만 이 A가 반드시 D에 속한 A라는 보장도 없고, 포인터에 무엇이 들어있는지 확신할 수 없는데.. C++ 컴파일러가 그런 어거지 무리수까지 구현하지는 않기로 한 모양이다.

2. 가상 함수로 이뤄진 추상 클래스(인터페이스)들만 상속

죽음의 다이아몬드를 해소하기 위해서 요즘 프로그래밍 언어들은 C++ 같은 우악스러운 수준의 다중 상속을 허용하지 않고, 잘 알다시피 데이터 멤버 없고 가상 함수로만 구성된 추상 클래스들의 다중 상속만 허용하곤 한다.
그러면 문제의 복잡도가 크게 줄어들긴 한다. 효과가 있다. 하지만 그게 전부, 장땡은 아니다.

명시적인 데이터가 없는 클래스라 하더라도 가상 함수가 들어있는 클래스를 상속받을 경우, 2개째와 그 이후부터는 클래스 하나당 vtbl (가상 함수 테이블 v-table) 포인터만치 클래스의 덩치가 커지게 된다.

단일 상속 체계에서는 this 포인터의 변화가 전무하니 상속을 제아무리 많이 하더라도 한 vtbl의 크기만 커질 뿐, 그 테이블을 가리키는 포인터의 개수 자체가 늘어날 필요는 없다.
그러나 다중 상속에서는 D 같은 한 객체가 상황에 따라 클래스 B 행세도 하고 클래스 C 행세도 하면서 카멜레온처럼 변할 수 있어야 한다. 그렇기 때문에 A, B, D일 때의 vtbl, 그리고 C일 때의 vtbl 이렇게, 테이블과 테이블 포인터가 둘 필요하다.

클래스 D에 속하는 인스턴스 포인터(가령, D *pd)를 부모 C의 포인터로 변환해서 전달할 때는 pd는 A, B, D 같은 직통 상속 계열 vtbl이 아니라 C의 vtbl을 가리키는 형태로 오프셋이 보정된다. 그리고 여기서 가상 함수를 호출하면.. this 포인터가 C가 아닌 D를 기준으로, 보정 전의 형태로 복구된 채로 함수에 전해진다. 이 함수는 애초부터 C가 아닌 D에 소속된 함수이기 때문이다.

즉, 다중 상속에서 가상 함수를 호출하면 비록 겉으로 this 포인터는 바뀐 게 없지만 내부적으로 vtbl을 찾는 것을 부모와 자식 클래스가 완전히 동일하게 수행하기 위해서 보정이 일어나고, 그걸 함수에다 호출할 때는 보정 전의 값을 전하도록 일종의 thunk 함수가 먼저 수행된다.
한 클래스 오브젝트에서 여러 인터페이스 함수를 자유자재로 호출하는 polymorphism의 이면에는 이런 비용 오버헤드가 존재하는 셈이다. 무슨 숫자나 문자열로 메시지를 전하는 게 아닌 이상, 서로 다른 클래스에 존재하는 가상 함수는 vtbl의 종류와 오프셋으로 구분할 수밖에 없다.

3. 멤버로만 갖기

다중 상속의 지저분함을 회피하는 방법 중 하나는.. 원하는 기능이 들어있는 클래스를 내 아래로 상속하지 말고 그냥 멤버 변수로 갖는 것이다. 상속하더라도 걔만 따로 상속해서 확장 구현을 한 뒤에 그걸 멤버 변수로 갖는다. 이 개념을 유식한 용어로는 aggregation이라고 한다.

이 방법은 다중 상속의 각종 오버헤드는 피할 수 있지만 그만큼 다른 방면에서 불편을 야기한다. 그 클래스가 동작하는 과정에서 내 클래스의 함수 및 데이터를 빈번하게 참조해야 한다면(결합도 coupling가 높은 관계..) 그 통로를 억지로 트는 게 더 불편하며 코드를 지저분하게 만든다. 또한 서로 다른 클래스 간에 중복 없이 동일한 기능을 제공하는 일관된 인터페이스를 만드는 게 다중 상속이 아니면 답이 없는 경우도 있다.

이게 경험상 딱 떨어지는 답이 있는 문제가 아니다. 복잡한 클래스 계층이 필요한 대규모 개발을 한 경험이 없는 프로그래머라면 이런 부류의 문제는 배경을 이해하는 것조차 난감할 것이다. 그렇기 때문에 다중 상속이 무조건 나쁘기만 한 건 아니며, 그걸 억지로 우회하다 보면 결국 다른 형태로 불편함과 성능 오버헤드가 야기될 거라며 다중 상속을 옹호하는 프로그래머도 있다.

이상.
객체지향 프로그래밍 언어에서 다중 상속은 사람마다 취향 논란이 많은 주제이다. 비록 C++이 이걸 지원하는 유일한 언어는 아니지만, 네이티브 코드 생성이 가능한 유명한 언어 중에서는 C++이 사실상 대표격인 것처럼 취급받고 있다.

어떤 기능이 절대적으로 나쁜 것만 아니다면야 없는 것보다는 있는 게 좋을 것이다. 상술했다시피 다중 상속이 가능해서 아주 편리한 경우도 물론 있다. 한 오브젝트로 다수 개의 기반 클래스 행세를 자동으로 하는 것과, 그 오브젝트 내부의 구현 함수에서는 여러 기반 클래스를 넘나드는 게 동시에 되니까 말이다.

하지만 다중 상속은 가성비를 따져 보니 그 부작용과 오버헤드, 삽질을 감수하면서까지 굳이 구현하고 지원할 필요가 있나 하는 게 PL계의 다수설 대세로 흐르고 있다. this 포인터의 보정이라든가, 복수 개의 기반 클래스들이 또 공통의 기반 클래스를 갖고 있을 때 발생하는 모호성의 처리 등.. 템플릿 export만치 막장은 아니지만 컴파일러 개발자와 PL 연구자들의 고개는 설레설레 저어지곤 했다.

그래서 C++ 이후에 등장한 더 깔끔한 언어인 D, C#, Java 등은 다중 상속을 지원하지 않는다. 그 대신 다중 상속을 우회하고 복잡도를 완화하기 위해, 적어도 가상 상속만은 할 필요가 없게끔 static 내지 가상 함수 선언만 잔뜩 들어있는 인터페이스에 대해서만 다중 상속을 허용하는 것이다. Java는 두 종류의 상속을 extends와 implements라고 아예 구분까지 했다.

물론 이런 패러다임 하에서는.. 프로그램 구조가 간단해서 가상 함수로 만들 필요가 없는 것까지 일단은 인터페이스부터 만들어 놓고 구현 클래스를 내부적으로 또 만드는 식의 오버헤드 정도는 감수해야 한다. 하지만 Java는 final이 아닌 함수는 기본적으로 몽땅 가상 함수일 정도로.. 디자인 이념이 애초에 성능 대신 극도의 유연성이니, 그 관점에서는 그건 별 상관이 없는가 보다.

가장 대중적인 기술이 알고 보면 레거시 때문에 굉장히 지저분하고 기괴하기도 하다는 것은 CPU계에서 x86이 그 예이고, 프로그래밍 언어에서는 C++이 해당되지 싶다. 전처리기(+생짜 파일 기반 인클루드), 다중 상속, 클로저 없이 특유의 pointer-to-member 기능 같은 것 말이다. 후대 언어에서는 저런 게 결코 도입되지 않고 있다.

본인은 다중 상속을 굳이 의도적으로 기피하면서 코딩을 하지는 않는다. 다중 상속이 필요하고 당장 편하겠다 싶으면 한 클래스에다 막 엮었다. 그 상태로 막 복잡한 pointer-to-member나 람다를 구사하면서 컴파일러를 변태적으로 괴롭히지는 않을 것이고, 컴파일러가 그냥 this 포인터 보정을 알아서 해 주는 것만 원했으니까 말이다.

하루는 ", public"라고 검색을 해서 날개셋 한글 입력기의 소스 코드 내부에도 혹시 다중 상속을 쓴 부분이 있나 찾아 봤는데.. 그래도 2017년 현재 날개셋 한글 입력기의 소스 코드에는 데이터 멤버가 존재하는 클래스를 둘 이상 동시에 상속받은 부분은 없었다. 추가적인 상속처럼 보이는 것은 COM 인터페이스 내지, 내가 콜백 함수를 대신해서 내부적으로 만들어 놓은 추상 클래스 인터페이스들이었다.

한두 번 썼을 법도 해 보이는데.. 이 정도 규모의 프로그램을 만드는 데도 실질적인 다중 상속을 사용한 부분이 없다면 그건.. 정말로 가성비 대비 불필요하게 지원할 필요는 없을 법도 해 보인다.

Posted by 사무엘

2017/12/26 08:37 2017/12/26 08:37
, ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1441

« Previous : 1 : 2 : 3 : 4 : 5 : 6 : 7 : 8 : 9 : ... 12 : 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:
2983456
Today:
1193
Yesterday:
1381