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

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

1. 프로젝트 -- IDE의 관점과 빌드 스크립트의 관점

C/C++ 빌드 시스템에서 프로젝트란, 한 바이너리.. exe, dll, lib, so, a, out 따위를 만들어 내기 위한 1개 이상의 파일들의 묶음을 말한다. 그리고 여러 바이너리들을 생성하는 여러 프로젝트의 묶음을 Visual Studio 용어로는 솔루션이라고 부른다.

프로젝트를 구성하는 파일 중, 컴파일러가 처리하는 각각의 소스 파일(c/cc/cpp)은 '번역 단위'(translation unit)이라고 불린다. 1개의 번역 단위는 1개의 obj 파일로 바뀌게 된다.
그런데 요즘은 프로그래머의 편의와 작업 생산성을 위해 통합 개발 환경(IDE)이란 게 즐겨 쓰이며, 이런 IDE에서 취급하는 프로젝트는 make 같은 재래식 툴에서 취급하는 빌드 스크립트(makefile 같은)와는 완전히 일치하지 않는 관계이다.

프로젝트 파일에 들어있는 정보를 기계적으로 추출해서 makefile을 생성하는 것은 비교적 쉽게 가능하다. 그러나 makefile로부터 역으로 IDE용 프로젝트 파일을 재구성하는 것은 더 귀찮고 번거롭다.
프로젝트 파일에는 빌드가 아닌 IDE 내부에서 의미를 갖는 각종 설정 정보들이 더 들어있으며, makefile은 절차형 스크립트로서 프로젝트 파일만으로 표현할 수 없는 각종 조건부 빌드 로직이 들어있을 수 있기 때문이다.

일례로, IDE의 프로젝트 파일에는 소스 파일들을 다단계 폴더 형태로 묶고 분류해서 표시하는 기능이 있다. 이런 계층 구조 정보는 전적으로 사용자의 편의를 위해 존재할 뿐, 빌드할 때는 전혀 쓰이지 않는다. 어차피 다 똑같이 일렬로 늘어놓아서 컴파일 하고 링커로 넘겨주는 파일들일 뿐이기 때문이다.

또한 이 계층 구조는 그 소스 파일들이 놓여 있는 디렉터리 구조와는 전혀 무관하게 지정 가능하다. 하지만 현실에서는 프로젝트에서의 파일 grouping을 실제 디렉터리 구조와 동일하게 해 주는 게 사람을 덜 헷갈리게 하고 좋을 것이다. 특히 여러 사람이 유지 보수하는 프로젝트라면 더욱 말이다.

한 프로젝트를 구성하는 소스 코드들이 반드시 동일한 디렉터리에 있어야 할 필요는 없지만.. 특별한 사정이 없는 한 컴파일된 출력 파일은 오로지 한 곳에서만 생성된다.
그렇기 때문에 서로 다른 디렉터리에 있더라도 한 프로젝트에 이름이 동일한 파일이 여럿 있지는 않는 게 좋다.

오픈소스 DB 라이브러리인 sqlite는.. amalgamation이라고 해서 4MB짜리.. 거대한 sqlite3.c 파일 하나로 라이브러리 전체의 기능을 제공하는 엄청난 용자짓도 하던데..;;; 이건 극단적인 예이다.
들고 다니고 관리하기 편하고 빌드가 깔끔하고 최적화가 잘 되는 장점이 있지만, 컴파일러나 IDE가 파싱 하다가 체할 수 있고 코드 분석이나 디버깅이 잘 안 되는 단점도 있을 수 있다. 요즘도 보수적인 IDE나 디버깅 업계에서는 줄 수가 64K를 넘는 소스 파일을 좋아하지 않는 편이다.;;.

2. 정적 분석

어떤 프로그램에서 구조적인 메모리 오류나 보안 결함을 찾아내는 검증 도구 내지 방법은 크게 ‘동적 분석’과 ‘정적 분석’으로 나뉜다.
전자는 빌드한 프로그램을 가상의 샌드박스 안에서 직접 실행해 보면서 문제점을 찾는다. 그러나 후자는 프로그램을 실행하지 않고 소스 코드만 쭉 훑으면서 문제점을 찾아 낸다. 둘은 손실 압축과 무손실 압축, 실시간 렌더링과 오프라인 렌더링만큼이나 서로 영역이 다르다.

서버처럼 무한 대기· 무한 루프를 돌며 반영구적으로 돌아가는 프로그램을 동적 분석으로 검증하는 건 쉽지 않다. 프로그램이 동일 지점에 돌아왔을 때 다른 메모리 문제 없이 항상성이 보장된다는 걸 겉으로 드러나는 상태만 보고 얼추 때려잡을 수밖에 없다.

그러나 정적 분석은 프로그램의 실행 형태와 전혀 무관하게.. 무한루프건 배배 꼬아 놓은 지수함수 시간 복잡도의 재귀호출이건 무관하게.. “코드의 양이 유한하다면 분석을 위한 시간 복잡도도 유한하다”, “동일한 코드를 컴파일하는 데 걸리는 시간의 최대 수십 배 정도”이니 신통하지 않을 수 없다.

물론 정적 분석은 100% 정확하지 못하며 오탐 오진도 많다.
그런데, 각종 구조체와 포인터를 넘나들면서 진짜 너무 복잡하게 꼬여 있는 메모리를 일일이 추적을 못 하는 건 차라리 수긍을 하겠다만.. 이거 뭐 사람만도 못한 너무 황당한 오진을 하거나 간단한 문제도 못 잡아 내는 경우가 있어서 좀 아쉬웠다.

정적 분석은 그 정의상 프로그램을 “실행해 보지 않고” 코드를 분석해 주는데..
개발툴과 연계해서 “빌드는 같이 하면서” 문제를 추적하는 놈이 있는가 하면, 빌드조차 없이 진짜 코드 외형만 들여다보고 분석하는 놈도 있는 것 같다. 둘은 개발 이념이 서로 다르다.
후자가 정확도가 더 떨어지겠지만, 그래도 사용하기는 더 쉽다. 프로젝트나 makefile 세팅 없이 그냥 방대한 h와 cpp/c 묶음을 압축해서 던져 주기만 하면 분석이 되기 때문이다. 마치 Soure Insight와 비슷한 유도리가 있다.

솔직히 정적 분석을 위해서는 코드가 특정 플랫폼용으로 반드시 빌드가 돼야 할 필요가 없을 것이다. 가령, 32비트에서는 괜찮은데 64비트에서만 메모리 오프셋 문제를 일으키는 코드라면.. 그건 어차피 이식성 문제가 있는 코드이니 정적 분석 툴이 지적해 줘야 할 것이다.

내가 C/C++ 정적 분석으로부터 기대하는 아이템들은 다음과 같은 것들이다. 그런데 이것도 생각보다 스펙트럼이 다양한 것 같다.

  • memcpy, malloc 같은 함수에서 버퍼 크기 계산 잘못한 것, 문자열의 경우 null문자 공간을 빼먹은 것, 0초기화를 하지 않은 것 등등 (C 코드 한정.. 제일 지저분)
  • 함수가 자기 지역변수의 주소를 리턴
  • memory leak 내지 dangling pointer 가능성이 있는 것
  • C++에서 아직 초기화되지 않은 멤버 변수를 다른 멤버의 초기화에 동원하는 것 (이거 굉장히 교묘한 실수인데 왜 컴파일러에서 지적해 주지 않을까?)
  • a=a++ 같은 이식성 떨어지는 코드, 잠재적인 코딩 실수

3. #include의 미묘한 면모

C/C++에서 #include가 하는 일은 말 그대로 다른 텍스트 파일을 현재 컴파일 중인 번역 단위에다가 끌어오는 게 전부이다. 외부 패키지나 라이브러리를 지정하는 기능이 없다. C/C++에는 Java의 import, C#의 using 같은 깔끔한 명령이 없다.
그 대신, #include를 남용하면 프로젝트에 정식으로 포함되어 있지 않은 파일을 끌어들여서 이에 대한 의존도를 생성할 수 있다.

개인적으로는 <xxx>가 아니라 "xxx" 형태의 include는.. 컴파일러가 프로젝트에 포함돼 있는 파일만 쓰도록 하고, 프로젝트에 없으면 파일이 디스크 상에 존재하더라도 없다는 에러를 내게 하는.. 그런 옵션이 좀 있었으면 좋겠다.
왜냐하면 의도하지 않았던 파일이 잘못 인클루드 되는 바람에 컴파일러가 난독증을 일으키고 사람은 사람대로 빡치는 일도 얼마든지 있을 수 있기 때문이다.
또한, 프로젝트에 포함되지 않은 채 #include 된 파일은 수정됐어도 걔를 #include하는 소스가 고쳐지지 않았다면 재컴파일 되지 않아서 다른 오동작을 유발할 수도 있다.

#define뿐만 아니라 #include로도.. 파일 내용 전체를 꼼꼼하게 파싱하지 않고 편의 시설을 제공하는(syntax coloring, 간단한 문법 체크, 선언/정의로 가기, 함수 목록 추출 따위) IDE 에디터를 농락하고 오동작을 유발할 수 있다.
가령, "}" 요 문자 하나만 달랑 들어있는 소스 파일을 하나 만든 뒤,

void func
{
  ......
#include "right_curling_bracket.c"

이렇게만 하면 얘는 문법에 맞는 코드가 된다.
또한, 따옴표로 둘러싸인 문자열을 잔뜩 넣은 뒤,

static const char BIG_STRING_DATA[] =
  "XXXXX"
#include "more_string_dadta.c"
  "ZZZ";

이런 식으로.. 거대한 테이블 데이터의 내용을 외부 파일 인클루드를 통해 조달할 수도 있다.
단지, #include는 자기 안의 코드만 대치 가능할 뿐, 같은 전처리기의 레벨을 넘나들지는 못한다. 즉,

#ifdef
#include "file_containing_sharp_endif.c"

이렇게 때우는 건 허용되지 않는다. 저 #if에 상응하는 #else나 #endif 따위는 반드시 지금 소스 파일에 존재해야 한다.

끝으로.. #include 대상인 "xxx"나 <yyy>는 C언어의 관할을 받는 문자열 리터럴이 아니다. 그렇기 때문에 \ 탈출문자가 적용되지 않으며, 디렉터리를 표현할 때 역슬래시를 두 번 \\ 찍을 필요가 없다. 사실은 Windows건 어디에서건 더 보편적인 / 를 쓰는 게 더 좋을 것이다.

#include 대상으로 매크로 상수를 지정해 줘도 된다. 이걸 사용한 예는 본인의 경험으로는 FreeType 라이브러리가 지금까지 유일하다.
다만, #include 경로는 C 문자열 리터럴이 아닌 관계로, "aaa" "bbb" 라고 끊어서 썼을 때 자동으로 "aaabbb"라고 이어지는 처리도 되지 않는다. 이런 식의 변태적인(?) 활용은 가능하지 않다는 걸 유의하자.

4. 빌드 절차의 디버깅

뭔가.. 빌드 스크립트와 컴파일러의 동작을 디버깅 하는 기능이 좀 있었으면 좋겠다.
breakpoint를 잡고 나서 F5 Run을 하는 게 아니라, F7 '빌드'를 누른다.
일반적인 디버깅이라면 빌드된 프로그램이 그 지점을 실행할 때 break가 걸리겠지만, 이때는 컴파일러가 그 지점을 읽기 시작했을 때 break가 걸린다.

break가 걸리고 나면 이 시점에서 현재 정의돼 있는 #define 심벌들을 몽땅 조회하고 실제 값과 정의된 곳(헤더 파일? 컴파일러 옵션?)을 추적할 수 있다. 치환 결과에 또 매크로가 들어있더라도 당연히 계속 까 볼 수 있다.
각종 #pragma 옵션이 지정된 내역, 옵션 스택, #line이 적용된 것도 당연히 확인 가능하다.

프로그램 실행 디버깅에서 step into / over / out이 있는 것처럼..
#include에 대해서는 마치 함수 호출처럼 step into를 할 수 있다. 어느 디렉터리에 있는 헤더 파일이 선택됐는지, 현재 컴파일러의 스택 상으로 include 깊이가 얼마나 되는지를 살펴볼 수 있다.
경우에 따라서는 <>, ""에 따라서 탐색 순서도 추적 가능하다. 요 디렉터리에 없어서 다음으로 이 디렉터리, 다음으로 저 디렉터리 같은 순이다.

#error나 #pragma warning 같은 건 아예 별도의 로그 창으로 찍히게 할 수도 있다.
흠, 좀 잉여력이 풍부해 보이긴 하지만, 그럴싸하지 않은가? =_=;;
웹브라우저에서 '개발자 모드'가 있는 것처럼.. 이런 기능이 있으면 개발자가 자기가 내력을 다 알지 못하는 방대한 프로젝트와 빌드 시스템에 처음 적응할 때 도움이 될 것 같다.

Posted by 사무엘

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

C 언어의 애환

1. 비트필드

C언어의 구조체에는 다른 언어에서는 거의 찾을 수 없는 비트필드라는 물건이 있다.
얘는 굉장히 편리하고 강력한 프로그래밍 요소이다. 바이트 경계에 딱 떨어지지 않는 숫자를 일반 숫자 다루듯이 읽고 쓰게 해 주니 이 얼마나 대단한가? IEEE754 부동소수점이라든가, 과거 2바이트 조합형 한글 같은 건 비트필드 구조체를 잘 만들어서 내부 구조를 쉽게 분석해 볼 수 있다.

다만, 비트필드와 관련해서 언어 문법 차원에서 다음과 같은 점이 보완되거나 강화됐으면 좋겠다는 생각이 개인적으로 오래 전부터 들었다.

(1) 지정 가능한 자료형은 그냥 unsigned 아니면 signed 둘 중 하나로 굳혀 버리고, 나머지 쓰잘데기없는 키워드들은 몽땅 거부하고 에러 처리했으면 좋겠다. 어차피 이 필드의 크기는 뒤의 비트수에 의해서 결정될 텐데.. int니 char이니 long이니 하는 건 전혀 불필요하고 쓸데없는 정보이기 때문이다. 괜히 unsigned char _field: 10; 이런 거 체크해서 10이 8보다 더 클 때만 에러 처리하는 건 잉여스러운 짓이다.

사실 본인은 비트필드에서 부호 "있는" 자료형이 쓰이기는 하는지, signed조차도 필요는 한지 그것도 굉장히 회의적이다. 차라리 enum이 쓰일 가능성은 있을지 모르겠다.

(2) 비트필드에서 공간을 배치하는 순서는 결국 타겟 플랫폼의 비트 endianness의 영향을 받는다. unsigned member : 4 라고 해 주면.. little endian에서는 하위 0~3비트가 할당되며, big endian에서는 상위 4~7비트가 할당된다.
더구나 비트필드라는 건 결국 2~4바이트짜리 커다란 정수 하나를 잘게 쪼개기 위해 존재하는 물건인데, 쪼개는 순서 자체가 비트 endianness에 따라 달라진다.

결국 비트필드를 사용해서 특정 파일 포맷이나 패킷 구조를 기술해 놓은 구조체 선언을 보면.. 빌드 환경의 endianness에 따라 조건부 컴파일을 시켜서 little일 때는 같은 멤버를 abcd 순으로 배치하고, big일 때는 이를 dcba 순으로 무식하게 배열해 놓곤 한다.

이게 정형화된 패턴이니 프로그래머가 쓸데없는 삽질을 할 필요 없이, 언어 차원에서 문법을 지원을 좀 했으면 좋겠다.
"이 비트필드들은 16/32비트 기준으로 큰/작은 자리부터 순서대로 분해하는 것이다. 그러니 타겟 아키텍처의 endianness가 이와 정반대이면 컴파일러가 알아서 멤버들의 배치 순서를 뒤집어라" 이렇게 힌트를 준다.

이런 일이 컴파일러가 하기에는 너무 지저분하다면 #pragma 같은 걸로 빼내서 전처리기 계층에다 담당시켜도 된다.
핵심 요지는.. 똑같은 멤버를 프로그래머가 순서만 바꿔서 다시 써 주고 조건부 컴파일을 시키는 무식한 짓만은 좀 없어져야 한다는 것이다.

비트필드가 쓰일 정도의 상황이면.. 아마 이 공간 전체를 거대한 숫자 한 덩어리로 같이 취급하게도 해 주는 union, 그리고 구조체 멤버 배치를 어느 플랫폼에서나 비트 단위로 일치하게 강제 동기화시키는 #pragma pack도 같이 쓰이고 있을 가능성이 매우 높다.
#pragma pack과 #pragma once는 진짜로 사실상의 표준이니 C/C++에서 정식 표준으로 좀 등재시켜야 하지 싶다. char32_t / char16_t 같은 게 결국 built-in type으로 받아들여지고 정식 표준이 된 것처럼 말이다.

참, 당연한 얘기이다만.. 구조체 템플릿에서는 비트필드의 크기를 나타내는 숫자도 템플릿 인자로 공급해 줄 수 있다.
비트필드의 크기는 구조체 멤버에 들어있는 배열의 크기와 위상이 거의 같으니 말이다. 구조체의 크기에 영향을 주는 숫자이며 컴파일 시점에서 값이 상수로 결정되어야 한다.

template<size_t N> struct XXXX {
    unsigned _member: N;
};

아주 C스러운 요소와 C++스러운 요소가 한데 만난 것 같다. ㄲㄲㄲㄲㄲ 비트필드의 크기를 템플릿 인자로 지정할 일은 극히 드물 것이다.;;

교통 분야에서 좌측· 우측 통행이 국가별로 찢어져 있다면, 디지털 컴퓨터에서는 비트의 배치 순서 endianness가 통행 방향과 비슷한 개념이며 아키텍처별로 찢어져 있는 듯하다.
네트워크 표준은 big endian이지만, 컴퓨터들은 x86이 주류이다 보니 little endian이 주류이다. 이건 세계적으로 자동차 도로 우측 vs 좌측과 비슷한 비율이며, 안드로이드 vs iOS와 비슷한 비율인 것 같다. 본인은 big endian을 native로 사용하는 컴퓨터를 평생 한 번도 구경해 본 적이 없다.

2. C의 단순 평면성

C++에 비해, C는 마소에서 거의 아오안 취급을 하기 때문에 컴파일러의 버전이 바뀌어도 달라지는 게 거의 없다. 다만..

  • C99에서 추가된 가변 길이 배열이 Visual C++에서는 지원되지 않는다.
  • 구조체의 가장 마지막 멤버를 구조체 자체의 크기를 차지하지 않는 명목상의 멤버로.. char data[] 내지 data[0] 같은 형태로 선언해서 구조체의 뒷부분을 가변 길이로 활용하는 게.. 여전히 일부 컴파일러의 편법일 뿐, 정식 표준이 아닌 것 같다.
  • 대소문자를 무시하고 문자열을 비교하는 함수가 의외로 표준이 아닌 것 같다. stricmp와 strcasecmp 부류가 혼재해 있다. C는 라이브러리 함수가 ANSI니 POSIX니 하면서 의외로 파편화된 게 좀 있어서 플랫폼 간의 이식성을 저해하는 중이다.

C는 클래스와 상속 계층이 없을 뿐만 아니라, 각종 명칭에 다단계 계층 scope이란 것도 없다. namespace나 using 같은 걸 신경쓸 필요 없이 모든 명칭이 오로지 local 아니면 global.. 그도 아니면 매크로 함수밖에 선택의 여지가 없다. 클래스라기보다는 번역 단위 자체가 클래스와 비슷하며 static이 외부로 노출되지 않는 private 역할을 얼추 담당한다.

그러니 뭔가 아주 단순하며, 입체적인 게 아니라 '평면적이고' 깔끔해 보이기는 하는데.. 한편으로 너무 중구난방이고 명칭이 충돌하기 쉽다.
새로 짓는 이름은 접두사에 목숨을 걸어야 할 것 같다. 이런 언어로 초대형 라이브러리를 만들고 대형 프로그램을 관리하는 데는 한계가 있을 수밖에 없다.

또한 매크로 함수를 너무 사악하게 남발 남용할 경우, 어지간히 복잡하게 꼬인 C++ 템플릿 이상으로 코드가 알아보기 어려워진다. 특히 전처리기의 존재를 알지 못하는 디버거는 매크로 함수와 완전히 상극이다.

매크로 함수 내부의 코드를 한 단계씩 실행할 수 없고, 또 ## 연산자에 의해 새로 생긴 토큰 명칭들은 어지간한 IDE에서 자동으로 파악도 못 해 준다. 이렇게 IDE와의 괴리가 커지고 붕 떠 버린 코드는 사람 입장에서도 짜증이 나서 제대로 들여다보고 유지 보수하기가 싫어진다. 이는 결국 생산성의 저하로 이어진다.
이런 게 C의 어쩔 수 없는 한계인 것 같다. -_-

3. C언어의 강력하고 자유로운 면모

  • 지역 변수, 전역 변수, heap 등 어디든지 가리킬 수 있는 포인터
  • 한 함수 안에서 어디로든 분기할 수 있는 goto문
  • type이고 뭐고 다 씹어먹고서 메모리를 조작할 수 있는 memcpy, memmove (malloc, free 같은 생짜 수동 메모리 관리는 덤)
  • 무슨 토큰이건 다 치환할 수 있는 전처리기 매크로

하지만 위의 요소들은 위험성과 복잡도도 너무 키운다. 저런 저수준 조작이 잔뜩 쓰인 복잡한 코드에서 버그를 찾아내야 된다면.. 정말 머리에서 연기가 피어오를 것이다.
오늘날의 프로그래밍 언어에서는 저것들은 최대한 금기시되고 봉인되고, 다른 형태로 대체되고 있다.

goto는 아무리 사악하다고 하지만 이중 for 문을 한꺼번에 빠져나가기, 그리고 switch와 while/for문을 한꺼번에 빠져나가기 같은 건 너무 아쉽다. 자기보다 뒤로만 goto가 가능하게 제한하는 것도 나쁘지 않을 것 같은데 말이다.
한편, 개발툴에서 define 전개된 결과 기준으로 문자열을 find in files 하는 기능이 있으면 좋겠다는 생각이 가끔 든다.

4. 전처리기 #if 의 동작 방식

C/C++에서 원래 있는 if문 말고, 전처리기의 #if에서는 소스 코드에 있는 변수들을 당연히 전혀 사용할 수 없다. 오로지 #define 심벌과 상수, 기성 연산자만이 사용 가능하며, #define 심벌들은 매크로 치환 후에 다들 상수로 바뀌어야만 한다.

변수나 type이라는 개념이 없기 때문에 대입 관련 연산자는 당연히 전혀 사용할 수 없으며 포인터도 아웃이요, sizeof 연산자도 지원되지 않는다. 그 대신, 어떤 심벌이 #define돼 있는지의 여부를 판별하는 defined라는 고유한 bool값 연산자가 있다.

sizeof는 피연산자가 값이 아닌 타입 명칭일 때는 피연산자를 ( )로 싸지 않아도 된다.
이와 비슷하게, defined도 피연산자가 다른 수식이 아니라 명칭 달랑 하나이기 때문에 ( )가 없어도 된다.

그리고 나도 지난 25년 가까이 전혀 몰랐던 특성이 하나 있는데..
#if 문에서는 정의되지 않은 아무 명칭/심벌을 들이대도 에러 처리되지 않는다. 그런 듣보잡 심벌은 그냥 곱게 상수 0과 동급으로 간주된다~!

무슨 포인터 역참조 할 때 if(ptr && *ptr==1) 이러듯이 #if defined SYMBOL && SYMBOL==1 같은 defined 가드를 설치할 필요가 없다.
SYMBOL 자체가 #define돼 있지 않다면 #if SYMBOL==1은 어차피 자동으로 false로 처리된다.
겨우 이런 사소한 사항 때문에 전처리기가 까탈스럽게 에러를 뱉지는 않으니 걱정하지 않아도 된다.

5. 특수한 코딩 요소

(1) 빌드 configuration이 맞지 않는다면 코드가 아예 빌드되지 않고 고의로 에러가 유발되게 하고 싶을 때가 있다. 이때는 일부러 무식하게 C/C++ 문법에 어긋난 문자열을 늘어놓을 필요가 없이 #error라는 전처리기 지시문을 쓰면 된다.
컴파일러에 따라서는 에러가 아니라 경고 메시지만 흉내 내고 빌드는 계속 진행되게 하는 #pagma message도 표준에 준하는 기능으로 쓰인다. deprecated API를 사용을 권장하지 않는다고 표시하는 것처럼.. 이런 건 언어 차원의 지원이 필요해 보인다.

(2) 파싱과 문법 체크만 할 뿐, 실제 코드를 생성하지 않고 아무 일도 하지 않는 허깨비 유령 함수라는 것도 필요하다. 디버그 로그를 찍는 함수를 조건부로 숨길 때, 템플릿 클래스에 인자가 제대로 주어졌는지 체크할 때 등(static_if 나 컴파일 타임 assert와 비슷)..
이건 _noop라는 컴파일러 인트린식 형태로 제공되는 편이다. 마치 인라인이나 매크로 함수처럼.. 외형은 함수이지만 실제로는 자기 주소가 존재하고 매개변수의 push/pop이 행해지는 함수가 아닌 셈이다.

(3) 내용을 깡그리 무시하고 컴파일러가 파싱하지 않게 하는 영역은 '주석'이라고 불리며, 이건 모든 프로그래밍 언어에 존재한다.
C/C++에서는 /* */ 와 //뿐만 아니라 전처리기를 이용한 #if 0 / #endif도 사실상 주석처럼 쓰일 수 있다.
게다가 얘는 /* */ 와 달리, 중첩이 가능하다. #if 0으로 막혀 있는 구간이라도 전처리기의 #if #else 로직은 무시되지 않기 때문이다. 그래서 중간에 또 #if 0이 섞여 있는 코드라도 한번에 싹 막았다가 해제할 수 있어서 편리하다.

Posted by 사무엘

2023/01/17 08:35 2023/01/17 08:35
, ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/2114

사람의 정치 성향 스펙트럼이라고 해서 백지에다 4개의 구획을 만든 뒤, 좌우로는 말 그대로 좌파와 우파, 상하로는 권위주의와 자유주의(혹은 전체주의와 개인주의) 이렇게 두 축을 표시해 놓은 그림이 있다.

사용자 삽입 이미지

보다시피 둘은 서로 독립적인 변수이다. 좌파라고 해서 다 빨갱이가 아니며 그냥 무정부주의에 가까운 좌파도 있다. 우파 역시 맹목적인 자유뽕에 가까운 성향이 있는가 하면 ‘국익을 위해 멸사봉공’ 이러는 노선도 있다.
두 축에 대해서 하나는 개인에 대한 자유도(상하)이고, 다른 하나는 시장에 대한 자유도(좌우)라고 생각하면 딱 이해가 될 것 같다.

무슨 MBTI 검사하듯이 수십 가지 질문으로 설문 조사를 한 뒤, 자신의 정치 성향을 저 평면 위에다가 찍어 주는 웹 서비스가 많이 있다.
극좌와 극우에 대해서 "극과 극은 통한다" 같은 소리가 종종 나오는 건, 좌우 말고 상하 축이 '전체주의' 쪽으로 일치하기 때문일 가능성이 높다. 그건 좌와 우의 전체 입장을 대변하는 진술은 아닐 것이다.

그런데 사람의 이념뿐만 아니라 프로그래밍 언어의 설계 이념도 이런 식으로 분류 가능하다. 대표적으로 type을 취급하는 방식이다.

사용자 삽입 이미지

먼저 좌우로 static이냐 dynamic이냐 하는 속성이 있다.
변수의 type이 소스 코드에 미리 명시되어서 빌드 때 완전히 붙박이로 고정되는 건 static이다. 정수에는 정수만 집어넣을 수 있고, 문자열에는 문자열만 집어넣을 수 있다.

int a;
string b;
a = 100;
b = "Hello world!!";

그 반면, dynamic은 한번 변수를 선언했으면 거기에 아무 형태의 값이나 집어넣을 수 있다.

var a;
a = 100;
a = "Hello world!!";

우리가 접하는 '가벼운, 인터프리터' 성향의 프로그래밍 언어들은 dynamic type이다. 그러나 exe/dll 따위를 생성할 때 쓰이는 기계어 직통 컴파일 성향의 '무거운' 언어들은 대체로 static type인 편이다.

dynamic은 사람의 입장에서 입문과 코딩이 용이하다. 그러나 코드의 실행 성능은 타입을 꼼꼼히 지정해 주고 이 범위를 벗어나지 않는 static이 훨씬 더 뛰어나다. 코드의 양이 수백, 수천만 줄을 넘어갈 때의 유지보수 난이도과 총체적인 생산성도 static이 더 낫다.

둘의 차이는 똑같이 표 형태의 데이터를 입력하는데 엑셀(스프레드시트)과 전문 데이터베이스의 차이와 비슷하다.
엑셀은 아무 셀에나 아무 값을(숫자, 문자열, 날짜 시간 등..) 아주 자유롭고 편하게 입력할 수 있는 반면, DB는 각 셀별로 들어갈 수 있는 자료형과 크기를 정말 딱딱하게 미리 정해 놓고 그걸 지켜야 한다.

그러나 그 상태로 데이터의 개수가 수백· 수천만 개에 달하면? 데이터를 원하는 대로 검색하고 정렬하고 한꺼번에 변형하는 성능은 스프레드시트가 DB를 절대로 범접할 수 없을 것이다. 유도리, 자유도 같은 건 성능하고는 아무래도 상극이고 등가교환 관계일 수밖에 없다.

하지만 static 언어라 해도 타입이 뻔한 문맥에서 타입 명칭을 일일이 써 주는 건 귀찮고 번거롭다. 특히 변수를 선언과 함께 초기화할 때 말이다. 대입하려는 우변의 값에 타입을 암시하는 정보가 어지간해서는 이미 포함돼 있기 때문이다.

그렇기 때문에 C++에서는 auto라는 파격적인 키워드가 도입돼서 변수 자체의 타입은 static하게 결정되더라도 최소한 int, string 같은 타입명을 번거롭게 쓸 필요는 없게 하고 있다.
또, 템플릿 메타프로그래밍이니 제네릭이니 하는 것을 도입해서 static type 언어이더라도 한 코드를 다양한 type에 대해서 범용적으로 활용 가능하게 해 놓았다. dynamic type 언어라면 저런 물건이 태생적으로 존재할 필요가 전혀 없을 것이다.

함수를 호출할 때는 보통은 값을 인자로 넘기고 값을 리턴값으로 받는다. 그런데 저런 패러다임 하에서는 함수를 호출하거나 클래스의 인스턴스를 선언하면서 타입까지도 인자로 넘기게 된다. 물론 이건 여느 함수 인자와는 성격이 많이 다르기 때문에 통상적인 괄호가 아닌 < >로 감싸고 전달하는 위치도 따로 구분돼 있다.

부등호로만 쓰이던 이항 연산자 < >가 여닫는 괄호처럼 쓰이니 이건 굉장한 발상의 전환이다. 이제는 소스 코드의 파싱도 마냥 단순무식이 아니라 주변 문맥을 의식하면서 해야 하게 됐다.

외형은 비슷해 보여도 C++의 템플릿은 C#/Java 같은 언어들의 제네릭과는 성격이 완전히 극과 극으로 다른 물건이라는 것이 주지의 사실이다. C++ 템플릿이 제네릭보다 자유도가 더 높고 화끈=_=하기는 하지만.. 이건 템플릿의 소스를 몽땅 까고, 서로 다른 템플릿 인자에 대해서 컴파일과 코드 생성이 매번 다시 행해지는 무식한 댓가를 치르는 덕분에 제공되는 장점이다.;;;

참고로 값과 타입에 이어서 { }로 감싸는 함수 몸체 자체까지 함수의 인자와 리턴값으로 마구 주고받을 수 있는 건 그 이름도 유명한 함수형 패러다임이 된다. 이게 제일 나중에 도입돼 있다.

자, static과 dynamic 타입에 대한 소개는 이 정도로 된 듯하고, 다음으로 상하 세로축을 살펴보자.
strong이냐 weak냐 하는 속성은 type safety에 관한 것이다.
서로 관련이 없는 타입의 값끼리 형변환을 알아서 쓰윽 해 주고 위험한 형변환도 별 탈 없이 허용하는 편이면 type safety가 weak인 것이다.

그렇지 않고 뭐 하나 하려면 깐깐하게 형변환 함수를 수동으로 매번 호출해야 한다면, 타입 관련 오류는 대부분 컴파일 때 다 걸러지고 런타임 때 딱히 문제가 발생할 일이 없다면 그런 언어는 strong이다. 단적인 예로,

a = 200 + "abc";

이런 구문을 알아서 "200abc"라고 접수해 주면 weak이고, 숫자와 문자열을 한데 섞을 수 없다고 까칠하게 에러를 내뱉으면 strong인 편이다.
그러면 static인 언어가 strong인 편이고 dynamic인 언어가 weak가 아니겠냐고 편견을 가질 수 있지만.. 실제로는 꼭 그렇지 않다.

같은 dynamic type 언어 중에서도 Visual Basic, JavaScript, 문자열의 유연한 조작에 특화된 Pearl, 그리고 PHP..;; 같은 언어들은 weak로 분류된다.
그 반면, 파이썬은 dynamic type 언어이지만 strong이라고 여겨진다. 둘은 아까 정치 성향과 마찬가지로 서로 별개의 개념이다.

특히 C/C++은 static이면서 weak인 매우 이례적인 언어이다. 이 범주에 드는 언어 자체가 사실상 얘들밖에 없다.
타입 시스템이 static인 것이야 의심의 여지가 없는데, C는 그에 덧붙여 type safety가 굉장히 개판이고 안전 장치가 빈약하기 때문이다.

숫자에서는 enum과 int를 제멋대로 섞어 써도 아무 문제가 없는 것, 0이 포인터와 정수에서 모두 통용되는 것, bool과 숫자의 구분도 없는 것, 관련 없는 타입의 포인터끼리의 대입이 굉장히 관대한 것, 타입의 통제 따위는 전혀 받지 않는 무식한 memcpy와 malloc이라든가 매크로 함수..;; 그리고 부동소수점 숫자의 내부 구조까지 뜯어볼 수 있는 공용체와 비트필드는 C/C++ 말고 도대체 어느 언어에서 찾아볼 수 있을까???

그나마 C++에 와서 무질서도가 눈꼽만치 개선됐다. explicit와 enum class도 도입되고 true/false 상수라든가 nullptr도 도입되면서 type safety를 강화하려고 애쓰는 중이다. 하지만 C++의 type safety는 Java나 C#에 비할 바는 못 된다고 여겨진다.

현대의 언어들은 static/dynamic이야 언어의 취향과 용도에 따라 달라지지만 type safety에 대해서는 strong을 추구하는 쪽으로 바뀌는 추세이다. weak인 언어는 당장 표현은 간결하게 할 수 있고 자유도가 더 높지만.. 안전하다는 보장이 없기 때문이다. 방대한 코드에서 갑자기 버그· 오류가 발생했을 때 지뢰가 어디에 숨어 있는지를 알기가 너무 어려워진다.
따지고 보면 제네릭이 도입된 것도 무식한 void*나 Object 떡칠만 하는 것보다 더 안전하게 코드를 작성하기 위해서이다.

Posted by 사무엘

2022/12/26 08:35 2022/12/26 08:35
, ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/2106

Windows는 태생적으로 ‘유니코드 = 2바이트 단위 인코딩’이라는 걸 전제에 깔고 만들어졌다.
거기에다 유니코드라는 게 없던 쌍팔년도 도스 시절과의 호환성을 너무 중요시해서 그런지, 2바이트가 아닌 1바이트 단위 인코딩 쪽은 일명 ANSI라 불리는 국가별 지역구 문자 코드에 오랫동안 얽매여 있었다. (cp949 따위)

그래서 이쪽 진영은 ‘유니코드의 1바이트 단위 인코딩’에 속하는 UTF-8의 지원이 맥이나 리눅스 같은 타 운영체제에 비해 굉장히 미흡한 편이었다.
가령, 파일의 경우 앞에 BOM을 꼭 넣어야만 ANSI가 아닌 UTF-8이라고 인식했는데.. 그러면 이건 말짱 도루묵이어서 지원하지 않는 것과 별 차이 없었다.

이러니 한 git 저장소에다가 넣고 여러 플랫폼에서 공통으로 사용하는 소스 파일의 경우, 영문이 아닌 한글로 주석은 무서워서 넣지도 못할 지경이었다.
Windows만 ANSI cp949를 선호하니 이건 타 운영체제의 IDE에서는 인코딩을 번거롭게 수동 지정하지 않는 한, 제대로 인식을 못 했다. 거기서 다시 저장을 하면 한글 내용은 당연히 다 날아갔다.

Windows에서도 UTF-8로 인식시키려면 파일 앞에다 BOM을 집어넣어야 하는데, 이러면 Windows 말고 타 컴파일러에서는 이게 배탈을 일으켰다.
정말 거지 같은 상황이었다. Windows는 1993년 NT 첫 버전부터 나름 유니코드를 염두에 두고 설계된 물건임에도 불구하고, 이런 분야에서는 전혀 유니코드에 친화적이라는 티가 느껴지지 않았다.

무려 2010년대 중후반이 돼서야 Visual C++ 2017인가 2019쯤에서야 드디어 BOM이 있건 없건 소스 파일의 인코딩을 다 UTF-8로 인식시키는 옵션이 추가됐다. 아마 202x 버전쯤에서는 이게 디폴트 옵션이 돼야 할 것이다.
그리고 언제부턴가 메모장이 편집하는 파일의 기본 저장 인코딩이 ANSI 대신 UTF-8로 바뀌었다.

응용 프로그램뿐만 아니라 Windows 자체도 10의 후대 패치를 통해 일단 명령 프롬프트의 인코딩에 UTF-8 지정이 가능해졌다. CHCP 65001 말이다.
단, 이런 명령 말고 프로그램 상으로 UTF-8 기반의 명령 프롬프트 환경을 어떻게 생성하는지는 잘 모르겠다. 검색해 보면 있겠지.. 배치 파일과 명령 argument를 몽땅 다 유니코드로 줄 수 있어야 진정한 유니코드화일 텐데 말이다.

다음으로 2019년쯤엔가 굉장히 큰 변화가 생겼는데..
유니코드를 지원하지 않는 구닥다리요 과거 Windows 9x의 잔재로나 여겨지던 각종 ...A 함수 말이다.
A 함수도 ANSI가 아닌 UTF-8 인코딩으로 문자열을 취급함으로써 유니코드를 지원하게 하는 통로가 뚫렸다.
그래.. 내가 원하던 게 이거였다. 진작에 좀 지원해 줄 것이지..!!

물론 Windows가 내부적으로는 문자열을 몽땅 UTF-16 방식으로 처리하고 있고, 2000년대부터는 ..A 함수 같은 건 만들지도 않는다. 그러니 ..A 함수의 유니코드화가 막 획기적으로 대단한 일은 아닐 것이다.
그러나 이렇게 해 주면 1바이트 단위로 문자열을 취급하는 각종 오픈소스 라이브러리에 대해서 골치 아프게 문자열을 변환하고 W 함수를 호출하는 thunk를 만들지 않아도 유니코드 파일명에 접근할 수 있어서 기존 코드의 포팅이 굉장히 수월해진다.

이 ANSI 코드 페이지라는 개념은 원래 시스템 global한 설정이며, 변경한 뒤에는 재부팅이 필요할 정도로 보수적인 속성이었다.
그런데 이걸 응용 프로그램마다 샌드박스를 씌워서 다른 값으로 가상화할 수 있고 심지어 UTF-8로 지정 가능해진 것은 고해상도 DPI 설정과 양상이 굉장히 비슷하다. 이것도 시스템 global이다가 응용 프로그램 단위로, 심지어 모니터 단위로 세부 지정과 변경이 가능해졌기 때문이다.

응용 프로그램의 매니페스트 정보를 통해 지정한다는 점마저도 동일하다. application → windowsSettings에 있다~!

<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>

20여 년 전에는 마소에서 unicows라고, 응용 프로그램이 Windows 9x에서 ...W 함수를 호출하면 문자열들을 변환해서 A 함수로 재호출해 주는 호환 layer를 개발· 배포한 적이 있었다. 한 프로그램이 2000/XP에서는 유니코드를 지원하고, 9x에서는 유니코드를 지원하지 않아도 기본적인 실행만은 되라고 말이다.
이제는 A 함수로도 UTF8 인코딩을 통해 유니코드에 접근하는 통로가 생겼다니, 참 오래 살고 볼 일이다.

또한, 이렇게 세월이 흐르면 Windows에서도 2바이트 완성형 CP949는 2바이트 조합형만큼이나 점점 보기 힘들어지고 역사 속으로 사라지지 싶다. 마치 플래시나 IE6, 보안이 안 좋은 http가 퇴출되듯이 말이다.
Windows가 일찍부터 유니코드를 지원했다고는 하지만 실질적으로 재래식 1바이트 인코딩의 퇴출을 가능하게 한 것은 UTF-8의 도입이라고 봐야 할 것이다.

한편, 웹이야 살아 있는 프로그램이 아니라 문서이니.. EUC-KR이니 CP949이 더 오래 남아 있을 것이다. 그러고 보니 내 홈페이지부터가 블로그 말고 HTML 페이지는 다 구닥다리 ANSI 인코딩을 쓰고 있구나. =_=

※ 여담: 2바이트 인코딩의 문자 집합 크기

우리나라의 KS X 1001 완성형 2바이트 한글 코드는 ISO/IEC 2022라는 옛날 규격에 맞춰서 94*94 = 8836 크기의 격자 안에 완성형 한글 2350자와 상용 한자 4888자, 그리고 나머지 1000여 자에 달하는 특수문자를 배당해 놓았다.

그 뒤 CP949, 일명 마소 확장완성형은 현대 한글 11172자에서 2350자를 제외한 나머지 한글 8822자를 KS X 1001이 사용하지 않는 2바이트 문자 조합에다가 억지로 집어넣었다.
KS X 1001이 lead byte와 tail byte 공히 0xA1부터 0xFE까지만을 사용하는 반면, CP949는 영역이 더 넓다. 특히 tail byte로는 알파벳 A~Z, a~z까지 사용한다.

그런데 이 ISO/IEC 2022 격자 크기 8836과, 비완성형 한글 수 8822는 값이 놀라울정도로 비슷하다. 우연인지, 의도된 결과인지 모르겠다.;;
한글 글자 수 11172와, 16*16픽셀 8*4*4벌 도깨비 한글 폰트의 크기 11520도 꽤 비슷하게 느껴진다. 이건 진짜로 의미가 서로 전혀 무관하기는 하다만 말이다.

Posted by 사무엘

2022/08/13 08:35 2022/08/13 08:35
, , ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/2054

1. 성능과 알고리즘

(1) 현실의 퀵 정렬 알고리즘 구현체는 구간의 크기가 일정 기준 이하로 작아지면 그냥 O(n^2) 복잡도의 단순한 삽입 정렬로 대체하곤 한다. 그게 더 효율적이기 때문이다.

(2) 균형 잡힌 트리는 삽입, 탐색, 삭제가 모두 O(log n)의 복잡도로 되는 매우 유용한 자료구조이다. 그렇기 때문에 단순히 메모리 레벨의 set이나 map 컨테이너뿐만 아니라 파일 시스템이나 DB 같은 디스크 레벨에서도 쓰인다.
요즘 아무렇게나 DIR을 해도 파일 목록이 언제나 ABC 순으로 정렬되어 출력되는 이유는.. NTFS 파일 시스템이 내부적으로 이런 트리 구조를 사용하기 때문이다. (반면, 과거의 재래식 FAT는 연결 리스트 기반이어서 파일 목록의 정렬이 보장되지 않음)

단, 디스크 레벨에서는 단순한 이진 나무가 아니라, 이를 변형하여 한 노드에 딸린 자식이 좀 더 많은 B+ 같은 트리 구조가 쓰인다. 왜냐하면 디스크는 메모리보다 입출력 속도가 훨씬 더 느리며 랜덤 지점 탐색에 취약하기 때문이다.
그래서 한 노드 안에서 선형 검색을 좀 더 하더라도, 노드 하나를 탐색하고 읽는 횟수를 줄이는 게 더 이득이다. 다만, 이런 이념도 재래식 하드디스크가 아니라 플래시 메모리에서는 유효하지 않을 수 있다.

(3) 한 번에 한 스레드만 접근 가능해야 하는 코드가 있다면 보통 그 구간을 critical section이나 뮤텍스 따위로 둘러싼다.
그런데 이것도 "어? 다른 스레드가 이미 들어가 있네? 그럼 우리는 닥치고 바로 대기".. 이렇게 단순무식하게 하는 것보다,
loop을 돌면서 busy waiting, polling, spin lock을 n번만 더 시도해 보고 "그래도 여전히 다른 스레드가 나가지 않았으면 그때 대기 타자" 이런 유도리 전략이 좀 더 효율적일 때가 있다.

왜? 대기를 탔다가 깨어나는 작업 자체가 사용자 모드에서 커널 모드로 들어갔다가 나오는 것이며, 수천 사이클에 달하는 CPU 오버헤드를 요구하기 때문이다. 대기하고 있는 스레드는 CPU를 먹지 않지만, 대기 상태로 들어가거나 깨어나는 출입 과정은 공짜가 아닌 것이다.

더구나 요즘 컴퓨터는 코어가 여럿 있기 때문에 한 스레드에서 아주 잠깐 무식한 busy waiting을 하더라도 그게 타 스레드의 실행 성능에 영향을 주지 않는다. 그럴수록 대기 진입을 한 템포 늦춰서 신중하게 하는 게 가성비가 더 커진다.

일상 생활에다 비유하자면, 여러 잡다한 물건을 들고 있어서 무거운 채로 엘리베이터나 버스를 기다리는 것과 비슷하다. 이걸 바닥에 완전히 내려놓아 버렸다면 팔이 힘들지는 않지만, 그걸 다시 집어드는 것도 굉장히 번거로운 일이 된다. 그러니 버스나 엘리베이터가 수 초 안으로 금방 온다면 그냥 그 물건들을 들고 기다리고 있는 게 더 낫다.

이렇듯, 컴퓨터에서는 성능을 최대화하기 위해 한 방법만으로 만족하지 않고, 상황에 따라.. 특히 아주 제한된 문맥에서는 통상적으로 비효율적이라고 알려진 무식한 방법까지도 동원한다는 걸 알 수 있다. 스타로 치면 여러 유닛을 조합하는 것과 같다.

2. 자원의 회수

식물은 죽어서 말라 비틀어진 잎· 줄기나 썩은 열매 따위의 처리가 아주 간편한 축에 든다. 땅에 파묻기만 하면 거름이 되고 도로 자연으로 돌아가고 구성 물질이 회수된다.
뭐, 동물도 궁극적으로 그렇게 되기는 한다. 하지만 사체가 분해되는 과정이 식물보다 훨씬 더 더럽고 끔찍하고 더 오래 걸리는 편이다.

이런 물질의 순환은 뭔가.. 가상 머신에서 GC에 의해 자동 관리되는 메모리 같다는 생각이 들지 않는지?
본격적으로 물질의 메모리 누수가 문제되기 시작한 건 인류가 자연이 제대로 감당하지 못하는 플라스틱 같은 고분자 화합물을 만들어서 쓰기 시작하고부터이다. 그리고 반감기가 끔찍하게 긴 방사능 물질도 이런 범주에 든다고 볼 수 있겠다.

뭐, 썩지 않는 물질이 다 문제이고 골칫거리는 아니다. 수도관 같은 건 절대로 부식되거나 썩지 않는 재료로 만들어서 수백, 수천 년은 써야 할 테니 말이다.

3. 코드

(1) 우리나라의 모든 법조문들이 몽땅 github에 올라오고, 전체 개정 이력을 Show log 명령을 통해서 조회하고 싶다는 생각이 든다. 전철 노선도 같은 물건도 마찬가지이다.

(2) 대학교 컴터공학과 학부에서 시스템 프로그래밍 시간에 MIPS 어셈블리어 갖고 깨작깨작 실습하는 건.. 육사에서 승마나 백병전 총검술 잠깐 맛보기 하는 것과 정확하게 대응하지 싶다~ ㅋㅋㅋ
학교에서 뭔가 C/C++, Java, Python 같은 실용적인(?) 언어 말고 뭔가 비현실적인 언어를 다뤄 보는 게 이렇게 어셈블리어 같은 레거시 계열, 아니면 엄청나게 순수한 이론 이상을 추구하는 함수형 언어 계열.. 이렇게 둘로 나뉘는 듯하다.

(3) 자동차 취급설명서는 소스 코드 곳곳에 들어서 있는 조건부 컴파일의 완벽한 예시로 보인다. * 표시가 돼 있는 각종 선택사양들.. 그리고 악보의 음표 위에 붙은 각종 나타냄말? 스타카토, 스타카티시모 이런 건 매크로의 예시이다.
악보는 각종 반복과 분기가 복잡하게 꼬이면 흐름이 진짜로 어지간한 프로그램 코드처럼 바뀌기도 한다.

(4) 성경에서 '주의 책', '(어린양의) 생명책' 같은 상상 속의 거대한 책이 언급된 걸 보면.. 예수 믿는 컴터쟁이들은 하늘나라에 있는 거대한 데이터베이스와 DB 서버 정도는 떠올릴 수 있을 것 같다.
물론 인간이 만든 컴퓨터는 신의 주요 성품 중 하나인 '무한, 영원'이라는 걸 절대로 구현하지 못하는 물건이다. 그러니 DB 드립은 마치 "김 성모 스타일의 성경 이야기"만큼이나 그냥 웃자고 늘어놓는 말일 뿐이다.

(5) 요한복음의 마지막 구절인 "이 세상이라도 예수님의 행적을 기록한 책들을 다 담지 못할 것이다"는 정보량과 관련된 언급이다. 그리고 삼손의 수수께끼 놀이는 정보 보호· 보안과 관련된 통찰을 주는 이야기이다.

4. 자동과 수동

요즘 수동 변속기 차량을 몰 줄 아는 사람이 갈수록 드물어지듯, 컴터 업계도 C/C++처럼 메모리를 수동으로 관리하는 저급 언어를 제대로 다룰 줄 아는 사람이 갈수록 드물어지는 것 같다.
직장에서 부사수로 들어온 어린 신입 개발자에게 사수가 메모리 leak이라는 개념을 알려주는 게 굉장히 뜻밖이고 놀라워 보였다.

하긴, 공대 1학년의 기초 필수 프로그래밍 과목에서 가르치는 언어도 초창기엔 C/파스칼이다가 나중에 Java를 거쳐 지금은 파이썬이지 않은가. 프로그래밍을 위한 전산학적인 소양하고, C나 컴퓨터 특유의 지저분한 감각이랄까, 이 둘이 영역이 완전히 일치하지는 않기 때문이다.

고깃집의 경우, 직원이 알아서 고기를 다 썰고 구워 주는 곳은 자동 변속기-_- 같고, 손님이 직접 고기를 얹고 굽고 자르고 뒤집어야 하는 곳은 수동=_=;;에 해당된다. 후자보다는 전자가 아무래도 마음 편하게 고기를 먹을 수 있지만.. 인건비가 추가되어 고기값이 더 비쌀 것이다.

5. 전체 리셋

컴퓨터 시스템을 날리는 방법으로 sudo rm -rf 라든가=_= Windows의 레지스트리 날리기, 시스템 디렉터리 날리기 같은 게 있다.
운영체제가 아닌 DB에서는 delete * 내지 drop table 같은 파괴적인 쿼리가 있다. 손가락 까딱 잘못 건드려서 회사 재산과 관련된 DB를 날렸다간 짤리는 정도를 넘어 손해 배상 소송을 당할 수도 있을 것이다.

그런데 국가로 치면.. 헌법 제1조가 바뀌거나 날아가는 게 그런 급의 파괴적인 사건일 것이다. 헌정 체제가 쿠데타로 인해 싹 뒤집히거나, 아니면 전쟁에서 지기라도 해서 외적이 자국 행정부를 완전히 접수했을 때에나 있을 수 있는 일이다.

우리나라의 경우, 옛날에는 "대한민국은 민주공화국이다" 같은 몇몇 조항은 개헌조차 아예 영원히 불가능한 조항으로 못 박으려는 시도가 있었다. 컴퓨터로 치면 운영체제의 작동과 직접적인 관련이 있는 일부 시스템 파일을 절대 변조· 삭제할 수 없게 특수하게 보호하는 것과 비슷하다고 하겠다(업데이트 받을 때만 빼고).

하지만 법리적으로 볼 때 그렇게까지 할 필요는 없기 때문에 개헌 불가 조항 같은 건 과거의 해프닝으로 끝났다. 그리고 지금 6공화국 헌법은 그렇잖아도 개헌이 너무 어려운 형태가 된 감이 좀 있다.;; 과거에 널뛰기 하듯이 수시로 개헌하던 관행을 없애고 싶었던 심정은 이해가 가지만 지금은 그것 때문에 미래까지 발목이 잡힌 것 같다.

6. C++ export와 우주왕복선

2000년대 초에.. EDG 같은 일부 C++ 컴파일러 개발사에서는 희대의 흑역사 표준 기능이던 export를 구현하느라 상상을 초월하는 삽질을 했던 거랑,
NASA에서 2003년의 컬럼비아 우주왕복선 사고 이후에 이제는 우주왕복선을 띄울 때마다 옆에 구조용 예비 기체까지 같이 대기시키면서 정말 눈물겨운 삽질을 잠시 했던 것..
둘이 시기도 비슷하고 심상이 뭔가 묘하게 비슷하게 느껴진다.

전자는 지금까지 C++ 표준에 새로 추가되었던 복잡한 기능들과는 구현 난이도가 차원이 달랐다. 기존 언어 구조의 근간을 다 뒤엎어야 하는 헬 수준이었는데, 그렇다고 템플릿의 모듈화를 제대로 실현해 주는 것도 아니었다. 이건 정말 백해무익에 가까운 미친 짓이었다. 결국 export는 2010년대 C++11에서는 완전히 삭제되었다.

우주왕복선에다가 구조 미션까지 추가한 것 역시.. 셔틀 한 대에다가 사람을 11명이나 태우는 것(기존 승무원 7 + 구조 요원 4), 안 그래도 3대밖에 없는 셔틀을 매번 2대나 세팅해야 하는 것, 묘기에 가까운 어렵고 위험한 기동으로 조난 당한 셔틀에 접근해서 사람을 구조하는 것..
살인적인 비용 대비 사람을 살릴 가능성도 별로 없는 미친 짓이었다. 다행히 이 미션이 실전에서 쓰인 적은 없었으며, 우주왕복선 역시 C++11과 비슷한 시기인 2011년에 완전히 퇴역했다.

7. 나머지

(1) 생물은 번식할 때 동물과 식물을 막론하고 가까운 혈통끼리 교배하지 말고, 최대한 먼 촌수끼리 다양하게 섞여서 교배해야 유전병 없이 건강한 후세가 태어나고 안전하다고 여겨진다. 유전적 다양성이란 게 중요하다.
이런 걸 뭔가 숫자의 특성으로 표현하면 해시값의 충돌이 안 나는 것, 셸 정렬이 빠르게 수행되는 간격 수열을 구하는 것(무식하게 2^n에서 절반씩 줄이는 건 최악), 퀵 정렬의 pivot 중간값을 적절하게 잘 고르는 것에 대응하는 것 같다.

(2) 자동차나 자전거 운전하다가 상대방과 부딪칠 것 같아서 한쪽으로 피하는데..
골때리게도 상대방도 내가 피하는 방향과 같은 방향으로 피하고, 이 상황을 탈피하지 못해서 결국 부딪히는 경우가 있을 수 있다.
이런 게 머신러닝이나 방정식 근 찾기에다 비유하자면 처음에 시작점을 잘못 잡고 학습을 잘못 시켜서 최적해로 수렴을 못 하고 삼천포로 빠진 것과 비슷해 보이는 상황이다. 아니면 데드락을 극복하지 못했거나.;;.

(3) 옛날, 1955년쯤에 중공의 마오 주석께서는 하늘을 향해 삿대질을 하며 "저 새는 해로운 새.." 아니, "참새는 해로운 새"라고 교시하시였다는데..
1968년쯤에 네덜란드의 전산학자 다익스트라는 ACM 저널을 통해 "GOTO Considered Harmful".. 즉, 스파게티 코딩이 해롭다고 저격했었다. 오늘날은 저 두 말투가 모두 밈..처럼 쓰이고 있다. ㅋㅋㅋ

Posted by 사무엘

2022/07/11 08:35 2022/07/11 08:35
,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/2041

1. 스마트포인터를 인식하지 못하는 버그

회사에서 이미 작성된 C++ 클래스 멤버 함수를 사용하고 싶어서 호출을 했는데.. 컴파일러인지 링커인지가 도무지 말귀를 알아듣질 못하고 unreferenced external symbol 링크 에러를 내뱉곤 했다. 매크로 치환, namespace 그 어떤 문제도 없는데 왜?
더 골때리는 건.. 같은 코드가 Windows에서 Visual C++은 아무 문제 없이 빌드되고, 안드로이드의 NDK 빌드 환경에서만 저런다는 것이었다.

그 함수는 첫째 인자의 타입이 FOO const&이었는데, FOO는 스마트 포인터 std::shared_ptr<BAR>의 typedef였다.
스마트 포인터를 왜 value로 전달하지 않고 또 레퍼런스로 전달했는지, 그 이유는 모르겠다. 이 코드를 처음에 내가 작성한 게 아니니까..

그런데 문제는 저 스마트 포인터를 그냥 날포인터 BAR*로 바꿔 주니까 링크 에러 없이 빌드가 됐으며, 프로그램도 양 플랫폼 다 별 문제 없이 돌아가기 시작했다는 것이다.
어느 경우건 -> 연산자를 쓰면 BAR 내용을 참조할 수 있으며, 몇몇 곳에서만 ptr 대신에 ptr.get()을 호출해 주면 됐다.

결국 이 문제의 원인은 안드로이드 쪽의 컴파일러 내지 링커의 버그이긴 한 것 같다. 하나만 고르라면 링커보다도 컴파일러의 문제인지도? 복잡한 type의 decoration string가 양쪽에서 서로 동일하게 생성되지 못했던 것으로 보인다.

2. 변수에도 extern "C" 구분이 필요한가

C++ 코드에서 다른 C 소스 파일에 정의된(C 소스로부터 빌드된 obj, lib도 포함) 함수를 참조해서 호출하려면.. 그 함수의 prototype이 extern "C" 형태로 선언되어야 한다.
C++은 오버로딩이라는 게 존재하기 때문에 C와 달리 함수를 이름만으로 유일하게 식별할 수 없으며, 인자들의 개수와 타입들도 명칭 decoration에 다 들어가야 하기 때문이다.

이건 상식 중의 상식이다. 그렇기 때문에 C언어 방식으로 만들어진 라이브러리는 헤더 파일이 중복 include guard뿐만 아니라

#ifdef __cplusplus
extern "C" {
#endif

(.....)

#ifdef __cplusplus
}
#endif

이렇게 관례적으로 감싸져 있기도 하다. C++ 코드에서 인클루드 되더라도 여기 함수들은 C++이 아닌 C 방식으로 링크 하라고 말이다.

그런데.. 난 함수뿐만 아니라 전역 변수도 이런 decoration 방식이 차이가 존재하며, 서로 일치해야 한다는 걸 요 근래에야 처음으로 알게 됐다.
C++이 C 코드에서 선언된 전역 변수를 참조하려면.. 역시 extern "C" int Global_in_C_code; 이렇게 해 줘야 된다. extern "C"를 생략하면 링크 에러가 난다..;;

헐 왜 그렇지..?? 변수는 언어 문법 차원에서 decoration이 전혀 필요해 보이지 않는데..?? Visual C++만 그런가?

그러고 보니 Visual C++은 함수를 C++ 형태로 decoration을 할 때 인자뿐만 아니라 리턴 타입까지 그 함수의 prototype의 모든 정보를 써 넣는다.
함수의 리턴 타입은 오버로딩 변별 요소가 아니기 때문에 "굳이 써 넣을 필요가 없음에도 불구하고" 그리한다는 것이다.

그런 것처럼 그냥 completeness 차원에서.. 나중에 미래에 혹시 필요할지도 모르니까 변수도 C++ 방식에서는 자신의 type까지 다 꼼꼼히 써 넣는 게 아닐까? 나로서는 이렇게밖에 생각되지 않는다.
예전에 C++에서는 const 전역 변수는 반드시 extern을 명시해 줘야 다른 번역 단위에서도 참조 가능해진다는 걸 알지 못해서 오랫동안 컴파일러/링커의 난독증을 의심하며 짜증 냈던 적이 있었는데.. 이것도 좀 비슷한 상황인 것 같다.

심지어 extern "C" 다음에 { }를 쳐서 C 방식의 외부 전역 변수 선언을 여러 개 하려면 중괄호 안에다가 extern을 또 써 줘야 된다. extern "C" { extern int x,y,z; } 처럼.

extern "C" { int x,y,z; }
이렇게 하면 x,y,z가 이 번역 단위 안에서 몸체가 직접 정의돼 버린다. 그렇기 때문에 unresolved symbol 대신, 명칭 중복 선언 충돌이라는 링크 에러가 날 수 있게 된다.

즉, 선언만 하고 마는 것은 중괄호와 함께 extern을 또 명시한 extern "C" { extern int x,y,z; } 이거 아니면..
그냥 extern "C" int x,y,z; 둘 중 한 형태라는 것이다. 어휴~ ㄲㄲㄲㄲ

3. 에러 안내

(1) 컴파일 에러는 컴파일러가 지적해 준 부분의 주변만 유심히 살펴보면 대체로 쉽게 해결 가능하다. 아주 복잡하게 꼬인 템플릿 코드에서 컴파일러가 뜬구름 잡는 난해한 소리만 늘어놓는다면 그건 상황이 다르지만, 그 정도로 극단적인 상황은 흔치 않다.
그 반면, 컴파일 에러보다 훨씬 더 무질서도가 높고 난해한 에러는 링커 에러일 것이다.

요즘 컴파일러는 명칭의 오타 때문에 에러가 나면 근처의 스펠링이 비슷한 변수· 함수를 제안까지 하면서 "혹시 이걸 의도하셨습니까?" / "혹시 뒤에 세미콜론을 빠뜨렸습니까?" 이런 안내를 할 정도로 똑똑해졌다.
링커도 "동일한 명칭이 C 방식으로는 존재하는데 혹시 extern "C"를 빠뜨렸습니까?" 정도의 유사 명칭 안내는 해 줘야 하지 않나 싶다.

(2) 아 하긴, C++ 템플릿은 그 자체만으로는 컴파일러가 문법 검사를 전혀 하지 않으며, 그 구조상 할 수도 없다.
템플릿에 인자가 주어져서 어떤 타입에 대한 실체가 생겼을 때에만 컴파일러가 그에 대한 코드를 생성할 수 있으며, 이때 비로소 문법 검사가 행해진다.

템플릿과 관련해서 발생하는 컴파일 에러는 뭔가.. 한 박자 다음에 발생한다는 점으로 인해 링커 에러처럼 더욱 난해한 구석이 있다.
템플릿 인자가 그 어떤 형태로 주어지더라도 무조건 발생할 수밖에 없는 컴파일 에러는 템플릿 자체의 코드만 보고도 컴파일러가 먼저 딱 잡아낼 수도 있으면 좋겠다만.. C++ 컴파일러 업계에서 그런 건 아직 신경을 안 쓰는가 보다. 메타프로그래밍이란 건 아무래도 추상화 수준이 높고 매우 난해한 기술이기도 하니 말이다.

4. 버전이 올라가면서 달라지는 C++ 컴파일러 동작

cmake라고 플랫폼별로 파편화돼 있는 개발툴 프로젝트/빌드 스크립트를 한데 통합해 주는 프로그램이 있다.
이건 분명 현실에서의 난해하고 복잡한 문제를 단순화시키고 해결하기 위해 만들어진 도구이겠지만.. 본인은 오픈소스나 크로스 플랫폼 같은 쪽으로는 인연이나 경험이 없다시피한 Windows 토박이에 Visual Studio 매니아이다 보니 얘를 다루는 게 참 난감하고 버겁게 느껴졌다.

회사에서 굉장한 구닥다리인 Visual Studio 2013을 오랫동안 쓰고 있어서 이걸 2019로 올리고, 플랫폼도 x86뿐만 아니라 x64도 추가하고 싶은데.. 그러려면 cmake 스크립트를 어떻게 바꿔야 하는지 알 길이 없었다.

나중에 알고 보니 cmake 자체도 버전업을 해야 했다. 그런데 VS가 2013이 없고 2019만 있을 때 발생하는 에러 메시지들이 그 근본 원인과는 전혀 관계 없는 엉뚱한 것들이어서 에러 메시지가 짚어 주는 부분만 뒤져서는 문제의 원인을 도무지 알 수 없었다.

cmake 따위 없이 Visual Studio 솔루션과 프로젝트 파일만 있었으면 이건 뭐 일도 아니었을 텐데 이런 것들이 cmake 스크립트가 좀 유연하지 못한 구석이 있는 것 같았다. 특정 Visual Studio 버전과 특정 타겟 아키텍처에 매인 비중이 크다. 뭐, 사실은 본인이 cmake 사용법을 잘 몰라서 삽질하는 것이겠지만..
cmake나 git 같은 빌드 관련 툴들은 학교에서 가르치기에는 너무 남사스럽고, 학원도 아니고.. 천상 스스로 독학하거나 직장에서 알음알음 배우는 수밖에 없나 모르겠다.

그리고 이렇게 컴파일러를 업글 하고 나면.. 기존 코드가 자잘하게 컴파일이 안 되는 부분이 꼭 발생하곤 한다. 그런 건 내 경험상.. C++이 갈수록 type safety가 강화되어서 더 까칠 엄격해지기 때문인 것 같다.
직장에서의 경험을 회고해 보자면, 이 클래스가 이 상태로는 vector, list, set 같은 컨테이너에 들어가지 않아서 에러가 나곤 했다. 2013에서는 됐는데 2019에서는 안 되는 것이다.

operator =의 인자가 T였던 것을 const T&로 바꾸고, 복사 생성자가 정의돼 있지 않던 것을 명시적으로 넣어 주고, 원래는 생성자에다가 U라는 타입 값을 넣으면 자동으로 형변환이 됐는데 이제는 되지 않아서 명시적으로 형변환을 하는 등.. 에러를 해결하는 방식이 다들 이런 식이었다.

Posted by 사무엘

2022/07/08 08:35 2022/07/08 08:35
,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/2040

1. 숫자를 표현하는 방식

20세기 중반에 컴퓨터가 아직 진공관 기반으로 만들어지던 시절에는 전기식이 아닌 전자식으로 바뀐 것뿐만 아니라 10진법 대신 순수 2진법을 사용하기 시작한 게 큰 전환점으로 여겨진다. 그게 더 기계 지향적이고 직관적인 설계이기 때문이다.

이건 사람으로 치면 별도의 교육을 통해 암산 때 머릿속에서 아라비아 숫자 대신 주판알을 떠올리는 것과 비슷하지 않을까 싶다. 아라비아 숫자는 문자로서 실용적인 기능도 겸하려다 보니, 숫자의 본질과 연산에 직관적으로 대응하는 체계가 아니기 때문이다.
심지어 주판법에는 선주법과 후주법이 모두 존재한다. 이건 컴퓨터에서 big/little endianness와 거의 동일한 개념인 것 같다.

2. 색공간과 실제 공간

우리가 사는 현실의 공간은 길이· 너비· 높이라는 xyz 세 축, 즉 3차원이라고 여겨진다.
그런데 우리에게 시각을 인지시켜 주는 색이라는 것도 어떤 형태로 축을 나누든.. RGB건 HSL이건 CMY건 결국 3개의 축으로 이뤄진다는 게 시사하는 바가 커 보인다.
가령, 색에서 hue라고 불리는 빨주노초~파남보 요소는 가시광선 파장의 차이라는 1차원 축으로 변별된다. 하지만 채도(S)와 명도(L)는 또 다른 차원의 변수라는 것이다.

컴퓨터의 그래픽 카드에서는 RGB 각 축에 대해 8비트의 정보량을 부여해서 총 2^24, 1600여 만 가지 색상을 제공하곤 하는데, 정작 1픽셀의 크기는 3바이트 24비트가 아니다. 컴퓨터가 처리하기 편한 단위인 4바이트 32비트 단위를 사용하며, 나머지 남는 8비트에다가는 픽셀의 알파 채널 정보를 넣곤 한다. 이건 여러 이미지를 부드럽게 합칠 때 활용된다.

알파 채널은 색깔을 나타내는 축 자체는 아니지만 색의 표현과 관계 있는 정보이다. 이걸 포함한 pixel format을 RGBA 구조라고 한다. 하지만 Windows의 GDI API는 1980년대에 개발되었으며, 픽셀에서 상위 8비트를 팔레트 등 독자적인 다른 용도로 이미 사용하다 보니 훗날 알파 채널을 제대로 지원하지 못하는 촌극이 벌어졌다. 그 역할은 GDI+ 등 후대의 API가 계승하게 됐다.

RGBA라는 개념은 물리학에서 XYZ 공간 세 축에다가 시간을 더한 XYZT 4차원과 뭔가 비슷하게 느껴진다.;; 그것도 기하학적 의미에서 정확한 4차원을 말하는 건 아니니 말이다. 하긴, 생각해 보니 3차원 컴퓨터그래픽에서는 픽셀마다 알파 채널이 아니라 Z buffer 값이 부가 정보로 들어가기도 한다.

3. 구 그리기

중고교 미술 시간에는 4B 연필 한 자루 들고 스케치북에다가 구를 그리는 데생(?) 실습을 해 보고.. 이과 나와서 컴공 전산을 전공한다면, 구 렌더링 정도는 C 코딩으로 저수준부터 뚝딱뚝딱 짜 봤으면 싶다.
둘이 매우 훌륭한 대조가 되리라 생각된다~! 후자의 경우, 구를 렌더링 하라고 openGL 셰이더 명령 한 줄 던져주고 끗~~이 아니라, 저 모든 픽셀의 RGB 값을 직접 계산해서 구하는 것을 말한다.

사용자 삽입 이미지

(본인이 직접 그리거나 생성한 그림이 아니니 오해하지 말 것! ㄲㄲㄲ)

이 픽셀이 구의 영역에 포함돼 있는지, 있다면 거리가 얼마나 되는지를 구의 방정식으로부터 구하고, 광원으로부터는 거리가 얼마나 되고 빛과 면이 접하는 각도가 어찌 되는지.. 최종적으로 밝기가 얼마가 돼야 하는지를 직접 공식 집어넣어서 계산으로 구한다는 뜻이다.

그림자까지 생각하면 일이 너무 어려워질지 모르니 필수가 아닌 옵션으로 남긴다만, 구 자체만이라도..;;
그럼 이 엄청난 계산을 실시간 애니메이션 수준으로 해내는 오늘날 PC와 폰의 그래픽 카드들이 얼마나 대단한 물건인지도 알 수 있을 것이다.

이런 이론 공부 잉여질 체험을 회사 취업한 뒤에 직장에서 할 수는 없을 것이고, 취업 목적 코딩 학원에서 할 수도 없을 것이다. 그러니 아직 학생일 때 학교에서 해야지...!!

4. AI

요즘 아시다시피 AI니 머신러닝이니 하는 분야가 아주 각광받고 있다. 현실에서의 문제의 목표와 input/output을 머신러닝 라이브러리가 이해하고 해결할 수 있는 형태로 변환하고, 데이터를 학습시키고 결과물을 얻는 건 확실히 학교에서 맛보기로나마 가르칠 필요가 있어 보인다.

자연어 처리라든가 영상에서 뭔가를 인식하기, ‘관련 추천 아이템 제시’ 같은 분야에서 요즘 AI들은 정말 눈부시게 똑똑해지고 기술이 발달해 있다.
개인적으로 좀 개발됐으면 하는 AI는 “문자열을 보고 폰트 종류 판별하기”, 그리고 “넓은 군중 사진을 보고는 여기에 사람이 몇 명이나 있나 추산하기”이다.

요즘은 AI를 통해 없는 정보를 유추해 내서 흑백 사진도 컬러로 얼추 복원하고, 흐릿한 영상을 선명하게 바꾸기도 한다. 그런 계산 능력이면 폰트 종류 유추는 말할 것도 없고, 이런 획이 요런 모양이었으니 다른 글자는 요런 모양이어야 하겠다는 것까지 유추를 못 할 이유가 없다. 그러면 한글이나 한자 같은 폰트를 만드는 일이 노가다가 줄어들고 한결 수월해질 것이다.

군중 사진에서 머릿수 카운트도.. 쉬울 것 같으면서도 은근히 어려울 수 있어 보인다. 하지만 기술적으로 불가능한 일은 절대 아닐 것이다. 이를 응용하면 사진에 찍힌 쌀알이나 콩알 개수를 세게 할 수도 있다.

지금 Google 검색은 영어는 정말 사람 말을 알아듣고 인간의 두뇌 활동을 어느 정도 흉내 내는 경지에 도달해 있다. 경악스럽게 그지없다. 유튜브 동영상에서 영어 자막을 자동 생성하는 걸 보면.. 어지간한 음성은 다 정확하게 알아듣는다.

여주인공이 격투를 벌이는 어느 첩보 영화의 제목을 까맣게 잊어버려서 “2017 female spy movie”라고만 쳤는데.. 우와, 저것만 토대로 Atomic Blonde라는 영화를 딱 정확하게 알아 맞히려면 도대체 저 영화의 특성을 어디까지 다 파악하고 있어야 되는 걸까..?
정말 외계인을 고문하는 기업이 아닐 수 없다.

꼭 인텔처럼 컴퓨터의 하드웨어 근간인 반도체의 본좌가 아니어도, 마소처럼 소프트웨어의 근간인 운영체제를 꽉 독점하고 있지 않아도 된다. 그 위에서 돌아가는 소프트웨어 내지 웹 서비스 중에서도 억 소리 나는 기술을 개발할 것들이 저렇게 넘쳐난다.;;

5. 암호 해독과 번역

난해한 수수께끼 암호를 풀기 위해 과거에는 언어학자가 동원되었다. 뭐, 보이니치 문서라든가 롱고롱고 문자, 로제타석처럼 인간이 만든 난해 정보를 해독할 때야 당연히 해당 지역의 고대 언어를 아는 것이 도움이 될 것이다.
하지만 현재의 군사 내지 보안 암호는 인간이 아닌 기계가 생성하다 보니 언어적인 요소가 전혀 동원되지 않으며, 오로지 수학자의 직관만이 필요하다. 2차 세계 대전 때 앨런 튜링이 독일군 에니그마 암호를 풀 때 딱히 독일어 지식이 쓰이지는 않은 것과 같은 이치이다.

기계번역도 이와 비슷한 맥락의 변화를 겪고 있다. 기계번역 시스템을 개발하는 데 입력 언어나 출력 언어의 전문가 내지 언어학자가 동원되지 않는다. 그냥 전산학자, 데이터 과학자, 머신 러닝 전문가가 동원된다.
취급하는 언어의 고유한 특성은 기계번역 시스템의 동작에 영향을 주지 않는다는 것이 한편으로는 굉장히 섬뜩한 점이다. 기계가 자연어든 암호문이든 언어 데이터를 취급하는 방식 자체가 근본적으로 바뀐 것이다.

6. 다중 상속

객체지향 패러다임에서 다중 상속이라는 걸 생각해 보자. 클래스가 기반 클래스를 하나만 두는 게 평범하고 일반적이고 권장되는 반면, 얘는 좀 특수한 상황에서 "논란과 무리수를 감수하고라도 둘 이상 갖는 것"이라는 특성이 있다.

이걸 인생에다가 투영해 보면 좀 뜬금없는 얘기지만 일부다처...;; 내지 복수 국적과 비슷한 것 같다.
C++에서 다중 상속을 지원해 봤는데.. 이건 좀 아니다 싶었는지 후대의 객체지향 언어들은 생짜 다중 상속은 금지하고, 데이터 멤버가 없는 인터페이스에 대해서만 복수 구현을 허용했다. class A extends B implements C,D,E처럼 말인데.. 이건 일부일처다첩-_-;;;처럼 들린다.

우리나라는 조선은 말할 것도 없고 일제 시대와 대한민국 초기에 이르기까지 오랫동안.. 일부다처체는 아니지만 첩이라는 게 관행적으로 있었다.
그러다가 1960년대, 박 정희 때 사회 구조를 대대적으로 뜯어고치면서 공무원들부터 첩을 두는 게 금지되었고(있으면 직장에서 징계=_=), 완전한 일부일처제가 자리잡았다.

이게 대놓고 불륜을 조장한다기보다는.. 전근대 시절엔 지금처럼 미혼 여성이 혼자 돈 벌고 사회 생활을 하는 게 도저히 가능하거나 용납되지 않았기 때문이다. 서로 필요하기 때문에 작은 마누라라는 게 존재했었다.

이런 결혼 말고 복수 국적도.. 나라마다 허용되는 정도가 케바케이고 우리나라는 징병제 병역 때문에 더 민감한 측면이 있다. 자기 원래 국적을 유지한 채로 외국의 영주권을 취득할 수는 있지만 완전히 시민권, 국적을 취득하는 건 또 별개의 문제가 된다.
우리나라의 경우, 이 대한민국 땅에 있을 때만은 한국 국적만 행사해야 한다는 각서를 쓴 뒤에 외국인의 복수 국적 취득을 허용한다.

국적 말고 이중학적, 이중인격 이런 건 명백하게 비정상일 것이다. =_=;;

Posted by 사무엘

2022/04/18 08:33 2022/04/18 08:33
, ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/2010

Windows 운영체제에서 제공하는 GUI용 컨트롤 중에는 애니메이션 컨트롤이라는 게 있다. 이것은 명령 버튼이나 에디트 컨트롤, 리스트 및 콤보 박스처럼 Windows 1.x 시절부터 있었던 완전 native가 아니고, 1990년대 중반에 운영체제가 32비트로 갈아 타던 95/NT 3.5 시기에 도입된 '공용 컨트롤'에 속한다. 즉, 도구모음줄, 리스트뷰 컨트롤, 트리 컨트롤, 진행 상황(progress) 표시 컨트롤, 슬라이더와 같은 급이다.

애니메이션 컨트롤은 컴퓨터가 무슨 작업을 하고 있을 때, 작업 중임을 간단한 '움짤'을 통해 사용자에게 시각적으로 피드백을 주는 역할을 한다. 즉, progress 컨트롤과 같이 쓰이는 경우가 많으며, 그 작업의 소요 시간이 굉장히 길거나 언제 끝날지 예측할 수 없는 상황일 때 애니메이션이 더욱 유용해진다.

게다가 애니메이션은 단순한 눈요기 이상으로 컴퓨터가 지금 내부적으로 하는 작업이 무슨 의미를 지니는지를 사용자에게 상징적으로 일깨워 주는 효과도 있다!

사용자 삽입 이미지

  • 탐색기에서 파일을 복사 중일 때 종이가 이쪽 서류가방에서 저쪽 서류가방으로 날아가는 모습
  • 삭제 중일 때 종이가 날아가면서 인수분해-_-되는 모습
  • 다운로드 중일 때 지구본에서 사용자의 컴퓨터로 종이가 날아가는 모습
  • 디스크 조각 모음을 실행할 때, 흩어졌던 건물 블록들이 짠~ 다시 한데 조립되는 모습

등이 좋은 예이다.
Windows 8 이후로 등장한 그 뱅글뱅글 돌아가는 동그라미들, 슉~ 중앙에 나타났다가 다시 슉~ 사라지는 동그라미들도 당연히 애니메이션에 속한다. 단, 얘들은 내부 작업의 의미를 시각화하는 건 없고 그냥 기하학적인 눈요기가 전부라 하겠다.

그럼 이 애니메이션 컨트롤은 어떤 형식의 파일을 사용할까?
컴퓨터 GUI에는 복잡한 코덱으로 디코딩해야 하는 전문적인 멀티미디어 동영상 말고, 그보다 가벼운 '움짤' 애니메이션 데이터라는 카테고리가 존재한다. 자동차에다 비유하면 버스보다 작은 승합차 정도에 대응할 것 같다.

  • 전문 동영상에 비해 파일 구조가 훨씬 더 단순하고, 프레임 크기는 작은 편이다.
  • 16/256색 같은 저색상도 지원한다. 저색상은 각 프레임을 무손실 압축으로 저장한다.
  • 오디오는 지원하지 않는다. 그 대신 투명색· 알파 채널을 지원한다. 영화 같은 전문 동영상에서는 이런 개념이 반대로 전혀 필요하지 않을 것이다.

카카오톡 이모티콘의 애니메이션이라든가 심지어 마우스 포인터의 애니메이션도 딱 이런 범주에 든다.
한때는 이런 움짤 저장용으로는 플래시(swf) 아니면 애니메이션 GIF가 널리 쓰였다. 그러나 플래시는 기능이 너무 많이 추가되면서 플레이어 런타임도 너무 무거워졌고.. 또 결정적으로 2010년대 중반부터는 완전히 퇴출됐다. gif야 뭐.. 256색의 한계를 벗어나지 못한 구닥다리일 뿐이고..

1990년대엔 오토데스크 사에서 개발한 flc/ fli라는 파일 포맷도 전문 동영상이라기보다는 애니메이션에 가까운 물건이었다. 심지어 Windows 매체 재생기의 초창기 버전이 재생을 지원하기도 했었다. 하지만 얘 역시 개인적으로는 실제 파일을 본 적이 전혀에 가까이 없으며, 소리소문 없이 듣보잡으로 전락하며 묻혔다.;;

Windows에서는 *.ani라고 애니메이션이 들어간 마우스 포인터 파일도 지원하긴 했지만.. 얘는 일반적인 비트맵이 아니라 아이콘에 대한 애니메이션이다 보니 담을 수 있는 그림에 대한 제약이 크다.
그러니 아주 오랜 세월이 지난 2010년대가 돼서야 png에다가 애니메이션이 추가된 apng, 그리고 jpg의 대체제로 개발된 webp에다가도 애니메이션이 추가된 Animated WebP가 뒤늦게 각광받는 중이다.

하지만 Windows 애니메이션 공용 컨트롤은 처음 도입되었던 1990년대 중반 이후로 시간이 완전히 정지한 채 시대에 너무 뒤쳐져 있다. 저런 최신 기술들을 전혀 지원하지 않고 오로지 avi만 지원하는데.. 제~~~일 단순하고 원시적인 run-length (RLE) 방식으로 압축된 256색 이하의 색상 영상만을 받아들인다.

얘의 디코딩 난이도를 이미지 파일 포맷에다 비유하자면, GIF에도 못 미치고 지금은 역사 속으로 사라진 PCX급밖에 되지 않는다.
저색상 기반답게 color key 기반으로 투명색 처리도 지원하긴 하지만.. 그럴 거면 gif라도 좀 지원할 것이지 하는 아쉬움이 남는다.

애니메이션 컨트롤은 왜 이렇게 허접하게 설계된 걸까? 전문적인 동영상 재생을 목적으로 만들어진 게 아니며, 탐색기에 들어가는 자그마한 애니메이션을 재생할 정도로만 극도로 최소주의 최적화 정신에 입각하여 기능이 구현됐기 때문이다.

1994~95년이면 모자이크나 넷스케이프 같은 WWW 기반 그래픽 웹브라우저가 이제 막 만들어졌던 시절이고, 386~486에 램 겨우 4~8MB급 컴퓨터로는 JPG는커녕 GIF 디코더를 돌리는 것도 다소 부담스러웠었다. 또한 그림판조차 BMP와 PCX 이외의 파일 포맷은 읽고 쓰는 걸 지원하지 않았었는데 GIF를 운영체제의 공용 컨트롤이 지원할 거라고는 전혀 기대할 수 없을 것이다.

물론 그땐 그랬다 치지만 지금까지도 애니메이션 컨트롤이 너무 빈약한 것은 변명의 여지가 없다고 하겠다. 그래서 이제는 탐색기 같은 운영체제 셸조차 애니메이션 컨트롤을 사용하지 않고 있다. 공용 컨트롤이란 게 원래는 셸에서 쓰던 물건을 보편적인 컴포넌트로 확장한 것이었는데 이건 참 아이러니한 현상이 아닐 수 없다.

요즘이야 탐색기에서 파일을 복사할 때는 전송 속도 그래프가 종전의 애니메이션을 대신하고 있다. 하지만 저 그림에서 보듯, Vista인가 7까지만 해도, 뭔가 서류 갈은 게 복사본이 짠~ 생기는 걸 형상화한 애니메이션이 떴었다. 파일을 삭제할 때도 비슷한 컨셉의 애니메이션을 볼 수 있었다.

그런 것들은 딱 봐도 알겠지만 Windows 9x 시절 같은 16~256컬러 나부랭이의 단순한 애니메이션이 아니다. 그리고 그건 애니메이션 공용 컨트롤로 재생하는 게 아니라는 것이다.;;
파일 내용을 표시하는 제일 중요한 부분조차 Windows 7의 탐색기부터는 리스트뷰 컨트롤을 사용하지 않는 것처럼 말이다.

(단, Spy++로 확인해 보면, 트리 컨트롤은 여전히 사용하고 있음. 외형을 많이 마개조해서 말이다.
반대로 Visual Studio는 먼 옛날 6.0 시절부터 지금까지.. 프로젝트/리소스 view에서 트리 컨트롤을 사용한 적이 없었다. 단적인 예로 트리 구조에서 ctrl+클릭으로 multiple selection이 동작하는 건 공용 컨트롤에서 전혀 지원되지 않는 기능이다. 흥미로운 사실이다.)

이상이다.
Windows 95 이후로 지금은 셸의 GUI와 공용 컨트롤 사이의 격차가 너무 많이 벌어져 있다.
9x 시절엔 작업 표시줄(taskbar)에 표시되는 각종 프로그램들 제목이 '탭 컨트롤'로 구현돼 있었다는 거 아는 분 계시려나.. 하지만 얼마 못 가.. 아무리 늦게 잡아도 XP때부터는 뭐 없이 당연히 자체 구현으로 바뀌었다.

Windows 10부터는 절대 안 바뀔 것 같은 메모장도 큰 파일의 로딩 속도가 획기적으로 개선됐고, \n 같은 줄 바꿈 문자 처리도 개선됐다.
에디트 컨트롤 같은 그 극도의 고인물 썩은물 코드도 마소에서 마음만 먹으면 개선될 수 있다. 그런 것처럼 시대 추세의 변화에 따라 애니메이션 컨트롤도 좀 개선이 됐으면 좋겠다는 게 개인적인 생각이다.

애니메이션 컨트롤과 관련된 기술적인 여담을 몇 가지 늘어놓으며 글을 맺도록 하겠다.

(1) 비트맵, 아이콘 따위는 응용 프로그램에서 자주 쓰이는 물건이다 보니 RT_BITMAP, RT_GROUP_ICON 같은 번호 기반의 표준 리소스 포맷도 있다. 그러나 애니메이션은 쓰이는 빈도가 압도적으로 낮다 보니 표준 리소스 포맷 번호가 제정돼 있지 않고, 그냥 "AVI"라는 포맷 문자열만 제정돼 있다. 거의 폰트(RT_FONT) 급으로 마이너하지 싶은데 말이다.

(2) 애니메이션 컨트롤은 전통적으로 백그라운드 스레드를 사용해서 애니메이션을 출력했는가 보다. 그리하지 않고 UI 스레드와 동일한 스레드에서 타이머만 사용해서 출력하게 하는 옵션은 ACS_TIMER라고 따로 있었는데..
Windows XP에서 도입된 공용 컨트롤 6에 들어간 애니메이션 컨트롤은 스레드 기능이 완전히 삭제되고 언제나 타이머 기반으로만 동작하게 됐다.

뭐, 애니메이션을 출력할 만한 상황이라면 작업은 어차피 백그라운드 스레드에서 진행되고 있을 것이고, UI 스레드는 당연히 살아 있어야 한다. UI 스레드가 응답 없이 block돼 있는데 애니메이션 컨트롤이 별도의 스레드로 혼자 살아서 UI 쪽에 접근하면.. 응답을 못 받고 같이 멎어 버리는 deadlock에 빠질 것이다.
애니메이션 컨트롤은 성능과 안정성 같은 요인을 감안해서 멀티스레드 기능을 빼 버린 것으로 보인다.

(3) progress 컨트롤은 공용 컨트롤 6 시절부터 marquee 애니메이션을 출력하는 기능이 추가됐다. 즉, 전체 작업량을 예측할 수 없어서 기약 없이 기다려야 할 때.. 프로그램이 작업 중이고 뻗지 않았다는 사실만 알려주는 뱅글뱅글 애니메이션 말이다.
요것만으로도 별도의 애니메이션 컨트롤을 사용해야 할 필요를 많이 줄여 주긴 했다. 완전히 대체해 버린 건 당연히 아니지만 말이다.

Posted by 사무엘

2022/04/04 08:35 2022/04/04 08:35
,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/2005

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

블로그 이미지

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

- 사무엘

Archives

Authors

  1. 사무엘

Calendar

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

Site Stats

Total hits:
2676723
Today:
1291
Yesterday:
2124