« Previous : 1 : 2 : 3 : 4 : 5 : ... 31 : Next »

1. 자동차 배터리와 컴퓨터 스택 메모리

자동차의 배터리 방전과 컴퓨터 프로그램의 스택 오버플로는 서로 완전히 다른 분야이긴 하지만 공돌이의 심상 면에서 공통점이 느껴지는 것도 좀 있는 것 같다.
지난 수십 년 동안 자동차는 엔진 효율이 크게 향상됐고 컴퓨터의 메모리도 동적 힙(heap)이야 넘사벽 급으로 용량이 뻥튀기 됐지만, 저것들은 용량이 획기적으로 크게 올라간 적이 없다. (특별히 그럴 필요가 없어서..)

그리고 저 둘은 남은 용량이나 고갈 징후를 미리 알려 주는 메커니즘도 딱히 존재하지 않는다.
자동차의 황산-납 배터리는 스마트폰이나 노트북의 리튬이온 배터리처럼 용량이 몇 % 남았다고 알려주는 기능이 없다. 블랙박스도 배터리의 전력 용량이 감소하면서 전압도 같이 감소하는 걸 감지해서 간접적으로 꺼진다거나 할 뿐이다.

컴퓨터 프로그램의 스택 메모리도 지역변수의 주소를 이용해서 남은 용량을 아주 간접적으로 유추할 수 있고, 시스템 exception을 이용해서 일이 벌어졌을 때 스택 overflow를 감지할 수도 있다. 하지만 이식성 있는 일반적인 방법은 존재하지 않는다. 그걸 일일이 체크하는 건 아주 비효율적이다. 이런 식으로 유사점이 있다.

2. TLS 슬롯

메모리와 관련하여 이렇게 아기자기하게 제약이 될 수 있는 게 본인이 보기엔 TLS 슬롯 공간이다.
사실, 한 사람이 만드는 프로그램이 수십~수백 종류의 코드가 동시에 실행되는 프로그램을 만들 일은 극히 드물겠지만, 문제는 내가 만들지 않은 프로그램(특히 DLL)들이 한 프로세스에 왕창 붙어서 실행될 때이다. 이런 코드들이 전부 TLS 슬롯을 하나씩만 요청하더라도 TLS 공간은 수십~수백 개씩 소모될 수 있다.

Windows 95 내지 NT 3.x시절에는 현실적으로 필요한 양과 그 당시 컴퓨터들의 평균적인 사양을 감안해서 스레드마다 TLS 슬롯이 64개씩 배당되었다. 이게 바로 TLS_MINIMUM_AVAILABLE라는 상수값의 의미이다. 역대 win32 환경 중에 TLS 슬롯이 제일 적었던 구현체가 제공했던 최소 개수가 64라는 뜻이다.

세월이 흐를수록 기본 제공되는 TLS 슬롯 수는 점차 늘어나서 Windows XP/2000부터는 64에다가 1024가 더해진 1088개라고 한다. 후대의 운영체제에서 슬롯 수가 이것보다 더 늘어났다는 얘기는 본인은 들어 보지 못했다. 힙 메모리처럼 엿가락처럼 한없이 더 늘어나야 할 필요는 없는 물건이기 때문이다.

스레드 단위로 전역적으로 공유돼야 하는 데이터가 많이 있거나 그 데이터의 길이가 들쭉날쭉 변한다면 별도의 heap 메모리를 할당하고 TLS 슬롯에다가는 포인터만 저장해야 한다. 즉, 응용 프로그램은 언제나 고정된 개수만의 슬롯을 사용하고, 슬롯 사용량을 최소화해야 한다.

내가 보기에 TLS가 꼭 필요한 때 중 하나는 사용하는 라이브러리가 너무 구닥다리여서 콜백 함수에 context 데이터를 넘겨주는 게 없어서 context 정보를 오로지 전역변수에 의지해야 할 때(+ 그런데 thread-safety가 보장돼야 할 때) 정도이다.
한 프로그램이 스레드 10개로 동작하건 100개로 동작하건 그건 소모되는 TLS 슬롯 개수와는 전혀 무관하다. 이건 그냥 실행되는 코드의 종류하고만 관계가 있다.

3. 복합 스레드 동기화

조금 부끄러운 얘기를 하자면.. 본인은 오래 전 학교의 컴공 운영체제 시간에 졸았는지, 아니면 다른 변고가 있었는지는 모르겠지만 나만의 스레드 동기화 오브젝트를 새로 만든다는 개념 내지 필요성을 지금까지 별로 이해하지 못했다. 그냥 운영체제가 제공하는 critical section이나 뮤텍스, 세마포어만 쓰면 끝이지 않은가..?? 그랬는데 회사 코드를 들여다보면서 뒤늦게야 '아...!!' 현타 비스무리한 걸 경험하게 됐다.

평범한 데이터 컨테이너가 멀티스레드 동작에 대비하여 곳곳에 뮤텍스 기반의 lock이 걸려 있는데.. 데이터를 건드리는 set쪽뿐만 아니라 단순히 read만 하는 get 메소드들까지도 내부가 전부 동일한 lock이 일일이 걸려 있었다. 흠, 이건 read일 뿐인데 여러 스레드가 동시에 접근해도 상관없지 않나? 저건 불필요한 삽질 오버헤드 아닌가? 이 lock은 빼 버려도 되겠는데?

그런데 알고 보니 그 생각은 절반만 맞았다. reader만 있을 때는 여러 스레드들이 동시에 접근해도 괜찮지만, 한 스레드라도 write를 할 때는 다른 writer는 물론이고 reader들도 다 줄 서서 기다려야 하기 때문이다. 그러니 get 함수도 lock이 전혀 없어서는 안 된다.

하지만 이렇게 정말 간단하고 범용적인 원리대로 lock의 종류를 구분하는 것이 운영체제 API로는 의외로 직통 구현돼 있지 않았다. 기존 동기화 오브젝트를 조합해서 사용자가 직접 구현해도 되며, 이런 복합 동기화는 읽기/쓰기 중 한쪽으로만 CPU가 너무 쏠리고 있을 때 분배를 어떻게 할지 같은 정책이 일방적으로 획일화 가능하지 않기 때문이다.

그래도 C++ 라이브러리의 스레드/동기화 클래스 중에는 이런 "다수 reader/단일 writer" 동기화를 구현한 놈이 혹시 있는지 모르겠다. 이런 클래스는 그냥 lock와 unlock만 있는 게 아니라 lock이 lockForRead와 lockForWrite로 세분화된다.

이렇게 custom 동기화 오브젝트를 만들면 기존 운영체제 API만으로는 바로 구현 가능하지 않은 복합 동기화를 시전할 수 있다. reader와 writer가 여러 놈이 동시에 경합할 때, 그리고 read와 write의 소요 시간이 차이가 많이 날 때 어떤 원칙으로 CPU를 배분할지 같은 세부 원칙도 손수 정할 수 있다.

그리고 디버그 빌드에서는 각종 참고 정보를 알려주고 오류를 검출하는 기능도 넣을 수 있다. 현재 포커스를 잡은 스레드, 또는 대기 중인 스레드들을 얻어 온다거나..
lock을 건 스레드가 아직 실행 중인데 그 동기화 오브젝트가 소멸되는 건 잘못된 상황이므로 곧장 예외나 assertion failure를 날린다.

(이 글을 쓰고 나서 나중에 알게 된 사실: Windows Vista에서부터 Slim Reader/Writer (SRW) Lock이라는 게 도입됐다. 크리티컬 섹션처럼 동일 프로세스 안에서만 사용 가능한 대신.. reader lock과 writer lock을 구분해서 요청 가능한 작고 가벼운 동기화 오브젝트이다. 역시 범용성이 있으니 2000년대 이후에야 도입됐구나 싶다.)

그리고 디버깅에 제일 도움이 되는 정보는.. 아무래도 deadlock 감지일 것이다.
응답이 멎은 프로그램을 강제로 break시킨 뒤, 스레드들의 스택 상태를 추적하다 보면 memory leak보다는 쉽게 찾을 수 있겠지만 크고 복잡하고 스레드를 왕창 많이 만드는 프로그램에서 이런 걸 프로그램이 디버그 정보를 통해 자동으로 찾아주면 디버깅에 큰 도움이 될 것이다. 물론 이 경우, 동기화 오브젝트가 락에 진입한 스레드들의 실행 정보를 일일이 관리하고 있어야 할 것이다.

Posted by 사무엘

2024/01/24 08:35 2024/01/24 08:35
,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/2256

1. 플랫폼 공통 스크립트

(1) qt가 단일 소스로 Windows, 리눅스, 맥에서 모두 똑같이 돌아갈 수 있는 GUI 프레임워크라면..
cmake는 단일 스크립트로 Visual Studio 프로젝트, 유닉스 계열 makefile, 그리고 xcode 프로젝트를 모두 생성해 주는 메타빌드 시스템이다.
그렇다면 qt를 사용해서 프로그램을 개발하고, 프로젝트 파일을 cmake로 관리한다면 진정한 크로스플랫폼 프로젝트를 만들 수 있을 것 같다. ㄲㄲㄲ

(2) 다음으로, 단일 소스/스크립트 기반으로 세 운영체제에서 똑같이 돌아가는 설치· 배포 패키지 생성 유틸은 없는지 궁금하다.
cmake(메타빌드)와 nsis(설치· 배포)는 스크립트 언어가 완전히 같은 문법 기반은 아니지만 좀 비슷하고 공통 조상을 둔 게 있는 것 같다.
얘들은 전문적인 프로그래밍 언어가 아니기 때문에 복잡한 수식에 복잡한 객체 선언, 배배 꼬인 복잡한 조건 분기 반복을 구현할 수는 없다는 공통점이 있다. 그리고 변수는 $로 시작해서 선언하고, 문자열 리터럴 안에다가 변수값을 바로 집어넣을 수 있다는 것도 비슷하다.

시대에 좀 뒤떨어지는 설치 배포 패키지는 고해상도 DPI를 지원하지 않는 경우가 있어서 좀 안습하다. 125~150% 배율 화면에서 설치 프로그램부터가 강제 확대되는 바람에 창이 뿌옇게 표시되면.. 정작 프로그램 당사자가 고해상도 DPI를 지원한다 해도 그 프로그램의 첫 사용 경험이 좋게 시작될 수 없을 테니 말이다. 본인은 이런 사례를 몇 번 본 적이 있다.

2. C/C++ 컴파일러

Windows용으로 쓸 만한 좀 가벼운 C/C++ 컴파일러가 없는지 좀 궁금하다.

  • 용량은 그냥 수십 MB 수준이며, 단독으로는 그냥 표준 C/C++ 라이브러리만 들어있고 명령 프롬프트 프로그램만 만들 수 있다.
  • MFC 같은 건 없어도 되고, 그냥 따로 설치한 플랫폼 SDK와 연계하면 Windows API 정도는 사용할 수 있다.
  • 프로젝트 없이 간단한 소스 코드 하나만으로 exe를 바로 만들 수 있다.
  • 특히 Visual Studio Code와 바로 연계해서 쓸 수 있다.

Visual C++은 정말 너무 무거워졌고.. Windows용 g++인지 뭔지는 런타임인 cygwin 깔고 이것저것 선행 작업이 많이 필요해서 무겁긴 마찬가지이다.
이렇게 딱 본질에만 충실한 개발 환경을 어디 구할 데 없을까? 개발툴이 무거워지는 건 인스턴트 메신저 프로그램들이 수익성 컨텐츠 집어넣느라 쓸데없이 너무 무거워지는 것과 비슷해 보인다.

요즘은 웹에서 어지간한 프로그래밍 언어들을 바로 코딩하고 돌려볼 수는 있다. 하지만 웹에서의 코딩 환경은 로컬 IDE와 같은 급으로 인텔리센스 자동 완성이 지원되지는 못하니 생산성이 떨어진다.
옛~~날에 요런 틈새시장 용으로 Dev C/C++라는 물건이 있었던 걸로 기억한다. IDE와 컴파일러 복합이었고, 개발사의 이름에 blood라는 단어가 있었는데=_=.. 그 뒤로 개발이 중단된 듯하다.

3. Visual Basic

Visual Basic 6은 사법시험 같고, Visual Basic .NET은 로스쿨 같다는 생각이 든다. 이렇게만 말하면 무슨 뜻인지 아시려나..?? 내가 보기엔 딱 그렇다. -_-;;

참고로, Visual Studio 툴 자체를 설치하지 않더라도, 닷넷용 언어들의 커맨드라인 컴파일러는 .NET 프레임워크를 설치하면 깔린다.
그러니 Windows에서는 리눅스처럼 gcc g++은 없지만, Windows\Microsoft .NET\아무 버전.. 디렉터리 가 보면
vbc (비베), csc (C#) 컴파일러는 어느 컴에나 다 있다.
그런데 C/C++ 컴파일러는 없으니 아쉽다. 비베는.. 6이건 .NET이건 실무 용도가 있기는 한지 개인적으로 궁금하다.

4. git

이놈의 git은 그냥 commit이나 push를 하기 전에 미리 "지금 원격 저장소에는 또 최신 작업 내역이 있는데요. pull부터 먼저 하시겠습니까?" 이렇게 말이다.
커밋할 때부터 지금 중앙 저장소의 상태가 최신이 아니니까 미리 니 쪽에서 pull부터 하고 나서 커밋 하는게 좋겠다고 좀 알려줬으면 좋겠다.
맨날 push할 때 충돌 난다고 뒤늦게 징징대서 사람 귀찮게 하지 말고 말이다. 이러면 commit 그래프도 일직선이 아니라 더 지저분한 모양이 된다. 이건 시스템이 좀 개선돼야 할 것 같다.

5. Visual Studio Code

오~ 써 보니 사용자 경험이 좋고 꽤 괜찮다!!
빌드 가능할 정도로 정교하게 프로젝트/makefile을 세팅할 필요 없이 디렉터리만 지정해 주면, 거기 있는 소스와 헤더 파일을 알아서 '적당히' 파싱 해서 심벌과 파일명 검색, 명칭 자동 완성이 가능한 범언어적 에디터.
요런 틈새시장 제품이 Source Insight밖에 없는 줄 알았는데 말이다. 쟤도 그 틈새를 멋지게 잘 공략했다.

외형 껍데기가 깔끔 모던하고, 파일 내용 변경한 게 find in files 결과창 같은 데에 실시간으로 쓱쓱 반영되는 것도 좋다.
마구 마구 아이디어가 샘솟고 코딩을 하고 싶어진다.
걍 Visual Studio IDE만 쓰면 되지 에디터가 굳이 따로 필요하나 소신이었는데, 이 정도 에디터면 프로그래밍 생산성에도 긍정적인 영향을 줄 것 같다.
Source Insight는 유료인 반면, 쟤는 무료이기까지 하다. Source Insight 측에서 분발해야 할 듯.

6. 궁금한 것: 공유 라이브러리 디렉터리

Windows에는 프로그램의 빌드 때만 쓰이는 정적 라이브러리인 lib, 그리고 프로그램이 실행될 때 매번 쓰이는 동적 라이브러리 dll이 있다. dll을 찾는 순서로는 현 디렉터리, 실행 파일이 있는 디렉터리, Windows 시스템 디렉터리, PATH에 등록된 디렉터리 등.. 여러 복잡한 절차가 존재한다.

시스템 디렉터리의 포화를 막고 DLL hell 현상도 해소하려고 20년도 더 전에 side-by-side assembly라는 기법이 도입되긴 했다. 하지만 사용이 너무 까다로워서 그런지 이건 마소 자기들끼리만 쓰고 제3자 개발자들은 잘 쓰지 않는 것 같다.
그리고 COM이야.. 파일 이름이나 디렉터리 같은 저수준 방식이 아니라 객체의 클래스ID로 DLL을 식별하는 거나 마찬가지이다. 깔끔하기는 하지만 레지스트리를 건드려야 하고 다른 방식으로 사용이 너무 까다롭고 복잡하다.
이미 COM을 기반으로 만들어진 DirectX, OLE 같은 특정 분야의 API를 사용할 때나 이걸 쓰지, 얘 방식으로 뭔가 새로운 컴포넌트를 만드는 일은 잘 없다. =_=;;

자, Windows 동네는 상황이 이렇고, 유닉스 계열에서는 이와 비슷한 개념으로 정적 라이브러리 a와 동적 라이브러리 so가 있는 걸로 안다. 그리고 내가 알기로, 거기도 so를 특정 사용자용 bin, 공용 bin 등으로 구분해서 수용하며, so 파일을 찾는 정형화된 절차가 있다. 구체적인 내역은 모르지만 말이다.
macOS는 거기에다가 dylib인지 framework인지 하는 개념도 있다. 이건 Windows의 side-by-side assembly나 COM처럼 자신들만의 컴포넌트 규격인 걸까? 이것들의 관계는 무엇인지 잘 모르겠다.

개인적으로 C/C++을 처음 공부하던 시절에도 모듈과 번역 단위 개념이 나올 때부터 생소하고 어려웠다. 무엇이든 범위가 여러 소스 파일, 여러 파일 수준이 되면 어려워지는 것 같다.

7. 콘솔(터미널): 화면을 모두 지우는 명령 등

Windows의 명령 프롬프트에서 CLS는 그야말로 현재 콘솔 버퍼에 있는 모든 출력 내용들을 싹 다 날리는 명령이다. 명령 프롬프트의 강화 버전인 PowerShell이나 Windows Terminal에서도 동일하게 사용 가능하다.
그러나 맥과 putty 터미널에서 clear는 기존 표시된 내용들을 다 위쪽으로 밀어내서 지금 보이는 겉보기 화면만 싹 정리된 듯이 보이게 한다. 화면을 위로 스크롤 시키면 기존 내용들을 여전히 다 확인할 수 있다.

난 개인적으로 이 동작이 굉장히 성가시고 불편했다. 빌드를 돌리고 나서 에러를 확인한 뒤, 에러를 고치고 clear 후 다시 빌드를 돌리는데 이전 빌드의 에러가 자꾸 검색되면 좋을 게 없기 때문이다.
화면을 CLS처럼 완전히 싹 지우는 기능은 '스크롤백 날리기'라고 보통 메뉴에서 별도의 명령으로 존재하는 편이더라. 차라리 clear이나 CLS 명령의 옵션으로 둘 다(전체 vs 한 화면만) 제공하면 어떨까 싶지만, 또 그렇지는 않더라.

개인적인 생각은 다른 터미널들에서도 모든 출력을 싹 날리는 게 더 쉽게 가능했으면 좋겠다.
도스의 배치 파일에도 if errorlevel goto 같은 아주 간단한 제어문이 지원되긴 했지만, 유닉스 계열의 셸 스크립트는 말할 것도 없고 GWBASIC하고도 비교가 민망한 허접한 기능밖에 없었다.;;

탐색기에서 자기 컴퓨터뿐만 아니라 LAN/FTP 상의 다른 컴퓨터까지 바로 들어갈 수 있으면 좋다.
그것처럼 한 터미널에서 내 컴뿐만 아니라 원격 컴퓨터의 터미널에도 바로 들어갈 수 있으면 좋을 것 같다.
마소에서도 이에 대한 필요성을 느끼고 마냥 도스 기반이 아니라 더 전문화된 터미널 앱을 제공하는 것이지 싶다. 너무 늙은 putty조차 대체할 수 있게 말이다.

putty는 문자열 찾는 기능과 특정 문자열이 나타났을 때 highlight 표시하는 기능이 좀 있었으면 좋겠다.

8. 앱들의 개발 형태의 변화

어제 오늘 일은 당연히 아니지만.. 개인용 컴퓨터라는 게 인터넷 단말기나 게임기로 바뀌어 가니.. 단순 정보 조회 프로그램도 이제는 다 PC가 아니라 웹 기반으로 바뀌어 간다. 예전 같았으면 RAD 툴이라도 썼을 법한 프로그램도 이제는 어지간해서는 웹인 듯..

사용자가 직접 다루는 키오스크 앱은..? 테이블마다 태블릿을 갖다놓고 웹이나 앱으로도 만드는 것 같다. 매출관리 프로그램은 직원만 다루니 키오스크처럼 비주얼 UI를 신경 쓸 필요는 없겠지만.. 얘도 바뀌어 간다.
Delphi나 Visual Basic 같은 통상적인 RAD 툴에 대한 수요도 20년 전에 비해 확실히 줄어들었지 싶다.

단순 사전류 프로그램은 한컴사전밖에 안 남았고.. 도움말/문서는 빼박 다 웹이다. 로컬에다 제공하지 않는다.
Windows는 help 디렉터리에 두툼한 도움말 파일들이 사라졌고, Visual Studio의 몇 기가짜리 MSDN도 없어졌다. 2015쯤부터 말이다.

에구~~ 개인적으로는 오프라인 문서가 아예 없어져 버리면 심리적으로 좀 불편한데 말이다. 뭔가 붕 뜬 느낌이다.
종이책이 컴퓨터 viewer 기반으로 바뀌었을 때 약간 떴고, 컨텐츠가 이젠 내 하드에 저장조차 되지 않고 늘 인터넷 연결이 필요하다면.. 더 붕 뜬다. 이게 피할 수 없는 대세이긴 하지만..
이런 시국에 종이책이라든가, PC용 프로그램이 담당해야 할 영역이 무엇인가 하는 생각을 진지하게 하게 된다.

Posted by 사무엘

2023/11/17 08:35 2023/11/17 08:35
Response
No Trackback , 3 Comments
RSS :
http://moogi.new21.org/tc/rss/response/2231

오랜만에 또 C/C++ 문법 잡생각들을 늘어놓아 본다.

1. elaborated type specifier

C에서는 struct, enum, union 타입의 변수를 지정하려면 말 그대로 저 '종류' 명칭을 먼저 지정하고 나서 타입 명칭을 명시해야 했다. 종류 명칭을 생략하고 타입 명칭만으로 해당 종류를 나타내려면 C에서는 typedef를 번거롭게 해 줘야 했다.
그래서 C 시절에는 typedef struct _XXX { ... } XXX; 이런 두벌일이 관행이었다. struct _XXX라고 하든가, XXX라고 하든가 둘 중 하나다.

그러던 게 C++에서는 class라는 종류가 또 추가되었으며, 타입을 선언할 때 종류 명칭을 생략해도 되게 바뀌었다. struct XXX { ... }; 만 해도 XXX를 단독으로 쓸 수 있는 셈이다.
종류 명칭 지정은 required가 아니라 optional이 된 건데.. 허나, C++에서도 종류 명칭을 반드시 지정해야 할 때가 있다. 이런 full 명칭을 "elaborated type specifier"이라고 부르는데, 이게 필요한 상황은 바로 타입 명칭과 변수 명칭이 겹칠 때이다.

굉장히 의외이고 사실 권장되지 않는 관행이기도 하지만, C/C++에서는 기존 타입명과 동일한 명칭으로 변수를 선언하는 게 가능하다. (int, float 같은 built-in 타입 예약어는 당연히 제외)
ABC라는 클래스가 있다면 ABC ABC;라고.. ABC라는 이름의 객체/변수를 그대로 선언할 수 있다는 것이다. '야마토 급 전함 야마토'처럼 말이다.

두 클래스 A, B가 있고 앞에서 A B; 라고 B라는 변수를 선점해 버렸다고 치자.
이때 나중에 B라는 클래스의 인스턴스를 또 선언하고 싶다면 그때는 class B 뭐시기.. 이렇게 명시함으로써 이 B는 변수가 아닌 타입 명칭임을 알려줄 수 있다. A라는 클래스 소속의 변수 B, B라는 클래스 소속의 변수 A라고 상호 참조시키는 건 불가능하지 않으나 너무 사악해 보인다. -_-;;

전역변수와 지역변수가 이름이 겹칠 때 구분을 위해 :: 연산자를 사용한다면(C++ 한정), 변수명과 타입명이 겹칠 때 저런 종류 지정자가 쓰인다는 것이다.
내 개인적으로는 저 때야말로 typename 키워드도 사용 가능해야 하지 않나 생각하는데.. 그건 허용되지 않는 것 같다. ㄲㄲㄲㄲ typename과 class가 혼용 가능한(interchangable) 곳은 템플릿 인자뿐이다.

그 반면, 저기서는 struct와 class가 혼용 가능하다. 즉, class A라고 선언해 놓고는 elaborated type specifier로 struct A라고 쓰는 건 가벼운 경고 하나만 나오고 허용이다. 흥미롭지 않은지? =_=;; typename은 템플릿 바깥에서 범용적인 elaborated type specifier로서는 아직 접점이 없는 셈이다.

아울러, class는 자체적인 scope도 생성하는 역할을 한다. 그래서 :: 연산자에 잘못된 명칭이 지정됐을 때의 컴파일 에러는 "XXXX는 class 또는 namespace의 명칭이 아닙니다"이다. 요럴 때는 class가 말 그대로 namespace와 엮인다.
"class vs struct / typename / namespace"라니.. 이것도 흥미로운 점이다.

하긴, 변수명과 타입명이 겹치는 게 가능하니까 망정이지, 겹칠 수가 없다면 C 라이브러리의 struct tm (time.h)은 당장 이름이 바뀌어야 했을 것이다. 너무 짧고 겹치기 쉽고 성의 없게 만들어진 명칭이다. -_-;;

2. 정수형의 다양한 alias들

C/C++은 boolean 타입조차 없이 전부 int로 퉁치는 정수 덕후였다. 하지만 세월이 흐르면서 type-safety에 대한 필요성이 부각되었고, 용도에 따라 다음과 같은 alias 타입들이 등장해서 쓰이게 됐다.

(1) wchar_t (문자열): 유니코드 때문에 등장했고 얘 자체는 언어 표준으로 등극했다. wcslen, wcscpy 함수라든가, L"" 리터럴까지..
하지만 문자의 크기가 플랫폼별로 2바이트 내지 4바이트로 심하게 파편화됐다. 이 때문에 코드의 이식성을 저해하고 프로그래머들에게 큰 혼란을 끼치게 됐다.
결국 직접적인 크기를 명시하는 char16_t, char32_t가 나중에 일일이 추가됐다. 하지만 이것도 각 타입별 함수라든가 리터럴의 표기 방법, 심지어 % 문자열의 형식이 플랫폼마다 완전히 통일돼 있지 않다. 이식성 문제가 완전히 해결되지는 않았다는 뜻이다.

참고로 얘들은 다 built-in type이며, 기존 부호 없는 정수형의 단순 typedef가 아니다. 가령, char16_t의 포인터는 unsigned short의 포인터와 호환되지 않는다.
그리고 char이야 플랫폼 불문하고 무조건 1바이트라는 게 언어 스펙 차원에서 정의돼 있으니 char8_t를 또 만들 필요는 없다. 하지만 1바이트 문자열을 가리키는 char*는 처음부터 부호 없는 정수형으로 만들었으면 깔끔했을 텐데 하는 아쉬움이 좀 있다.

(2) ssize_t size_t (컴퓨터 비트 수): charXX_t처럼 일반 정수형도 크기를 명시한 intXX_t, uintXX_t 같은 게 도입됐는데, 얘들은 charXX_t와 달리 그냥 typedef이다.
그리고 64비트에서는 int와 long의 크기가 플랫폼별로 파편화돼 버린 관계로, 어디서나 포인터 크기와 동일함이 보장되는 정수형이 따로 만들어졌다. size_t라든가 intptr_t, uintptr_t, ptrdiff_t 말이다.
int를 4바이트로 유지시킨 건 그렇다 쳐도, long까지 32비트 4바이트로 굳힌 플랫폼은 Windows가 유일하다. 하위 호환성에 정말 목숨을 건 결정이다.

(3) time_t (미래 시간): 얘는 문자열이나 컴퓨터와 직접적인 관계는 없지만.. 그래도 21세기보다 훨씬 더 먼 미래를 표현하기 위해서 64비트로 확장되었다. time_t가 32비트이던 시절 기준으로 빌드된 구닥다리 프로그램들은 15년쯤 뒤 2038년 이후부터는 제대로 쓰기가 어려워질 것이다.
참고로 얘는 언제나 부호 "있는" 정수로 정의된다. 시각뿐만 아니라 두 시각의 차인 '시간'을 표현할 때도 쓰이기 때문이다. 과거와 미래를 모두 분간하려면 당연히 부호가 필요하다.

이런 숫자 alias들은 %문자와는 영 어울리지 않는다는 걸 알 수 있다. 저 typedef의 유동적인 비트수에 맞게 printf/scanf의 % 문자가 모든 플랫폼에 맞게 바뀌게 하려면... % 리터럴도 #define 해 가면서 바꾸면서 정말 지저분한 짓을 해야 된다. %ls인지 %S인지..?? %Id인지 %lld인지 %I64d인지.. 알 게 뭔가?

물론 값을 출력할 때는 모든 가변인자들이 intptr_t 크기로 promote되기 때문에 상황이 조금은 단순해진다. 하지만 입력을 받을 때라든가 32비트 플랫폼에서 64비트 값을 다룰 때는 역시 % 문자와 실제 변수 짝을 조심해서 대응시켜야 한다. 이러느니 C++ stream을 쓰고 말지.. =_=;;
그래도 %문자를 쓰는 게 다국어 지원 localize 관점에서는 취급이 아주 편리하다는 장점도 있는데 말이다. 차라리 독자적으로 % 문자 해석기를 만들기라도 해야 하나 싶다.

3. <=> 연산자

C/C++엔 ? : 이라고 유일하게 3개의 피연산자를 받는 독특한 연산자가 있다. if else문을 연산식 하나에다 박아 넣은 것이고, 오버로딩이 되지 않는다. 얘는 그냥 if else문만큼이나 C/C++의 문법처럼 취급되기 때문이다.
그런데, C++20에서는 단일 토큰으로서 길이가 3자나 되면서 연산 결과도 boolean 2종류가 아니라 '3종류'인 참 독특한 연산자가 추가되었다. 바로 <=> ... a <=> b는 a와 b의 대소 관계에 따라 1 0 -1 중 하나를 되돌린다. (실제로는 정확하게 정수형이 아니라 저 세 종류를 나타내는 comparision 객체 타입)
쉽게 말해 a, b가 문자열이라면 이 연산자의 결과는 strcmp 함수의 결과와 같다.

연산식에서 이 연산자가 당장 막 쓰이지는 않을 수 있다. 그러나 어떤 클래스를 구현할 때 이 연산자는 굉장히 유용하게 쓰일 것 같다. 얘는 온갖 자잘한 비교 연산자들의 상위 호환이기 때문이다.
<=> 연산자 하나만 오버로딩 해 놓으면 > < >= <= == != 을 모두 유추할 수 있다. a==b는 a<=>b == 0 이렇게 말이다.

이 연산자가 지원되는 클래스는 Java로 치면 Comparable 인터페이스를 받아서 CompareTo 메소드를 구현한 거나 마찬가지일 것이다.
C의 사고방식이라면 이 함수의 리턴값은 그냥 int이겠지만.. 얘는 C++의 이념이 가미됐다 보니 built-in 연산자의 리턴 타입이 언어 차원에서 따로 정의돼 있다.

Visual C++에서도 최신 C++20 표준 문법 옵션을 켜 주면 바로 써 볼 수 있다.
외국에서는 <=> 가 무슨 우주선(!!!!)처럼 생겼다면서 spaceship operator이라는 애칭으로 불리는가 보다.
10여 년 전엔 R-value 참조자 &&가 아주 참신하게 느껴졌는데 지금은 쟤가 비슷하게 참신하게 느껴진다.

4. 나머지 C

(1) 비트필드에 배열이 지원됐으면 좋겠다는 생각을 하는데.. 5비트씩 n개 같은 식으로 말이다. 이건 너무 욕심 부린 걸까..?? ㅎㅎ
뭐, 컴파일러의 입장에서 코드를 생성하는 게 힘들 수는 있지만.. 그래도 불가능하지는 않을 텐데 말이다.
아키텍처에 따라서 멤버들 방향 지정을 자동화하는 것과 더불어 개인적으로 비트필드에 바라는 사항이다.

(2) 배열의 원소 개수를 구하는 arraysize, 그리고 배열에서 특정 멤버의 오프셋을 구하는 offsetof
이거는 언어의 기본 문법과 연산자만으로 구현 가능하기 때문에 딱히 예약어로 지정돼 있지는 않다.
하지만 최소한 표준 라이브러리에 채택돼서 표준 헤더에서 제공할 만은 해 보인다. 특히 arraysize의 경우, C에서는 그냥 x/x[0] 같은 매크로로 구현되겠지만 C++에서는 더 type-safe한 인라인 템플릿 함수로 제공되면 될 것이다.

(3) C에는 자기 번역 단위의 밖으로 노출되지 않는 static 변수와 함수가 C++ 사고방식으로 치면 private 멤버와 얼추 비슷한 지위이다.
static 함수가 한 소스 파일 안에서 선언되고 참조(= 호출)도 됐는데 그 함수의 몸체가 정의돼 있지 않으면?? 이건 링크 에러가 아니라 해당 번역 단위에 대한 컴파일 에러로 처리된다. 오오~!! 다른 번역 단위들을 뒤질 필요가 없기 때문이다.
C++로 치면 unnamed 익명 클래스라든가 함수 안의 local 클래스에서 멤버 함수의 몸체가 곧장 정의되지 않은 것과 비슷한 상황이다. 이런 일회용 클래스들은 함수의 몸체를 바깥 딴 데서 찾을 만한 여지가 없다. ^^

C와 C++에서 이런 캡슐화 패러다임의 차이가 드러날 때가 있다.
한 클래스 A의 내부에서만 쓰이고 마는 내부 클래스 B를 그냥 A.cpp 안에다가 global scope로 선언할지, 아니면 A가 선언된 A.h 헤더 파일에다가 A 내부의 scope로 private 선언할지 말이다.
객체지향 이념에 따르자면 헤더 파일에다가 선언하는 게 좋지만, 실용적으로는 그냥 cpp가 낫다. 헤더에다가 넣으면 외부에 노출되지 않는 클래스인데도 수정할 때마다 그 헤더 의존하는 소스 파일들이 다 빌드되니까 말이다.

5. 나머지 C++

(1) "한 번도 참조되지 않은 변수"라고 경고(컴파일러 또는 정적 분석에 의해)가 뜨는 걸 무시하기 위해서 [](...){}(a,b,c,d,e); 라는 람다가 쓰인다니 참 대단하다. 아울러,
auto convert(const istream &input)  -> void;
void convert(const istream &input);

클래스의 멤버 함수도 이렇게 람다 스타일로 선언할 수 있으며, 위의 둘은 완전히 동치라고 한다. typedef 대신 using을 쓰는 문법과 비슷해 보인다. ㄲㄲㄲㄲㄲ

(2) 그나저나 using은 typedef의 완벽한 상위 호환이어서 typedef는 이제 쓸 필요가 전혀 없어지는 건지? signed 같은 잉여가 되는 건가 싶다. 템플릿 인자에서 class가 typename으로 대체되고 static 함수가 익명 namespace 함수로 바뀌는 것과 비슷한 양상인데, typedef는 쟤 말고는 다른 용도가 전혀 없으니 말이다.
using A = B는 파스칼에서 type A = B와 형태가 아주 비슷해 보이기도 한다.

(3) C++의 iterator들은 어지간한 건 내부 구현이 그냥 포인터 하나와 다를 바 없을 텐데.. intptr_t 같은 정수 하나로 간단하게 reinterpret_cast가 가능했으면 좋겠다. 그래야 type-safe하지 않은 C 스타일 콜백 같은 데서도 내부적으로 C++ 컨테이너의 원소에 접근할 수 있기 때문이다.
특히 list, vector 말이다. hash는 모르겠다만.. 트리 기반 컨테이너인 set, map은 그 특성상 노드들이 parent 노드 포인터까지 갖고 있는데, iterator도 포인터 하나만 갖고 있어도 다음 진행 방향을 결정할 수 있지 않은가?
하지만 포인터 하나보다 크기가 더 큰 iterator도 심심찮게 보이는 것 같다.

(4) constexpr은 C++도 단순 read-only와 진정한 constant의 구분을 두려는 시도인 듯하다. 게다가 멀쩡한 함수를 '인라인화'도 모자라서 컴파일 시점에서의 상수로 바꾼다니..
팩토리얼이나 피보나치 수열 상수를 재귀적으로 구하는 건 예전에는 템플릿 클래스의 상수값 형태로나 가능했다. 하지만 이제는 C/C++ 상으로 멀쩡하게 생긴 함수의 호출 형태로도 표현 가능해졌다.
뭐, 템플릿에서도 static_assert와 더불어 많이 활약할 것으로 예상되는데, 자세한 건 더 공부해 봐야겠다.

(5) 객체를 초기화할 때 생성자 obj(arg)나 대입 연산 obj=arg 말고 중괄호는 배열이나 구조체를 초기화할 때에나 쓰이는 물건으로 여겨졌다. 하지만 C++11부터는 이게 initializer list라는 개념으로 리모델링되어 임의의 클래스의 public 멤버들을 순서대로 초기화할 때도 쓰고, 컨테이너에다 여러 원소들을 한꺼번에 집어넣을 때도 쓰일 수 있게 됐다.
참 혁신적이긴 하지만 용도가 너무 다양한 것 같다. 모호성이 발생하지는 않는지, {...}는 그럼 R-value 리터럴인 건지, 내가 만드는 클래스에서 저런 걸 받아들이려면 어떡해야 하는지 궁금한 게 많다. 이것도 공부 필요.. =_=;

(6) 인터페이스를 여러 개 받아서 구현한 클래스가 정작 그 인터페이스들의 base로는(예: IUnknown) 모호하다고 형변환 되지 않는 오류 말이다(Visual C++ 기준 C2594). 정말 아무 의미 없고 멍청한 페이크에 가까운 오류인데..
base가 고유한 vtbl이 없고 데이터 멤버도 없다면 그냥 자기 this에서 가장 가까운 base를 언어 차원에서 알아서 지정하게 하는 게 좋지 않을까? 애초에 자기 데이터가 없는데 가상 상속을 할 필요도 전혀 없는걸? 궁금하다.
이게 언어 차원에서 interface라는 게 없고 그 대신 무식한 다중/가상 상속을 지향하며 만들어진 C++의 맹점인 것 같다.

(7) 나는 C/C++ 문법을 어지간한 건 다 마스터 해서 머리에 숙지하고 있고, 아무 코드나 보면 머릿속으로 가상의 컴파일러를 돌려서 "얘는 이런 식으로 기계어로 번역되겠다, 구현 비용이 얼마나 되겠다, 이렇게 동작하겠다, 이런 문제가 있다" 같은 게 예측이 된다고 생각해 왔다. 넓은 의미에서 암산과 비슷한 경지일 것이다. 아 당연히 난해한 코드 출품작 급의 괴물 코드 말고, 평범한 코드 말이다. -_-;;
하지만 계속해서 새로운 기능, 기괴한 기능들이 추가되고 있는 modern C++을 보면 이런 자신감이 갈수록 줄어드는 것 같다. 배배 꼬인 템플릿에다 auto에 람다에, ...에 헥헥~ 이 기능은 어떤 문법적 근거를 통해 빌드 되는 건지부터가 파악이 안 되는 것도 있다. =_=;;

요즘 C++은 정말 옛날에 내가 알던 그 C++에서 갈수록 멀어져 간다. 그 경직된 정적 타입 네이티브 코드 컴파일 언어에서 어떻게 동적 타입 언어의 유연함을 집어넣은 걸까? 특히 가변 인자 템플릿 말이다.;; (튜플!!) ㄷㄷㄷ

Posted by 사무엘

2023/11/14 08:35 2023/11/14 08:35
,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/2230

1. 자연어 처리

우리나라 헌법 전문은 무진장 길고 복잡한 만연체인데, 결국 핵심은 "대한국민은 헌법을 개정한다"이다. ㄲㄲㄲㄲㄲㄲ
자, 문장 구조를 파악하기 위해 프로그램 코드처럼 들여쓰기를 적용해 주면 얼추 다음과 같다.

. 유구한 역사와 전통에 빛나는
우리 대한국민은
. . . . . 3·1운동으로 건립된
. . . . 대한민국임시정부의 법통과
. . . . . 불의에 항거한
. . . . 4·19민주이념을 계승하고,
. . . . . 조국의 민주개혁과 평화적 통일의 사명에 입각하여
. . . . . 정의·인도와 동포애로써
. . . . 민족의 단결을 공고히 하고,
. . . . 모든 사회적 폐습과 불의를 타파하며,
. . . . . 자율과 조화를 바탕으로
. . . . 자유민주적 기본질서를 더욱 확고히 하여
. . . . . 정치·경제·사회·문화의 모든 영역에 있어서
. . . . 각인의 기회를 균등히 하고,
. . . . 능력을 최고도로 발휘하게 하며,
. . . . . **자유와 권리에 따르는
. . . . 책임과 의무를 완수하게 하여**,
. . . . **안으로는
. . . 국민생활의 균등한 향상을 기하고
. . . . 밖으로는 항구적인
. . . 세계평화와 인류공영에 이바지함으로써**

. . . 우리들과 우리들의 자손의
. . 안전과 자유와 행복을 영원히 확보할 것을 다짐하면서
. 1948년 7월 12일에 제정되고 8차에 걸쳐 개정된
헌법을
. 이제 국회의 의결을 거쳐
. 국민투표에 의하여
개정한다.

안팎드립을 비롯해 몇몇 좋은 훈계조 문구들은 국민 교육 헌장에서 모티브를 딴 것 같다~!

"... 안으로 자주독립의 자세를 확립하고, 밖으로 인류 공영에 이바지할 때다."
"... 자유와 권리에 따르는 책임과 의무를 다하며 ..."


다만, 이런 요지의 문장 자체는 제헌헌법 시절부터 있었다고 한다. 따지고 보면 국민 교육 헌장이 기존 헌법 전문으로부터 영향을 받은 것이다.

2. AI

AI가 앞으로 의료나 법조계 직업을 많이 빼앗을 거라고 흔히들 예측한다. 특히 사법 불신이 팽배하다 보니 법 쪽은 "대체될 것이다"가 아니라 "당장 대체돼야 된다"라고 강하게 주장하는 사람도 있다.
하지만 세상 돌아가는 양상을 보면 그런 분들의 바람은 가까운 미래에 호락호락 이뤄질 것 같지 않다.

AI 기술의 침투가 제일 소극적이고 더디고 분야, 제일 신중하고 보수적으로 행해지는 분야가 바로.. 사람의 돈이나 인생, 목숨이 왔다갔다 하고 법적 책임이 부과되는 크리티컬한 분야이기 때문이다. 의· 법이 권위 있는 직종인 이유도 바로 이 때문이다.
10살짜리 초딩이 무슨 미적분 할아버지 문제를 풀고 자동차 내부 구조를 달달 외운다 해도, 걔한테 대형 트럭 운전 면허증을 주지는 않는 것과 비슷한 이치이다. 고졸 일자무식이어도 법적 책임 능력이 있는 성인이 그런 차를 몰지.

기계 및 기계 관리자를 전적으로 믿을 수 없어서 자율주행이나 전자투표도 호락호락 정착하지 못하고 있는데, 진료나 판결을 AI가 덜컥 한다..???
솔직히 AI가 수많은 판례들을 학습해서 일관성 있는 합리적인 판결을 내릴 기술적 역량은 이미 갖춰져 있다. 허나 AI라고 해서 흉악범을 몽땅 속 시원하게 사형 때리지도 않을 뿐더러, 그와 별개로 그 분야에서의 AI 대체는 정서상 절대로 금방 되지 않을 것이다. 그런 분야에서 AI의 개입은 기껏해야 "참고만 할 것. 아니면 말고" 수준인 법률 자문, 조언, 보조 수준에서 그칠 것이다.

저런 분야 말고 상상화를 너무 기괴하게 그렸다고, 바둑 게임에서 상대편에게 졌다고, 애한테 조금 허언증스러운 잘못된 정보를 가르쳤다고 해서 당장 사람이 죽고 탈 나지는 않는다. 그런 분야는 AI가 이미 넘치도록 활개를 치고 있다.

3. 정보 보안

입구에 '신천지 OUT 출입금지' 딱지가 붙어 있는 교회를 보면.. 참 웃픈 생각이 든다.
작정하고 기존 교회 신자들을 빼 가려고 침투하는 신천지 공작원(?)이 그걸 보곤 참 잘도 겁 먹고 꽁무니를 빼겠다.

울나라 군사분계선 근처에다가 '북한군 접근 금지'라고 딱지 붙여 보지 그래..?
그런 곳에다가는 보통은 북한군이 아니라 '허가받지 않은 민간인은 접근 금지'라고 써 붙이는 게 자연스럽다. 둘의 차이가 뭔지 모르는 분은 없으리라 믿는다. 암튼..

내가 출처를 직접 확인해 보지는 못했지만.. 마틴 루터가 "이단은 무력이 아니라 설교로 퇴치해야 된다"라는 말을 남겼다고 한다. "펜은 칼보다 강하다"와 같지는 않지만 비슷한 관점에서 참으로 옳은 말이다.

이단들이 물리적으로 꽹과리 치고 흉기 휘두르면서 타 교회 예배를 방해하고 집기를 파손하고 신자들을 해친다면야.. 그럼 당연히 경찰에 신고해야 되고 공권력에 호소하며 도움을 요청할 수 있다. 세상 법정에다 손해배상 소송 걸어서 깽값 받아낼 수도 있다. (받아내야 한다.. 가 아니라 그럴 수도 있다고)

그러나 그러나~~ 계시록 14만 4천 명이 누군지, 동방의 의인이 누군지.. 새 하늘과 새 땅이 뭔지, 열두 지파 정체가 뭔지.. 그딴 거 해석하고 판단하는 일을 세상 경찰이나 법원에다 맡길 참인가? 그런 걸 자기와 다르게 생각하는 이교도 이단들을 과태료 물리거나 민형사 책임을 물어서 처벌이라도 할 생각인가?

교리를 똑바로 가르쳐서 자기 성도들이 신천지 추수꾼들한테 홀딱 속지 않게 해야 하지, 그러지도 않으면서 무책임하게 '신천지 출입금지' 이러는 건 교회가 자기 할 바를 다하지 않고 세상 공권력에다가 이단 퇴치를 다 떠넘기는 것처럼 보인다. 무슨 웹사이트들 하단에 관행적으로 쓰여 있는 "이메일 주소 무단수집 거부"도 아니고 참..;;

신천지라든가 공산주의나 동성애 따위가 아무리 나쁘다고 해서 그걸 무슨 세상 공권력이나 타 종교하고까지 손잡아서 물리적으로 박멸하는 것은 기독교회가 해야 할 '하나님의 일'이 아니다. 당장은 그게 편해 보여도 세상 공권력은 언제든지 기독교를 박해하는 역공 비수가 되어 교회로 되돌아올 수 있다~!

그건 그렇고.. 컴퓨터 정보 보호 보안의 관점에서도 이거랑 아주 비슷하게 느껴지는 원칙이 있다.
암호 보안은 '암호화 알고리즘'이라는 코드가 아니라, '거기에다 준 암호화 키'만으로 완벽하게 보장돼야 한다는 거.

"이 데이터는 우리 회사만의 기가 막힌 블랙박스 알고리즘으로 암호화돼 있습니다. 보안을 위해 구체적인 알고리즘은 공개할 수 없습니다" 이건 저런 "신천지 출입금지" 딱지를 거는 것과 비슷한 수준 낮은 보안이다. -_-;;; 지난 2009년, 코드소프트 암호 크랙 공모전 흑역사처럼 말이다.

다시 말하지만 암호화 알고리즘은 무슨 요리 비법마냥 장인의 손맛이 담긴 비기 같은 존재가 절대 아니다~!! ㄲㄲㄲㄲㄲ
실제 정보 보호 업계가 돌아가는 방식은 이와 전혀 다르다. 모든 암호화 알고리즘들이 소스째로 다 투명하게 버젓이 풀려 있다. 아무라도 그 코드를 돌려서 임의의 데이터를 임의의 키로 암호화 복호화한 결과를 볼 수 있다.

이 데이터는 무슨 알고리즘으로 암호화됐는지 알려져 있고, 그 알고리즘 코드도 이미 다 있다.
그럼에도 불구하고 무슨 키로 암호화됐는지를 모르면.. 일일이 모든 키로 다 해독해 보는 brute force 외의 방법으로는 원래 데이터를 절대로 복원할 수 없다.

이게 "이단은 설교만으로 퇴치해야 된다"와 같은 급의 보안인 것이다. 암호화 키가 무슨 교리 같은 존재인 듯.. ^^
히틀러인지 누군지 아무튼 어느 나치 독일 간부가 남긴 명언(?) 중에 "힘은 방어가 아니라 공격에 있다"가 있었던 것 같은데.. 그 말을 응용해 보면 "보안은 알고리즘이 아니라 키에 있다" 정도가 될 것이다.

여담:
여기까지 글을 썼더니 현업에서 목회를 하시는 분들께서 글 주제와 관련하여 여러 조언 첨언을 본인에게 해 주셨다.

  • 그냥 자기가 소속된 교단에서 딱지 붙이라고 반강제 강권해서 붙이는 편이라고 한다. 오키. 거기까지는 이해가 되는데..
  • 심지어 신천지 교회들이 '위장'을 위해서 자기 예배당 입구에다가 "신천지 OUT" 딱지를 붙이는 경우도 있다고 한다. 미친~~ ㄷㄷㄷㄷㄷㄷ
  • 신천지에서는 기존 교회들에다가 스팸성 우편물도 끈질기게 왕창 보낸다고 한다. 흐미~ 도대체 뭔 재정으로 저럴 여력이 있는지 모르겠다.

Posted by 사무엘

2023/10/30 19:35 2023/10/30 19:35
,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/2225

컴퓨터의 정보 기억장치는 다음과 같은 속성에 따라 분류 가능하다.

(1) 배열처럼 아무 지점이나 O(1) 시간 복잡도로 즉시 접근 가능한가? 아니면 링크드 리스트처럼 순차적으로만 접근 가능한가?
Random Access memory라는 건.. 아무렇게나 읽고 쓰기가 가능하다는 뜻이 아니라, 임의 지점 접근이 가능하다는 뜻이다.
물론 오늘날은 테이프 같은 극단적인 물건 말고는 RAM이 당연시되고 있다. 테이프는 임의 접근이 안 되니 번거로운 감기 기능이 필요했지만.. CD는 아무 트랙이나 바로 갈 수 있다.

(2) 읽고 쓰기 가능한가, 아니면 읽기 전용인가?
ROM이라고 불리는 물건들은 RAM의 속성을 지니면서 동시에 ROM인 셈이다. ROM은 RAM의 엄밀한 반의어가 아니니 유의할 것.
CD 같은 광학 디스크는 요즘 기술로 '쓰기' 자체는 가능하다. 하지만 아무 부작용이나 부담 없이 자유자재로 아무렇게나 쓸 수 있는 건 아니다.

(3) 휘발성인가, 비휘발성인가
아주 중요한 속성이다. 전원이 끊어지면 내용이 싹 다 날아가느냐, 아니면 그 뒤에도 내용이 남아 있느냐? 읽기 전용 메모리는 당연히 비휘발성이어야 할 테니 이건 읽쓰 겸용 메모리가 대상이다.
반도체 기반의 주메모리는 속도가 빠른 대신 전자이고, 나머지 보조 기억장치들은 느린 대신 용량 많고 후자의 속성을 지닌다.

(4) 매체와 reader/writer가 쉽게 분리 가능한가? 아니면 붙박이인가?
이거 무슨 철도 차량으로 치면 기관차-객차 vs 동차 같은 차이점 같다.

(5) 그리고.. 어떤 기술 배경에 따라 만들어졌는가?
다음과 같이 세 계열로 나뉜다.

1. 자기장: 테이프, 디스켓 // 디스크, 드럼.

매체 분리형에서는 테이프만이 무슨 방송국이나 데이터센터 급의 백업/아카이빙 용도로 쓰이고 있고 나머지는 도태했다. 디스켓이고 zip드라이브고 뭐고 다 망했으니까. 붙박이형은 디스크만이 '하드'의 형태로 남고 다 도태했다.
1950년대에 슈퍼컴퓨터 용으로 무려 5MB짜리 하드디스크를 지게차에다 조심스럽게 실어 나르던 시절을 보면 참 격세지감의 극치가 따로 없다.

사용자 삽입 이미지

여기는 전통적인 트랙이니 섹터니 하는 구조 구분과 '포맷'이라는 게 통용되는 기억장치이다. 하드디스크는 실린더라는 것도 있었고 말이다.
똑같은 디스켓이라도 운영체제에서 소프트웨어적으로 공간 구획을 구분하고 인식하는 방법이 다르다. 과거에는 플랫폼과 운영체제에 따라 이런 파편화가 더 심했기 때문에 디스켓들이 포맷되지 않은 채로 판매되곤 했다. 물론 IBM PC와 MS-DOS가 천하를 평정한 뒤부터는 디스켓들이 다 미리 포맷되어서 나오기 시작했다.

하드디스크는 전원이 끊어질 때 안전을 위해 파킹이라는-_- 마무리 동작도 권장되곤 했다. 물론 훗날 자동 파킹이 지원되면서 별도의 파킹 유틸리티는 화면 보호기만큼이나 별 필요 없는 눈요기 잉여로 전락했다.

2. 반도체: USB 스틱, SD카드 // SSD

메모리 반도체는 100% 전자식으로만 동작하는 물건이다. 빠른 대신에 비싸고, 무엇보다도 그 특성상 전기가 끊어지면 내용도 다 날아가는 '휘발성 메모리' 전용이었는데.. 기술의 발달로 보조 기억장치 역할도 가능한 메모리 반도체가 등장했다.
얘 덕분에 기존의 테이프나 디스켓이 완전히 전멸해 버렸다. 그리고 SSD도 가격 내려가고 용량 올라가면서 기존 기계식 하드디스크의 입지를 상당수 위협하고 있다.

SSD는 조각 모음이 필요하지 않으며, 동작하는 특성이 기존 디스크와는 많이 다르다.
USB 스틱은 매체와 구동부가 일체형인 반면, SD카드는 매체와 구동부가 분리돼 있다. (별도의 reader가 필요)
옛날 8비트 시절에 게임용으로 쓰였던 롬팩 카트리지도 1번이 아니라 2번 반도체 기반이었던 거지..??

전자기기에서 캐퍼시터(축전기)와 본격적인 화학 전지의 관계가 반도체 메모리와 타 보조 기억장치의 관계하고 비슷해 보인다~!!
전자는 충전· 방전이 아주 빠르고 용량이 아주 작으니까. 그리고 캐퍼시터를 용량을 왕창 키워서 배터리처럼 사용하려는 연구가 진행 중이기도 하다.

3. 광학(레이저)

얄팍하고 비까번쩍 빛나고 뭔가 하이테크스럽게 생긴 원반이다. 1990년대에 첫 등장했을 때는 얼마나 간지 뽀대 났겠는가.

사용자 삽입 이미지

얘는 그 특성상 붙박이라는 게 존재하지 않고 드라이버와 매체가 분리돼 있다. 기억장치들 중 디지털 방식의 음반· 영상매체와 가장 친화적이라는 특징도 있다. (그 반면, 테이프는 '아날로그' 방식의 매체..)
얘는 '쓰기'와는 그렇게 친화적이지 않다. 디스크에다가 작정하고 새로 기록 추가만 가능하며, 한번 새겨 버린 내용을 자유자재로 덮어쓸 수 없다. 평범한 디스크 저장이 아니라 종이에다 인쇄하는 것과 얼추 비슷하다고 생각해야 한다.

디스크의 영어 스펠링은 disk와 disc가 혼용되는 듯한데.. disc는 특별히 대놓고 원반 모양인 광학 매체에 한정되어 쓰이는 것 같다. 가령, 하드는 hard disk이지만, CD는 compact disc이다.
그리고 CD(+ DVD, 블루레이)는 지름이 12cm인 반면, 과거에 있었던 레이저 디스크는 지름이 12인치였다는 아주 흥미로운 차이점이 있다. 뭐, 거기에다 미니CD라고 지름 8cm짜리 규격도 있긴 하고 말이다.

한때 광학 기억장치는 용량이 방대하다는 장점이 있었지만 오늘날은 빛 바랜 장점이다. 이제는 컴퓨터에서 광학 드라이브 자체가 거의 퇴출되었고, 운영체제 설치도 그냥 네트워크나 USB로 다 되는 세상이 됐다.
그래서 DVD의 다음 규격인 블루레이는 용량이 더 방대함에도 불구하고 오늘날 존재감이 매우 미미하다. 블루레이에다가 수십 GB짜리 패키지 게임을 담아서 판매하는 시대도 아니니 말이다.

옛날에는 뭔가 레이저를 사용하는 컴퓨터 주변기기는 가격이 억소리 나게 비쌌던 걸로 악명 높았다. 레이저 프린터, 그리고 씨디 라이터.. 그게 어쩌다가 가격이 확 떨어지고 개인용 컴퓨터에 씨디를 굽는 기능까지 내장되어 들어갔는지.. 경이롭기 그지없다.
기껏 들어갔던 기능이 이제는 필요 없어져서 퇴출되는 지경이고..

※ 여담 1: 옛날 추억 더

  • 1990년대에 VGA 이후로 SVGA 그래픽 카드들이 표준 규격 없이 난립했던 것처럼.. 확장 디스켓도 표준 규격 없이 너무 난립했던 것 같다. 더구나 USB니 plug & play니 없던 시절에는 하드나 디스크 드라이브를 하나 더 장착하는 것도 엄청나게 어렵고 컴퓨터 하드웨어 지식이 많이 필요하던 과업이었다. 그러니 그런 싸제 물건들이 성공적으로 보급되기가 어려웠다.

  • 그나저나 이 바닥은 자동차 브레이크 말고도 '디스크와 드럼'을 쌍으로 구경할 수 있는 또 다른 분야인 게 신기하다. '자기 드럼'은 어떤 형태로 동작하는 물건이었을까..?? 개인적으로 무척 궁금하다.

  • 옛날에는 테이프나 디스크에 물리적인 쓰기 방지 탭이나 딱지 같은 게 붙어 있기도 했지만.. 요즘은 그런 관행을 찾을 수 없다. (운영체제 셸이 그런 데다가도 메타데이터나 썸네일 캐시 같은 걸 임의로 써 넣곤 함)

  • 옛날에는 광자기 디스크라는 것도 있었던 것 같은데.. 뭐지? 1번과 3번의 하이브리드인가 싶다.

카세트 테이프(자기장)나 롬 카트리지(반도체)는 8비트의 산물이다. 16비트 IBM 호환 PC급에서 저런 것들을 취급하는 사례는 내가 아는 한 없다.
그 대신 디스켓 FDD는 컴퓨터 붙박이 형태로 16비트 이후 시대를 풍미했다가 64비트 시대에는 사실상 전멸했다.
CD-ROM은 16비트 도스 시절에도 존재하긴 했지만 mscdex니 뭐니 하는 굉장히 무거운 드라이브를 실행해야 사용 가능했다. 그리고 386, 486급 이상 PC의 전유물이었다.
그 뒤로 USB 메모리는 도스와의 유의미한 접점이 없다. ^^

아무리 생각해도 1990년대 중후반에 plug & play와 USB는.. 2000년대 중후반의 64비트와 멀티코어하고 굉장히 비슷한 관계인 것 같다.
서로 담당하는 분야는 다르지만 결국 비슷하고 관련 있는 성격의 기술이었다는 점에서 말이다.

※ 여담 2: 정보 저장이라는 관점에서 성경 본문 고찰

성경에는 뭔가 정보 기록 매체를 암시하는 얘기가 있다. 가령, 요한복음의 21장 25절 제일 마지막 구절은 이렇다.
"예수님께서 행하신 다른 일들도 많으므로 만일 그것들을 낱낱이 기록한다면 심지어 이 세상이라도 기록된 책들을 담지 못할 줄로 나는 생각하노라."

"그 크신 하나님의 사랑"이라는 찬송가의 3절 가사는 이렇다.
"하늘을 두루마리 삼고 바다를 먹물 삼아도 한없는 하나님의 사랑 다 기록할 수 없겠네"

흠, 책이 아니라 SD카드라면 어떨까? 자기 테이프라면 어떨까..??
혹시 생명책이 실제로는 책이 아니라 무슨 SQL 서버가 돌아가는 IBM 메인프레임인 건 아닐까?

Posted by 사무엘

2023/10/05 08:35 2023/10/05 08:35
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/2215

컴퓨터 알고리즘 문제 중에는.. "N개의 원소로 구성된 목록에서 majority.. 과반/다수파라는 게 존재하는가? 존재한다면 무엇인가?"를 구하는 문제가 있다.
목록에서 과반의 원소가 다 같은 값이면 그게 바로 majority라고 정의된다. 원소들은 꼭 대소 관계가 성립할 필요가 없고 그냥 동등성 판단만 가능하면 된다. 그러니 꼭 정수가 아니어도 된다.

또한, 반반이 아니라 과반이라는 특성상, majority는 존재하지 않거나, 유일하게 존재.. 언제나 둘 중 하나이다. 공동 1위 같은 것도 고려할 필요가 없다.

이 문제는 뭐랄까.. 단순하면서도 참 므흣하다.
일단, "목록에서 가장 자주 등장하는 원소--등장 빈도수가 가장 큰 원소를 구하시오"라고 접근하지 않는 게 핵심이다.
각 원소들의 빈도수를 일일이 관리하자면 알고리즘의 시간 복잡도가 기본이 O(n^2)에서 시작할 것이고, 균형 잡는 tree 기반의 컨테이너를 사용한다 하더라도 O(n log n)이 한계이다.

그러나 이 문제를 풀기 위해서는 일을 그렇게 크게 벌일 필요가 없다.
각 원소의 빈도수가 아니라.. "이 목록은 과반의 원소가 그냥 동일한 값인가?"라고 접근하는 게 좋다.

수학인지 논리학인지 거기에는 "비둘기집의 원리"라는 게 있다. N+1개의 물건을 N개의 상자에다 다 집어넣는다면, 적어도 한 상자에는 그 물건이 2개 이상 들어가 있다. 뭔가 미적분에서 말하는 중간값 정리처럼.. 너무 당연한 말 같은데 말이다.
그것처럼 어떤 목록에 같은 원소가 "과반"이라면 그 목록은 다음 둘 중 한 특성을 반드시 갖게 된다.

  • 과반의 원소가 아무리 고르게 분산되어 분포한다 하더라도, 그 원소가 연달아 두 번 이상 등장하는 구간이 반드시 하나 이상 존재한다~! 1 1 2 1 특히 원소의 전체 개수가 짝수라면 이건 뭐.. 무조건 빼박이다.
  • 만약 그게 아니라면.. 그냥 맨 마지막 원소가 다수파이다.

어, 정말 저렇게 단정할 수 있나 의아할 텐데.. 이런 과감한 주장은 다수파의 정의가 절반의 '초과', '과반'이기 때문에 성립 가능하다.
절반을 포함하는 '이상'이기만 해도 위의 조건들은 당연히 성립하지 못하게 된다. "1 2 1 2" 같은 것만 생각해 봐도 알 수 있다.

이렇듯, 다수파가 존재할 때 가질 수밖에 없는 목록 전체의 특성을 생각하면.. 다수파를 굉장히 단순한 절차만으로도 정확하게 구할 수 있다.

  • 최초엔 맨 첫째 원소가 다수파라고 가정하고 후보로 지정한다. 연속 등장 횟수(이하 점수)도 1을 부여한다.
  • 그 다음 원소가 후보 원소와 동일하면 점수를 1 증가시킨다. 그렇지 않으면 점수를 1 감소시킨다.
  • 단, 이미 현재 점수가 0이 된 상태여서 더 감소시킬 것이 없으면 후보 자체를 지금의 새 원소로 교체한다. 그리고 점수를 1로 다시 부여한다.
  • 이 과정을 모든 원소들에 대해 수행한 뒤, 현재 지정되어 있는 후보를 결과값으로 되돌린다.

사용자 삽입 이미지

위키백과에는 이 과정을 다음과 같이 시각적으로 잘 묘사한 그림이 있더라.
정말 허무할 정도로 단순하다. 이 알고리즘은 고안자의 이름을 따서 Boyer-Moore majority vote algorithm이라고 명명되어 있다. 1981년에 학계에 처음으로 발표됐다고 하는데.. 동작하는 방식을 보니 후보, vote 이란 워딩이 적절해 보인다.

Boyer-Moore 이거 혹시 "문자열 검색 알고리즘에도 나오는 이름이 아닌가?"라는 생각이 들 텐데.. 정확하다. 동일한 명칭이다. ㄲㄲㄲㄲ

단, 위의 알고리즘은 목록에 다수파라는 게 실제로 존재하지 않더라도 언제나 후보를 갖고 있다가 되돌린다. 그러니 목록에 다수파가 존재한다면 정확한 답을 되돌리지만, 애초에 다수파가 존재하지 않는다면 뭔가 임의의 엉뚱한 후보를 되돌린다.

그렇기 때문에 이 알고리즘에는 2nd-pass, 즉 후처리라는 게 필요하다. 앞의 1st-pass를 통해 구해진 후보가 진짜로 다수파가 맞는지, 얘만 개수를 처음부터 다시 세어 보는 것이다.
1st-pass 때 쓰였던 점수 변수는 증가했다가 감소하기를 반복했기 때문에 정확한 개수가 담겨 있지 않다.
이거 무슨 분수· 무리방정식을 풀고 나서 검산을 해서 무연근을 제거하는 것과 비슷한 느낌이다.

자, 두 pass 모두 시간 복잡도는 O(n)이고, 공간 복잡도는 지역변수 꼴랑 한두 개.. O(1)에 지나지 않는다. 정말 놀랍지 않은가?
얘는 알고리즘 자체는 쉽게 이해할 수 있지만, 정말 이렇게만 해도 언제나 문제에 대한 정확한 답이 구해지는지 correctness를 이해하는 게 좀 빡셀 수 있는 문제이다.

알고리즘이라 하면 복잡한 기법을 동원해서 무식하게 풀었을 때 O(n^2)짜리인 것을 O(n log n)으로 낮춘다거나, 팩토리얼/지수함수 급인 것을 O(n^2)나 O(n^3)으로 낮추는 형태인 게 많다.
그러나 최적화를 통해서 이렇게 O(n)을 만들 수 있는 문제는 흔치 않아 보인다.

이 다수파 구하기와 성격이 아주 비슷한 문제는 N개의 숫자 목록 내부에서 "합이 가장 큰 연속된 구간을 찾기"인 것 같다. 당연히 양수와 음수가 뒤죽박죽 섞여 있는 목록에서 말이다.

정답이 (x~y) 구간은 그야말로 1<=x<=y<=N 아무렇게나 가능하기 때문에 이것도 언뜻 보기에는 시간 복잡도가 O(n^2)이나 최하 O(n log n)이 될 것 같다. 그러나 얘도 아주 간단한 검사만 하면서 시간 복잡도 O(n)과 공간 복잡도 O(1) 만으로 아주 빠르게 풀 수 있다.

  • 정답 구간이 맨 첫 원소에서 시작된다고 가정하고 각 원소들을 쭉쭉 더해 본다. 그 합이 지금까지 구한 max보다 더 크면 최대값을 갱신하고 정답 구간도 업데이트 한다.
  • 그런데 그러다가 합이 음수가 돼 버리면... 그러면 지금까지 살펴봤던 구간은 "더 살펴볼 필요가 없고" 그냥 통째로, 아무 미련 없이 버리면 된다. 그 다음에 양수가 나오는 구간부터 시작점을 새로 설정하고 동일한 과정을 반복한다.

더 살펴볼 필요가 없기 때문에 시간 복잡도 O(n)이 가능한 것이다. 이게 아까 다수파 문제에서 점수가 음수가 돼 버린 시점에서 후보를 깔끔하게 바꿔 버리는 것과 비슷하다. 왜 이렇게 해도 되는지를 생각하는 게 이 문제의 관건이다.

Posted by 사무엘

2023/07/29 08:35 2023/07/29 08:35
, , , ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/2188

4. 숫자 자리수 잉여 구분자

21세기 들어서 프로그래밍 언어들에 알음알음 몰래 도입돼 들어간 요소 중 하나로는.. 숫자 자리수를 구분하는 잉여 구분자가 있다.

가령, Java는 _ 밑줄을 이 용도로 지원한다.
그래서 a = 1234567890이라고 쓸 것을 a = 12_3456_7890이라고 써도 되고, a = 1_234_567_890이라고 써도 된다. 소수점도 3.141_592_653 이렇게 쓸 수 있고, 0xFFFF_0000처럼 타 진법도 마찬가지이다.

C++에서는 참 흥미롭게도 '(어퍼스트로피)를 동일 용도로 지원한다. C++11에서인가 추가됐다고 한다. a=1'234'567;
_은 공백을 염두에 둔 기호인 반면, '는 콤마를 염두에 둔 기호라는 차이가 있다.

이런 구분자는 컴파일러의 입장에서는 있건 없건 토큰을 인식하는 데 아무 차이가 없다. 주석과 동급으로 전혀 필요하지 않은 잉여일 뿐이다.
단지, 사람 입장에서의 가독성을 위해서 이 구분자만은 예외로 컴파일러가 이물질이라고 토해내지 않고 무시 처리해 주는 것이다.

64비트 double 부동소수점이야 16비트 시절부터 존재했겠지만, 64비트 정수 리터럴은 쌍팔년도에는 거의 볼 수 없는 물건이었지 싶다. 이 정도로 큰 수는 10진법으로 나타내도 글자 수가 거의 20자에 달하게 된다. 그러니 필요할 때 인위로 자리수를 끊어서 표기할 수 있다면 코드의 가독성 차원에서 깨알같은 도움이 될 것이다.
다만, 학술적인 의미가 있지 않고 사람이 값을 참조할 일이 없는 단순 난수표 같은 숫자 테이블이라면 굳이 자리수 구분해서 적을 필요가 없을 듯하다.

그러고 보니 C++에는 0b로 시작하는 2진법 리터럴 표기도 추가됐다. 얘는 아무래도 길이가 굉장히 길기 때문에 8자 단위로 '로 끊어서 표기하는 게 확실히 유용하겠다.
그에 비해 C 시절부터 존재했던 8진법 표기는 진짜 아무데서도 안 쓰이는 잉여가 된 것 같다. FTP 파일 권한 777 이런 것 말고 딴 데서는 도통 본 적이 없다.

참고로 C/C++에는 줄 바꿈 문자를 없애고 토큰을 한데 이어 주는 \ 역슬래시라는 강력한 기호가 있다.
C/C++은 태생적으로 줄 바꿈에 연연하지 않고 중괄호와 세미콜론으로 문장을 구분하기 때문에 \ 가 필요할 일이 그리 많지는 않다. 사실상 #define 매크로 함수를 여러 줄에 걸쳐 길게 선언하는 용도로만 쓰인다.
하지만 그 특성상,

int a=123\
456;
const char b[]="abc\
def";

이렇게 써 줘도 얘는 a=123456이라고 인식되며, b에는 "abcdef"가 들어간다. \는 컴파일러라기보다는 거의 전처리기 수준으로 소스 코드의 두 줄을 기계적으로 연결해 준다고 생각하면 된다.

이걸로 심지어 // 주석조차 다음 줄까지 계속되게 만들 수 있으니 말 다 했다.;;
참고로, 주석은 컴파일러의 입장에서 whitespace 하나로 간주된다. 그렇기 때문에 100/*ㅋㅋㅋㅋ*/00은 100과 00을 분리시키며, 100'00과 같은 역할을 할 수 없다.

객체 지향, 제네릭/메타프로그래밍, 함수형 등 갖가지 패러다임들이 C++, C#, Java 등 메이저 언어들에 다 도입되면서 프로그래밍 언어들은 서로 비슷해지는 '수렴 진화' 중인 것 같다. 물론 자기 고유한 정체성을 상실할 정도로 완전히 똑같아지지는 않겠지만 말이다.

5. 오타

현직 프로그래머 내지 소프트웨어 엔지니어는 코딩을 한다고 해서 맨날천날 시간 복잡도, 공간 복잡도 따지고 다이나믹이니 그리디니 하는 신선놀음 같은 알고리즘 고민을 하는 게 아니다.

현실에서는 알고리즘이야 이미 만들어져 있고 잘 돌아가는 검증된 라이브러리나 오픈소스를 가져와서 쓰는 게 훨씬 더 많다.
자기가 새로운 코드를 만들어 내는 것보다 남이 만든 기존 코드를 읽고 유지보수 하고 버그를 잡는 비중이 훨씬 더 크다.
그리고 그 와중에 그나마 새로운 코드를 작성하는 게 있다면.. "뭔가 이름을 붙이는 것"의 비중이 매우 크다. 동사구이든 명사구이든..

그러니 프로그래머가 자기 조직이 마음에 안 들 때 아주 교묘하게 사보타주를 하고, 자기 후임을 엿먹이고 생산성을 저해하는 효과적인 방법이 있다.
자기가 작성하는 각종 클래스, 함수 등의 이름에다가 고의로 오타를 교묘하게 집어넣는 것이다.
아주 간단하게 getUserAdress 라든가.. receiveIncommingMessage 따위.

...;; 프로그램이야 멀쩡하게 돌아가니까 그 당시에는 아무 문제가 없는데..
문제는 나중에 그 프로그램의 버그를 잡고 기능을 추가하는 등 유지보수를 할 때다.
대놓고 약어를 쓴 것도 아니고 원래 그대로 풀어 쓴 듯한 영단어가 미묘하게 스펠링이 여기저기 틀려 있으면..
나중에 "검색"이 안 되어서 미치고 펄쩍 뛰는 일이 야기된다.

이런 코드는 여러 사람을 거쳐 가며 작업을 하기 어려우며, 처음 짰던 사람이 아니면 구조를 쉽게 파악할 수 없게 된다.
도서관에서 책을 꺼냈다가 일련번호 순서가 아닌 아무데나 꽂아 넣는 것과 같은 일이 벌어진다. (잘못 꽂힌 책은 없는 책과 같습니다)

진짜.. 개발 환경에서는 프로그래밍 언어 차원에서 코드의 문법 오류만 빨간줄을 치는 게 아니라, 명칭의 영어 스펠링 오류를 체크하는 것도 꽤 도움이 되지 싶다.

먼 옛날에 컴퓨터가 너무 비싼 물건이고 텍스트 에디터의 인터페이스가 불친절· 불편하고 디스크 공간이 부족하던 시절에는
뭐든지 getpid() 이런 식으로 짧게 줄여 쓰는 게 관행이었다. PC통신 채팅이나 전보에서 '안냐쎄여' 등으로 필사적으로 줄이는 것의 코딩 버전이나 마찬가지이다.

그러나 디스크 용량 걱정이 없어지고, 한번만 명칭을 정한 뒤부터는 에디터에서 긴 명칭을 자동 완성해 주는 기능이 매우 편리하게 발달하고(거의 90년대 말.. =_=), 또 소프트웨어의 규모가 왕창 방대해지고 공동 작업의 중요성이 커진 뒤부터는 GetProcessID() 이렇게 길게 풀어 쓰는 게 더 바람직한 관행으로 정착했다.
소스 코드가 자연어와 더 비슷해지고 길어지고 나니 스펠링 오류에 대한 취약성도 더 커진 셈이다.

6. 함수 안에 함수, 클래스 안에 클래스

파스칼 내지 Ada 같은 옛날 구시대 언어 중에서는 함수 안에 함수를 만드는 걸 지원하는 경우가 있었다. 그에 비해 C는 함수 호출 구조를 단순화시키느라 그런 걸 제공하지 않았다. 한 함수 안에서만 잠깐 쓰이는 코드 반복 패턴을 표현하려면 그냥 매크로 함수를 쓰라는 취지였던 듯하지만.. 이건 막 깔끔한 해결책은 못 됐다.

오늘날은 함수형 프로그래밍이 도입되면서 람다 덕분에 함수 안에 함수를 넣는 게 '사실상' 가능해졌다. 다만, 예전에 생각했던 그런 문법이 아니라, 함수 몸체를 지역변수에다 대입하는 굉장히 이색적인 형태로 가능해졌다는 게 신기한 점이다.

함수와 달리, 클래스는 원래부터 자기 내부에 클래스를 또 가질 수 있다. 그래서 C++은 C와 달리 계층적인 다단계 scope을 구현할 수 있으며, 필요에 따라 이거 표현을 간소화하기 위해 using이라는 키워드도 도입됐다.

함수건 클래스건.. (1) 내부에 안겨 있는 녀석의 명칭은.. 걔를 품고 있는 outer의 문맥에서만 유효하고 거기서만 접근 가능하다. 이건 너무나 당연한 이치이다.
그런데 안겨 있는 녀석은.. (2) 반대로 자기를 안고 있는 outer의 멤버(클래스의 경우)나 변수(함수의 경우)에 접근 가능해야 한다.

C/C++은 (2)를 지원하는 것이 미흡하고 인색했다. 그래서 C 시절부터 함수 안에 함수 같은 건 골치 아프니 지원하지 않았으며, 클래스 안의 클래스도 바깥 클래스의 인스턴스 멤버로 접근을 지원하지 않았다. Java로 치면 static class밖에 지원하지 않은 것과 같다.
C/C++은 포인터를 그렇게도 좋아하는 언어인데, 저것들은 정수 하나짜리 날포인터만으로 구현할 수 없는 개념이어서 지원을 안 한 것이지 싶다.

static 함수야 클래스에만 소속됐지, 클래스의 각 인스턴스에 매여 있지는 않아서 this 포인터가 존재하지 않는 함수이다(0). 사실상 전역 함수이나 마찬가지이다.
그리고 일반적으로는 자기 자신을 가리키는 this는 메모리 주소 딱 1개만 가리키는 포인터이지만.. 하지만 객체지향을 제대로 구현하려면 this의 크기가 한 칸만으로 충분하다는 고정관념을 깨야 할 것 같다. inner class라든가 다중 상속은 이런 문제를 훨씬 더 복잡하게 만들기 때문이다.

하긴, 그래서 Java에서는 C++에 없는 Outer o = (new Inner()).new Outer(); 이런 코드가 가능하다. C++에서는 new 연산자를 오버로딩 하더라도 무조건 static 형태만 되는데, Outer는 this가 자기 자신뿐만 아니라 Inner까지 사실상 두 파트로 구성되는 셈이다.
이게 가능하니 C++ 같았으면 다중 상속을 해야 했을 것도 저렇게 퉁치고, 프로그래밍을 더 작은 객체 단위로 깔끔하게 할 수 있을 것 같다.

클래스는 그렇다 치고.. 함수가 outer의 변수에 접근하는 건 요즘 C++도 '캡처'라는 기능으로 제공하기 시작했다. 원래는 '클로저'라고 부르는 개념이었지 싶은데 말이다. 이건 프로그래머/사용자의 관점에서는 아주 편리한 기능이지만, 내부적으로는 역시 함수 실행 문맥을 가리키는 포인터를 집어넣고 어쩌구 하면서 꽤 힘든 과정을 거쳐서 구현된다.

Posted by 사무엘

2023/06/30 08:35 2023/06/30 08:35
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/2177

1. C++이 C보다 편리한 결정적인 요인

C++은 템플릿, 람다 등 온갖 다양한 프로그래밍 패러다임이 추가되어 C보다 훨씬 더 방대하고 복잡한 언어가 되어 있다.
그러나 C++은 맨 처음에 객체지향 언어로 시작했기 때문에 C와의 근본적인 차이는 아무래도 이와 관련된 것들이다.
본인은 C++이 C에 비해서 더 편리하고 간결하게 코딩할 수 있고 사람의 실수를 줄여 주는 제일 강력하고 중요한 요소는 다음 세 가지라고 개인적으로 생각한다.

(1) 클래스 멤버 함수 안에서 this 포인터를 생략하고 바로 자기 멤버를 참조해도 된다.
즉, C의 함수와 비교했을 때, 복잡한 구조체의 포인터인 첫째 인자는 생략 가능하다는 뜻이다. 매번 obj->member 할 필요 없이 바로 member를 쓰면 된다.

(2) 어떤 객체 변수를 선언해 주면(지역/전역) 생성자와 소멸자를 호출하는 코드가 앞뒤에 자동으로 삽입된다.
함수나 블록의 실행이 중간에 끝나더라도(return, break) 메모리를 해제하거나 파일을 닫는 코드를 거치게 하려고 지저분한 goto문을 쓰지 않아도 된다. 예외를 던질 때에도 소멸자 처리가 자동으로 된다는 건 longjmp 따위로 결코 흉내 낼 수 없는 엄청난 축복이다.

(3) 상속이라는 걸 자동으로 제공하고, 포인터 형변환 때의 상위· 하위 상속 관계를 자동으로 맞게 판단해 준다.
파생에서 기반으로 가는 건 괜찮지만, 기반에서 파생으로 가는 건 바로 안 되고 최소한 static_cast라도 해 줘야 된다.
그에 비해 C언어는 void*냐 그렇지 않느냐 하나만 판단하고, void*가 아닌 다른 모든 타입의 포인터들은 서로 남남인 타입일 뿐이다.

2. C++과 Java의 enum class

컴퓨터 프로그램에서는 숫자가 산술 연산의 대상인 수가 아니라 그냥 이산적인 식별 번호로 취급되고, 각각의 값이 서로 완전히 다른 의미를 갖는 경우가 많다. 그래서 프로그래밍 언어에서는 범용적인 정수형뿐만 아니라 sub-range 내지 열거형이란 걸 제공하곤 한다.

sub-range는 파스칼이나 Ada 같은 옛날 언어 유행으로 끝나는 분위기이고, 요즘 대세는 열거형이다.
C언어는 열거형이란 게 있긴 했지만 모종의 이유로 인해 매크로 상수가 훨씬 더 많이 쓰였다. 하긴, 그쪽은 참/거짓 bool 형조차 없었고 그냥 다 int로 퉁쳐서 썼을 정도로 int 만능 덕후 성향이 좀 있었다. =_=

C++에서는 C++11 버전부터 enum class라는 것이 도입됐다. (1) scope을 반드시 지정해 줘야 하고, (2) 정수형으로 암시적으로 형변환이 되지 않아서 type-safety가 강화되니 굉장히 적절한 변화인 것 같다.
즉, 평범한 enum이라면 int를 받는 아무 곳에서나 ENUM_VALUE라고만 써도 됐을 텐데, enum class라면 반드시 static_cast<int>( EnumClass::ENUM_VALUE ) 라고 길게 지정해 줘야 하게 된 것이다. type safety가 강화되었다.

Java에도 enum이 있긴 하지만, 후대인 Java 5에서 추가로 도입된 물건이다. 그렇기 때문에 거기도 상수 명칭을 선언하는 용도로는 재래식 static final int 뭉치가 더 많이 통용돼 왔다.
같이 도입된 건지 또 나중에 추가된 건지는 모르겠지만, Java에도 enum class라는 게 존재한다. 그런데 이건 C++과는 관점이 전혀 다른 재미있는 물건이다.

public enum Planet {
    MERCURY (3.303e+23, 2.4397e6),
    VENUS   (4.869e+24, 6.0518e6),
    EARTH   (5.976e+24, 6.37814e6),
    MARS    (6.421e+23, 3.3972e6),
    JUPITER (1.9e+27,   7.1492e7),
    SATURN  (5.688e+26, 6.0268e7),
    URANUS  (8.686e+25, 2.5559e7),
    NEPTUNE (1.024e+26, 2.4746e7);

    private final double mass;   // in kilograms
    private final double radius; // in meters
    Planet(double mass, double radius) {
        this.mass = mass;
        this.radius = radius;
    }
   (... 이후 생략)
}

이렇게, enum {} 내부에는 명칭들을 쭈욱 쓴 뒤, 세미콜론을 찍으면 명칭 나열을 종결할 수 있다.
그 뒤, 다음부터는 클래스를 선언하는 것처럼 public이니 private니 어쩌구 하면서 멤버 함수와 멤버 변수를 쓰면 얘는 enum의 탈을 쓴 평범한 클래스가 된다.

허나, 이 enum 클래스는 new 연산자를 사용해서 임의의 인스턴스를 만들 수 없다. 이 enum의 인스턴스는 저 명칭으로 선언된 녀석들만이 허용된다. 그래서 enum 명칭을 선언과 동시에 저렇게 생성자 함수에다 전할 인자를 ()로 지정할 수 있다. 우와~~~

즉, 일반적으로 enum 명칭들은 0 1 2 3  같은 숫자의 alias에 불과한 반면, enum class는 각각의 명칭들이 이 클래스의 붙박이 인스턴스가 된다는 것이다.
enum은 상수를 나타내는 만큼, enum 클래스는 멤버들도 다들 final로 선언해서 실행 중에 값이 변경되지 않는 속성을 지정하게 하는 편이다.

enum 명칭이 하나밖에 없으면..?? 얘는 자연스럽게 이 클래스의 싱글턴/단일체가 된다. 그렇기 때문에 Java의 enum class는 싱글턴을 만드는 정석 디자인으로 통용되기도 한다.
생성자를 private로 감추는 등 별별 쑈를 해도 serialize나 reflect 같은 꼼수를 통해 싱글턴 객체를 여러 개 만드는 게 가능한 반면, enum class는 언어 차원에서 그런 일이 벌어지지 않는다는 게 보증된다.

정말 신기한 용법이다. C++의 enum class는 클래스처럼 취급되는 enum이지만, Java의 enum class는 enum처럼 생긴 클래스라고 볼 수 있겠다.

3. 이름이 붙지 않은 일회용 함수/클래스

2000년대 이후부터는 C++, C#, Java 같은 주류 프로그래밍 언어에 객체지향뿐만 아니라 함수형이라는 패러다임이 도입되었다. 덕분에 중괄호 {}로 둘러싸인 코드를 통째로 변수에 대입한다거나, 심지어 함수의 인자로 일회용으로 익명으로 전하는 게 가능해졌다. 인자를 받아서 리턴값을 주는 코드의 묶음이지만 굳이 함수의 형태로 선언· 정의하고 이름을 붙일 필요가 없다는 것이다.

심지어는 클래스까지 이렇게 간편하게 선언해서 그 인스턴스를 넘겨줄 수 있다.
Java에서 무슨 이벤트에 대한 handler나 listener를 인자로 넘겨줄 때, new XXXX { } 이러면서 객체 선언과 새 파생 클래스 선언과 주요 함수 오버라이딩을 한번에 하는 것 말이다.

그런데, 이렇게 이름 없는 함수나 이름 없는 클래스는 태생적으로 이름이 필요한 요소를 언어의 문법 차원에서 구현할 수 없다.
람다 함수는 자기 자신을 호출하는 재귀호출을 구현할 수 없다.
그리고 이름 없는 클래스는.. 정말 웃기게도 컴파일러가 기본 생성해 주는 것 말고 자신의 독자적인 생성자와 소멸자를 가질 수 없다. =_=;; 흠..

C++은 Java처럼 저렇게 함수 인자에서 새 파생 클래스를 즉석에서 만드는 것까지 지원하지는 않지만.. 새 클래스를 선언할 때 이름을 생략할 수 있다. 이건 반대로 Java에서 지원하지 않는 문법이다.
이름 없는 클래스나 함수를 만드는 게 가능하니 이름에 의존하지 않고 생성자· 소멸자나 함수 자기 자신을 지칭하는 방법이 있긴 해야 할 텐데.. 이건 그냥 언어 차원에서의 한계로 남겨 두려는가 보다.

참고로 C++은 이름 없는 namespace라는 것도 지원해서 얘는 C의 static의 상위 호환으로 간주하고 있다. 즉, 이 영역에 선언되는 함수나 변수는 다른 번역 단위에서는 인식되지 않는 private한 물건이 된다.
그 밖에 이름 없는 구조체· 공용체도 있는데, 개인적인 생각은 오프셋 보정을 위해 크기(자리)만 차지하는 용도이고 실제로 쓰이지는 않는 멤버에 대해서도 이름을 생략할 수 있었으면 좋겠다.

Posted by 사무엘

2023/06/27 08:35 2023/06/27 08:35
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/2176

1.
고층 건물에는 엘리베이터가 있다.
그런데 건물이 왕창 크고 층이 수십 개 이상으로 많고, 이용하는 사람도 왕창 많으면.. 엘리베이터도 한 대만으로는 감당이 안 되니 여러 대를 설치하게 된다.

그런데 이게 설치만 한다고 장땡이 아니다. 엘리베이터는 에스컬레이터나 다른 정규 노선 대중교통과 달리, 승객의 수요예 따라 그때 그때 이동하는 물건이다. 이 점에서는 버스보다는 택시에 더 가까운데, 그래도 중간 합승이 허용된다는 차이가 있을 뿐이다.
이런 특성상, 엘리베이터는 승객과 차량을 똑똑하게 분배하는 운영 시스템이 필요하다. 무식하게 운용하면 차량이 많아도 승객들은 엄청 오래 기다려야 하고, 탄 뒤에도 층마다 서면서 엄청난 비효율이 발생하기 십상이다.

그래서 엘리베이터를 똑똑하게 분배하는 시스템이 없던 과거부터 현재까지 제일 보편적으로 쓰이고 있는 단순한 방법은 (1) 각 차량의 용도를 그냥 층별로 물리적으로 분할하는 것이다. 저층부/고층부, 그리고 홀/짝 이렇게 말이다.
이건.. 모든 엘리베이터들이 바쁠 때야 그럭저럭 괜찮은 방법이다. 그러나 서로 다른 구간끼리 오가는 사람은 불편하게 환승을 하거나 인접층으로 이동해야 한다. 그리고 엘리베이터들이 전혀 바쁘지 않은데도 나와 더 가까이 있는 엘리베이터를 이용하지 못하고 먼 쪽의 엘리베이터를 강제로 이용해야 하는 비효율이 발생한다.

얘보다 약간 더 발전된 엘리베이터는.. 버튼을 누르면 그 층의 모든 엘리베이터의 버튼에 불이 켜진다. 그리고 (2) 지금 층에서 가장 가까이 있는 엘리베이터를 중앙 시스템이 알아서 골라서 보내 준다.
고급 호텔 같은 곳의 엘리베이터가 이런 형태인 경우가 있다. 각 엘리베이터들이 현재 몇 층에 있는지 표시도 해 주지 않는다. 그리고 더 신기한 건.. 밖에서 상행이나 하행 중 한 버튼을 누르는 게 아니라, 처음부터 가고 싶은 층을 찍게 돼 있는 것도 있다.

요런 엘리베이터는 어느 엘리베이터의 문이 열릴지 알 수 없어서 승객이 처음에 약간 헤맬 수 있지만, 그래도 물리적인 층 분담보다는 더 똑똑하고 효율적으로 동작할 가능성이 있다.
하지만 약간 아쉬운 점은.. 중앙 시스템이 찜한 엘리베이터가 승객의 층으로 오는 도중에 다른 승객 때문에 많이 지체되고 있을 때, 그냥 다른 엘리베이터를 대신 지정하는 게 안 될 확률이 높다는 것이다. AI가 그런 것까지 고려해서 만들어지지는 않기 때문이다.

이건 절대적인 정답이 없는 문제이다. (1) 엘리베이터를 이미 탄 승객, (2) 밖에서 기다리는 승객, 그리고 (3) 엘리베이터 자체의 주행 거리 총량(소모 전력) 사이의 trade-off 제로썸 게임이기 때문이다.

그래도 최적해에 근접하는 가장 효율적인 전략은.. (1) 엘베들이 바쁘지 않을 때는 층수 구분 따위 불문하고 가장 가까이 있는 차량을 보내 주고, (2) 도중 정차도 일단은 아무 요청이나 들어 주되, 이 방향으로 가는 동안 2번, 3번 이상 이미 정차를 했다면 그때부터는 요청을 서서히 안 들어 주고 다른 차량을 대신 보내는 식으로 유도리를 발휘하는 게 좋을 것 같다.
횟수 제한, 또는 앞으로 최소한 n층 정도는 무정차로 진행.. 이런 식으로 정차를 제한할 수 있다.

물론 대체 차량이 너무 멀리 떨어져 있다거나 하면 전략이 달라지게 된다.
그리고 처음에 찜했던 차량이 너무 오랫동안 '열림'으로 붙잡혀서 못 가고 있을 때도 가까이 있는 다른 차량을 대신 보내 줘야 한다.

이 정도면 거의 AI 기술까지 접목해도 되지 않나 싶다.;;
AI가 진짜 똑똑하게 동작하려면 승객을 목적지 층별로 분할하는 것까지 알아서 처리할 필요가 있다. 차량들을 강제로 홀/짝, 고/저로 구분하지 않더라도 말이다. 모든 차량에 모든 층 승객이 고르게 뒤섞여 있으면 AI 할아버지라 해도 효율이 떨어지는 걸 피하기 어렵기 때문이다.

그리고 그렇게 하기 위해서는 엘리베이터를 기다릴 때 가고 싶은 층을 미리 지정할 필요가 있다. 그러면 이 AI가 상황을 대충 파악하고는 "3층, 7층, 12층 가실 승객은 지금 오는 엘리베이터 B차량을 타십시오. 4층, 10층, 15층에 가실 승객은 1분쯤 뒤에 도착 예상되는 저쪽 A차량을 타십시오" 이렇게 구간을 적절하게 나눠서 교통정리를 할 수 있다.
물론, 다음 엘리베이터의 도착이 너무 늦어지고 있으면 한 엘리베이터가 좀 더 촘촘하게 정차하게 된다. 이걸 유동적으로 결정하는 것도 AI의 몫이다.

이렇듯, 엘리베이터 분배도 깊고 어렵게 생각하면 끝이 없는 문제라는 걸 알 수 있다.;; 실용성도 아주 높고 말이다. 오죽했으면 엘베 관제는 1989년 제1회 국제 정보 올림피아드의 문제로 나오기도 했다.
층수가 20? 30? 정도 넘어가면 주행 속도도 굉장히 빨라져서 도착하기 수 층 전부터 감속하는 게 느껴지는 고속 엘리베이터가 투입되는 듯한데.. 이런 건 본인도 태어나서 지금까지 경험한 게 극소수이다. 엘베가 다 같은 엘베가 아닌 듯하다.
딱 층에 맞게 정차하는 건 지하철 전동차가 역의 정차 위치에 정확하게 맞춰서 정지하는 것과 비슷한 테크닉이라 하겠다.

2.
고층 건물에서 다수의 엘리베이터를 분배하는 알고리즘은 자동차를 통제하는 신호 알고리즘과도 비슷한 구석이 있다.
특히, 도로 상황을 전혀 고려하지 않고 그냥 무조건 일정 간격으로 적록 신호를 주는 게 엘베로 치면 무식하게 홀/짝, 고/저를 나눈 것과 비슷하다.

모든 방향에서 차들이 엄청 많이 다닐 때는 묻지도 따지지도 말고 그렇게만 해도 충분히 공평하다. 그러나 차량 통행이 아주 적을 때도 그렇게 하면 쓸데없는 대기 시간이 길어져서 비효율적이고 불편하다.
그래서 작은 도로에서는 평소에는 적록 신호이다가 심야에는 황색 점멸로 신호가 바뀌어서 일말의 스마트함을 추구하기는 하는데.. 신호 종류가 바뀌는 기준은 그냥 밤 11시에서 아침 6시 사이처럼 미리 지정된, '하드코딩'된 시간대일 뿐인 게 좀 아쉽다.

그러니 미래에는 신호라는 것도 더 스마트하게 바뀌어야 하리라 생각된다. 아까 그 엘리베이터 분배 알고리즘에서 하던 고민이 그대로 적용된다. 트래픽이 많을 때와 적을 때의 전략이 서로 크게 달라지는데, 그 경계 판단을 잘 해야 한다.
그래서 운전자가 시험 든다거나 분노해서 신호를 위반하려는 충동을 느끼지 않게 해야 한다. (1) 다니는 차가 아무도 없는데 빨간불이어서 쓸데없이 멀뚱멀뚱 서 있는 것, 그리고 (2) "왜 나만 갖고 그래~ 왜 교차로마다 계속 신호에 걸리는 거야..?? 이런 걸 아주 없애지는 못하더라도 최소화해야 할 것이다.

좌회전을 위해서 비보호나 점멸 대신 감응 신호가 더 늘어나고, 교차로 건너편이 계속 막히고 있으면 애초에 파란불이 되지 않게 신호등이 더 똑똑해져야 할 것이다.
비보호를 대체하는 좌회전 감응 신호는 좌회전 차량이 요청하자마자 무작정 맞은편 방향을 틀어막는 게 아니다. 처음 20~30초 정도 동안은 맞은편에 차가 없어서 충분히 안전할 때만 파란불 신호를 준다. 그렇지 않고 너무 오랫동안 좌회전을 못 하고 있으면 그때에야 불가피하게 맞은편 방향을 막고 파란불 신호를 준다.

이전글에서 언급했던 것처럼 교차로 건너편에 차가 들어갈 자리가 없으면 우리 신호가 돌아왔더라도 파란불 신호를 주지 않는 것도 덤.. 꼬리물기도 원천 차단된다.
이런 스마트 신호 체계 하에서는 교통법규 위반 건수가 줄어들고 교통사고도 자연히 더 줄어들 것이다.

Posted by 사무엘

2023/04/03 08:35 2023/04/03 08:35
, , ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/2144

1. 컴포넌트화의 필요성

전산학 중에서 소프트웨어공학이라는 것은 방대한 소프트웨어를 인간이 여전히 유지보수 가능하게 복잡도를 제어하며 설계하기, 기능과 파트별로 역할을 잘 분담시켜서 각 파트만 재사용하거나 딴 걸로 교체를 쉽게 가능하게 하기, 소프트웨어의 분량· 작업량· 품질을 정확하게 측정하고 효율적인 개발 절차를 정립하기처럼..
응용수학이나 전자공학보다는 산업공학과 가까운 측면이 있다. 단지, 얘는 유형의 제품이 아니라 무형의 코드 형태이기 때문에 여느 공산품과는 성격이 약간 다르게 취급될 뿐이다.

20세기 후반에 인공지능 연구 업계에 "AI 겨울"이 있었고 게임 업계에 "아타리 쇼크"라는 재앙이 있었던 것처럼, 프로그래밍 업계에도 "소프트웨어의 위기"라는 게 이미 1970년대에부터 있었다.

  • 코딩을 너무 중구난방으로 하고 나니, 일정 규모 이상의 프로젝트에서는 도대체 유지보수가 되질 않고 그냥 처음부터 다시 새로 만드는 게 더 나을 지경이 된다.
  • 이놈의 빌어먹을 스파게티 코드는 하는 일도 별로 없는데 쓸데없이 너무 복잡해서.. 처음에 작성했던 사람 말고는 알아먹을 수가 없고 maintainable하지가 않다.
  • 소프트웨어의 개발 속도가 오히려 하드웨어의 발전 속도를 따라가지 못한다.
  • 작업 기간을 줄이기 위해서 사람을 더 뽑았는데.. 웬걸, 신입들을 가르치느라 시간이 더 소요된다;;;
이런 문제들이 체계적인 소프트웨어공학이라는 이론의 도입 필요성을 촉진시킨 것이다.

그래서일까..??
"무식한 goto문 사용을 자제하자"라는 구조화 프로그래밍 이후로 객체지향 프로그래밍이란 게 프로그래밍 언어와 코딩 패러다임을 완전히 정복했다. 요즘 주류 언어들 중에 '클래스', 그리고 '상속'이라는 게 없는 언어는 찾을 수 없을 것이다.;; 이게 캡슐화, 은닉, 재사용성 등 소프트웨어공학적으로 여러 바람직한 이념을 코드에다 자연스럽게 반영해 주기 때문이다.

실험적으로 시도됐던 초창기의 순수 객체지향 언어들은 유연하지만 느린 런타임 바인딩 기반의 메시지로 객체 메소드를 호출한다거나.. 심지어 정수 하나 같은 built-in type에다가도 몽땅 타입 정보 같은 걸 덧붙이며 객체지향을 구현하느라 성능 삽질이 많은 경우도 있었다.
그러나 C++은 객체지향 이념에다가 C의 저수준, 그리고 빌드타임 바인딩(경직되지만 빠른..)을 지향하는 현실 절충형 디자인 덕분에 상업적으로 굉장히 성공한 객체지향 언어로 등극했다.

2. 마소의 실험

자 그래서..
1990년대에 마소에서는 고유 브랜드인 Windows가 대히트를 치고 소프트웨어 OEM (IBM 납품..)으로 그럭저럭 먹고 살던 처지를 완전히 벗어나니.. 당장 먹고 사는 고민보다 더 본질적이고 고차원적인, 소프트웨어공학적인 고민을 시작했던 것 같다.
얘들 역시 소프트웨어를 재사용 가능한 컴포넌트 형태로 만드는 것에 관심을 많이 기울였다. 그래서 재사용을 위해 바이너리 수준의 공통 규약, 프로토콜을 만들어서 자기들의 운영체제 차원에서 밀어붙이고 홍보하기 시작했다.

이때가 마침 C에 이어 C++ 컴파일러를 개발하고 MFC라는 라이브러리도 만들고, 코딩 스타일에 본격적으로 '객체지향'이란 게 가미되기도 했던 때이다. 하지만 마소에서 추구했던 것은 단순히 언어나 개발툴 차원에서 함수나 클래스의 모음집인 라이브러리 SDK 만들고 DLL 만드는 것 이상의 수준이었다.

제일 먼저.. (1) Windows라는 이름답게, 특정 기능을 수행하는 윈도 컨트롤을 컴포넌트화한다. 리치 에디트 컨트롤을 비롯해 각종 공용 컨트롤, 웹브라우저 컨트롤 같은 것 말이다. 이 사고방식이 극대화되어 "컴포넌트를 내 폼에다가 끌어다 놓고, 프로퍼티를 설정하고 이벤트 핸들러를 구현해서 응용 프로그램을 곧바로 만든다" RAD라는 개념이 완성되었으며.. Visual Basic이라는 정말 똘끼 충만한 개발툴이 만들어지게 됐다.

도스 시절의 GWBASIC이나 QuickBasic에서 참신한 점은 그 특유의 대화식 환경이었는데, Visual Basic은 또 다른 새로운 돌풍을 일으켰다. 경쟁사인 볼랜드에서는 이런 개발 스타일을 파스칼과 C++에다가도 도입하게 됐다.

(2) 그리고 마소에서는 서로 다른 응용 프로그램에서 만든 결과물을 문서에 자유롭게 삽입할 수 있게 했다. 이름하여 OLE라는 기술이다.
가령, Windows의 워드패드는 아래아한글이나 MS Office Word에 비하면 아주 허접한 프로그램일 뿐이다. 하지만 문서 안에 그림판에서 만든 비트맵 이미지를 집어넣고, 엑셀에서 만든 차트를 집어넣을 수 있다.

별도의 수학 수식 편집기에서 만든 수식, 악보 편집기에서 만든 악보, 그리고 WordArt/글맵시 같은 프로그램으로 만든 각종 글자 꾸임 배너까지..
단순히 무식한 그림 형태로 집어넣는 게 아니라는 것이 핵심이다. 이것들은 벡터 이미지로 취급되기 때문에 크기를 키워도 화질이 깔끔하게 유지된다.

그리고 그런 출력 이미지 자체뿐만 아니라, 각 프로그램에서 취급하는 내부 원본 데이터, 즉 소스가 그대로 보존된다. 그렇기 때문에 만들었던 객체를 손쉽게 수정도 할 수 있다.
그 객체를 더블 클릭하면 프로그램 내부에서 그림판이나 악보 편집기, 수식 편집기 등이 잠시 실행돼서 객체를 수정하는 상태가 된다..;;

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

서로 다른 프로그램이 이런 식으로 서로 분업 협업한다니.. 신기하지 않은가? 도스 시절에는 상상도 못 한 일일 것이다.

3. 프로그래밍의 관점

그러니 Windows에서 워드처럼 뭔가 인쇄 가능한 출력물을 만드는 업무 프로그램이라면 OLE 지원은 그냥 닥치고 무조건 필수였다. 다른 OLE 프로그램의 결과물을 삽입하든(클라), 아니면 다른 프로그램에다 자기 결과물을 제공하든(서버), 혹은 둘 다 말이다. macOS나 리눅스에는 비슷한 역할을 하는 규격이나 기술이 있는지 궁금하다.

Windows 프로그래밍을 다루는 책은 고급 topic에서 OLE를 다루는 것이 관행이었다. 다만, Windows API만으로 OLE 지원을 저수준 구현하는 건 굉장히 노가다가 심하고 귀찮았다. 그래서 MFC 같은 라이브러리 내지 아예 VB 같은 상위 런타임이 이 일을 상당수 간소화해 줬었다. MFC 앱 신규 프로젝트 세팅 마법사의 경우, OLE 지원 기능의 추가 여부를 선택하는 옵션이 응당 제공되었다.

Windows라는 플랫폼의 프로그래밍에 입문하려면 창(윈도)의 스타일과 특성, 메시지 메커니즘을 알아야 할 것이고 그래픽 API라든가 셸의 구조에 대해서도 알아야 할 것이다.
그런데 그런 것뿐만 아니라 이 바닥도 완전히 독립된 별개의 프로그래밍 분야이며, 기초부터 고급까지 한데 연결된 Windows 프로그래밍의 정수라고 생각된다.

이건 제일 간단하게는 확장자 연결이나 클립보드, drag & drop 구현과도 연결고리가 있다. 이 분야 API를 제공하다 보니 COM이라는 IUnknown이 어떻고 type library가 어떻고 하는 규격이 제정되었다.
사실, Windows에 레지스트리라는 것도 맨 처음엔 확장자 연결이나 OLE 클라/서버 정보만 저장하기 위해서 만들어졌다가.. 나중에 ini를 대체하는 응용 프로그램 설정 저장 DB로 용도가 확장된 것이다.

COM 형태로 제공되는 운영체제 기능을 사용하려면 CoCreateInstance를 호출해야 하고, 이런 프로그램은 처음에 CoInitialize라는 함수를 호출해 줘야 한다. 즉, 운영체제를 상대로도 별도의 초기화가 필요하다는 것이다.
그런데 OLE 기능을 사용하려면 OleInitialize라는 함수를 사용하게 돼 있는데, 얘가 하는 일은 CoInitialize의 상위 호환이다. OLE가 COM의 형태로 구현돼 있기 때문에 그렇다. 둘의 관계가 이러하다.

굳이 OLE 관련 기능뿐만 아니라 가까이에는 DirectX, 그리고 날개셋 한글 입력기와도 관계가 있는 TSF 문자 입력 인터페이스도 다 COM 기반이다. 하지만 문자 입력은 굳이 COM이나 OLE 따위 기능을 사용하지 않는 프로그램에서도 관련 기능을 접근할 수 있어야 하기 때문에 COM 초기화 없이 관련 인터페이스들을 바로 생성해 주는 함수를 별도로 제공하는 편이다.

4. ActiveX

과거에 인터넷 환경에서 마소 IE 브라우저의 지저분한 독점과 비표준 ActiveX는 정말 악명 높았다. 그런데 ActiveX라는 건 도대체 무슨 물건인 걸까??

마소에서는 앞서 컴포넌트화했던 그 윈도 컨트롤들을 데스크톱 앱뿐만 아니라 인터넷 웹에서도 그대로 돌려서 그 당시 1990년대 중후반에 각광받고 있던 Java applet에 대항하려 했다. Visual Basic 폼 내지, 특정 프로그램의 내부에서 플래시나 IE 컨트롤 생성하듯이 꺼내 쓸 법한 물건을 웹에서 HTML object 태그를 지정해서 그대로 띄운다는 것이다.

그때는 컴퓨터의 성능이 지금처럼 좋지 못했고 지금 같은 방대한 웹 표준이 존재하지도 않았었다.
그러니 웹브라우저에서 동영상도 보고 초고속으로 돌아가는 게임도 하고, 특히 무엇보다도 금융 거래를 위한 각종 암호화 기능을 돌리기 위해서는 닥치고 웹에서 그냥 생짜 x86 native 앱을 돌리는 게 제일 편했다.

이런 컴포넌트의 이름이 OLE Control이었는데, 이걸 웹에다 특화된 형태로 신비주의 마케팅 명칭을 붙인 게 ActiveX 컨트롤이다. 아마 마소 역사를 통틀어 길이 남을 엽기적인 작명이 아닐까? 하긴, DirectX도 비슷한 시기의 작명이니까 말이다. ㅡ,.ㅡ;;

하지만 웹에서 가상 머신이 아니라 특정 플랫폼의 네이티브 코드를 직접 구동하는 건 너무 무식하고 이식성도 떨어지고 표준 친화적이지 못하니 ActiveX는 마소에서도 버림받고 늦어도 2010년대부터는 완전히 퇴출 단계에 들어섰다.
지금은 Java applet도 완전히 멸망했고, 이들의 대체제는 정말 눈부시게 성능이 향상된 JavaScript 가상 머신이라고 보면 될 것이다.

5. OLE와 관련된 과거 유행: embed된 형태로 실행

저렇게 마소에서 COM/OLE니 ActiveX를 막 밀고 양성하던 1990년대 말~2000년대 초에는 어떤 프로그램이 다른 프로그램의 내부에 embed된 형태로 실행된 모습을 지금보다 훨씬 더 자주 볼 수 있었던 것 같다. 그와 관련해서 ActiveDocument (!!)라는 기술도 있긴 했다.

당장, MS Office 97에 있었던 Binder라는 유틸은 여러 Word, Excel 따위의 문서를 한데 묶어서 자기 안에서 해당 프로그램을 띄워서 내용을 편집하는 유틸이었다. 대단한 기술이 동원됐을 것 같지만 그래도 쓸모는 별로 없었는지 후대에는 짤리고 없어졌다.

Visual C++ 6의 IDE는 “새 파일” 대화상자를 보면 통상적인 텍스트 파일이나 프로젝트/Workspace뿐만 아니라 맨 끝에 Other documents라는 탭도 있어서 MS Office 문서를 자기 IDE 안에서 열어서 편집할 수 있었다. 당연히 MS Office가 설치돼 있는 경우에만 한해서.. 아까 그 Binder처럼 말이다.
그런데 이 역시 현실에서는.. 그냥 Word/Excel을 따로 띄우고 말지 굳이 워드/엑셀 문서를 왜 Visual C++ IDE에서 편집하겠는가? 그 기능은 후대엔 없어졌다.

ActiveX 기술의 본가인 IE 브라우저야 더 말할 것도 없다.
저런 MS Office 문서를 다운로드 해서 열면 해당 앱이 따로 열리는 게 아니라, 문서 보기/편집창이 웹페이지 화면에 떴었다. 별도의 프로세스로 말이다. 그 기술이 최초로 도입된 건 IE4가 아니라 1996년의 IE3부터였다.
파워포인트 슬라이드는 그 웹페이지 화면에서 곧장 슬라이드 쇼가 시작됐다. 이건 괜찮은 기능인 것 같다.

옛날에 pdf를 보기 위해서 Acrobat Reader를 쓰던 시절엔, 이 앱도 OLE 기술을 이용해서 IE 내부에 embed된 상태로 뜨는 걸 지원했었다. 지금이야 브라우저가 자체적으로 PDF를 표시해 주는 시대이지만 말이다.;;

요즘 컴터 다루면서 OLE 개체 삽입 기능을 쓸 일이 과연 얼마나 될까?
요즘은 개체 삽입으로 프로그램을 실행했을 때, 예전처럼 프로그램이 embed 형태로 실행되는 게 아니라 그냥 별도의 창으로 따로 뜨는 편인 것 같다. 앞서 소개했던 XP 시절의 모습과는 좀 다르다.
따로 실행됐을 때는 원래 문서에 표시된 컨텐츠와 자기가 다루는 컨텐츠가 따로 노니, 전자는 검은 빗금을 쳐셔 구분해야 한다는 UI 가이드라인도 있긴 하다.

사용자 삽입 이미지

이런 것들이 다 20여 년 전의 아련한 추억이고 한물 간 유행이다.
그나저나 인터넷으로 ppt 슬라이드를 받으면 바로 열리지 않아서 불편하다. 속성을 꺼내서 ‘신뢰할 수 없는 파일 차단’을 해제해야 볼 수 있다. 이것도 chm 도움말 파일을 바로 열리지 않는 것처럼 보안 강화를 위해서 취해진 조치인지 모르겠다.

Posted by 사무엘

2023/03/16 08:35 2023/03/16 08:35
, , , , ,
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/2137

« Previous : 1 : 2 : 3 : 4 : 5 : ... 31 : Next »

블로그 이미지

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

- 사무엘

Archives

Authors

  1. 사무엘

Calendar

«   2024/03   »
          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:
2620040
Today:
3039
Yesterday:
1544