« Previous : 1 : ... 142 : 143 : 144 : 145 : 146 : 147 : 148 : 149 : 150 : ... 221 : Next »

페르시아의 왕자는 개발된 지 벌써 25년이 돼 가지만 아직도 기억하는 사람들이 있는 불멸의 명작 고전 게임이다.
개발자의 이름을 가히 전세계적으로 알린 물건이다. 로토스코핑 기법으로 주인공의 부드러운 움직임을 그 옛날에 만들어 냈다는 걸 생각하면 그 천재성에 감탄을 금할 수 없다.

게임 엔진도 충분히 기술적으로 뛰어나지만, 제작자인 Jordan Mechner는 방송· 연출 쪽으로도 조예가 있는 사람이다 보니 게임을 마치 한 편의 영화처럼 굉장히 웅장하게 만들고 어떤 사건이 발생했을 때 돌아오는 시청각 피드백의 디자인에 세심한 신경을 썼으며, 스토리를 탄탄하게 짜 넣은 것도 인상적이다.

가령, 적이나 주인공이 죽었을 때 매번 짤막한 멜로디가 나오는 게임, 적이 칼에 맞았을 때와 내가 칼에 맞았을 때의 소리가 서로 다른 게임은 지금 생각해 봐도 흔치 않다. (당연히, 내가 칼에 맞았을 때의 소리가 더 불쾌하고 위급하게 들린다. 이런 것까지 다 신경 썼다)
또한, 같은 엔진으로 천편일률적인 디자인의 맵만 집어넣은 게 아니라, 중간에 해골이라든가 영혼 탈출, 그리고 공주가 생쥐를 보내서 왕자를 구출하는 것 같은 이벤트도 집어넣어서 사용자가 지루하지 않게 배려했다. 괜히 명작 소리를 듣는 게 아니다.

제작자는 네이티브 뉴요커이며 예일대 출신이라는 건 덤이다. 페르시아의 왕자는 20대 중반의 나이로 그가 대학을 졸업한 거의 직후부터 개발을 시작한 것이지만 대학 재학 중에도 프로그래밍을 안 한 것은 아니다. 컴공 전공자도 아닌데 말이다.
게다가 오리지널 1편 기준으로 모션 촬영은 제작자의 동생을 뛰고 오르고 이리저리 구르게 하면서 촬영했으며, 음악은 제작자의 아버지가 만들었다 하니 이 정도면 가히 엄친아 집안이 따로 없다. 아버지 Francis Mechner는 심리학 박사 학위 소지자이다.;;

이 게임은 1989년에 최초로 애플 II 용으로 개발되었지만 이듬해에 PC용으로 이식되면서 대박을 터뜨렸으며, 여타 PC나 게임기용으로도 널리 이식되었다. 오늘날에는 3D 형태로 리메이크된 작품이 나오고 심지어 모바일용으로 이식되기도 했다.
어디 그 뿐인가? 게임 mechanic이 간단하다 보니 플래시로도 비스무리한 게임이 있다. 그리고 전세계의 양덕후들이 LEVELS.DAT 파일을 임의로 고친 custom level들까지 즐비하다. 이 정도면 페르시아의 왕자가 비디오 게임계에 끼친 영향은 가히 지존의 경지라 하겠다.

어휴, 말이 길어졌는데.. 이 글에서 소개하려 하는 것은 그 페르시아의 왕자 1 PC 버전에서 MEGAHIT 치트만 안 쓰고 프로그램 상의 모든 버그와 꼼수를 활용한 궁극의 타임어택이다.
어지간한 고퀄이 아니었으면 내가 굳이 내 블로그에다가 소개까지 하지도 않았을 것이다.
정말 프레임 단위로 삽질 없이 시간을 아껴 쓰려는 노력을 읽을 수 있었다.

http://www.youtube.com/watch?v=ZvlNppHraWs
http://www.youtube.com/watch?v=U8Kw2pA6hb8

레벨 1: 게임 시작 직후에 왕자가 엎드렸을 때 음악과 함께 살짝 랙이 있으니 이것은 Ctrl+A로 곧바로 스킵. (경악) 꿈에도 생각을 못 했다.
그리고 이 레벨의 경우, 정석대로 칼을 먹고 돌아오면 깨는 데 2분이 걸리지만, 잘 알다시피 칼 든 악당을 꾀어낸 뒤 칼을 먹지 않고 깨면 1분대로 시간이 줄어든다.
그러나 저 고수의 플레이 영상은 그마저도 초월했다. 칼을 먹으러 가지 않을 뿐만 아니라 일체의 우회가 없이, 칼 든 악당을 그대로 정면돌파하여 통과한다. -_-;; 이로써 1분도 걸리지 않고 깬다. 프로그램의 미묘한 버그를 활용해서 말이다. 더 설명이 필요하지 않으니 영상을 직접 보시라.

레벨 2와 3은 그냥 그럭저럭 보면 되고...

레벨 4: 포인트는 시작 후에 오른쪽 방에 있는 악당을 움직이게 하여 닫힌 문을 곧바로 열게 만드는 것이다. 버그 활용임.
출구가 있는 방에는 악당이 있다. 출구를 개방한 뒤에 다시 이쪽으로 돌아와야 하기 때문에, 여기 있는 악당은 시간 소요를 감수하고라도 보통은 어쩔 수 없이 싸워서 죽이는 것이 정석이다. 그러나 이 사람은 그 악당마저도 죽이지 않고 매번 도망쳐서 통과한다.

레벨 5: 역시 악당을 이용한 버그를 활용하여, 왼쪽 방의 닫힌 문을 그대로 워프로 통과한다. (빙빙 돌 필요가 없다!)

레벨 6: 이 레벨에는 일반적인 도움닫기 점프로는 통과할 수 없는 간격으로 가시가 놓여 있다. 그러나 딜레이 없이 빠르게 타넘는 점프 패턴이 있는데, 이것은 본인도 경험상 알고 있었던 것들이다.

레벨 7: 중간에 공간 워프 버그를 하나 활용한다.

레벨 8: 역시 우주괴수는 출구를 개방한 뒤에 철문에 갇혀서 생쥐가 발판을 밟아 주러 올 때까지 기다릴 필요가 없다. 최고다.
톱날이빨과 악당을 동시에 마주치는 곳에서는 악당을 죽이는 게 불가피한 듯. 문닫힘 발판을 통과하려면 뛰어야 하는데 도움닫기도 못 하고(바로 다음 방에서 절벽이 있기 때문에), 그러면서 악당의 공격을 당하지도 않아야 하니, 이러지도 저러지도 못하기 때문이다.

레벨 9: 중후반에 톱날이빨을 동작하지 않게 하는 버그는 나도 알고 있었다.

레벨 10과 11은 특이사항 없음.

레벨 12: 그 공간 워프 버그도 유명하며, 나 역시 스스로 발견했었다.

일반적으로 레벨을 깨는 출구는 문이 완전히 열리기 전까지는 왕자가 들어갈 수 없다. ↑ 키를 눌러도 왕자는 말 그대로 펄쩍 뛰기만 한다.
단, 최종 보스인 Jaffar를 죽이고 나서 자동으로 열리는 출구는 예외. 완전히 열리기 전에도 왕자가 쏙 들어가는 게 가능하다.
그러나 동영상을 보면 왕자는 여기서도 출구가 완전히 열릴 때까지 약 1초 남짓 기다렸다가 들어간다.
물론 어차피 Jaffar가 죽은 뒤부터는 게임 내부의 타이머가 정지하긴 하지만, 게임 시작 직후의 앉음 딜레이조차 건너뛰려고 Ctrl+A를 누른 타임어택의 취지에 비춰 보면 살짝 옥의티라면 옥의티 같다.

이 게임은 Shift+L을 누르면 레벨 4까지 레벨을 건너뛸 수가 있다. 물론 이 경우 60분이던 시간이 15분으로 1/4토막나기 때문에 게임을 제대로 더 진행할 수는 없다.
그러나 프로 타임어태커는.. 그 상태로 시작해서도 게임 엔딩을 볼 수 있다. 경악. =_=;;:

단, 후편인 왕자 2는 저런 게임 메카닉 상의 버그가 거의 다 사라졌으며, 악당도 움직임이 굉장히 빨라져서 자리 바꾸고 튀는 게 불가능해진 관계로 1과 같은 궁극의 타임어택이 나오지는 못한다. 결정적인 시간 절약 요인 버그 exploit들이 없어졌으니 말이다.

Posted by 사무엘

2013/08/02 19:20 2013/08/02 19:20
,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/862

분당선 구성 역

수도권 광역전철 분당선은 혼잡한 서울 시내와 분당 신도시를 지나지만 종종 한적한 곳도 지난다.

구룡은 서울 강남에서 잉여력 높기로 악명 높아서 '굴욕'이라고까지 불릴 정도인 역이다.
이매는 야탑과 서현 사이에 새로 생긴 역인데 출구 근처에 기본적으로 역세권이라 불릴 만한 업무· 상업 시설이 없으며, 길거리에 행인이 거의 안 보인다. 예전에 역이 괜히 없었던 게 아니다.

오늘은 그러한 역들 중의 하나로 '구성' 역을 살펴보겠다.
구성은 죽전 이남의 보정 다음에 등장하는 역으로, 서울-성남-용인을 잇는 대표적인 두 길인 경부 고속도로와 국가 지원 지방도 23호선--이 구간에서는 특별히 용구대로라는 이름으로도 불림--의 사이에 자리잡아 있다. 신분당선 청계산입구 역과 더불어 지하철 출입구에서 경부 고속도로를 곧장 볼 수 있는 얼마 안 되는 역이기도 하다.

고속도로가 코앞에 보인다는 건 그만큼 역도 한적한 곳에 있다는 뜻이다. 청계산입구 역과 같은 맥락이다.
주변의 분당선 역들이 여느 지하철들처럼 다 용구대로를 따라 만들어진 것과는 달리, 구성 역은 용구대로를 한 블록쯤 벗어나서 용구대로와 고속도로의 중간의 어정쩡한 지점에 자리잡고 있다. 역 주변에 보이는 건 자그마한 개천, 한적한 들판이다.

역이 그렇게 만들어진 이유는 선형 때문이다. 자동차 도로만 따라 남쪽으로 내려갈 경우 교차로에서 급곡선이 생기기 때문이다.
그래서 이곳에서는 일부 단거리 마을버스만이 일부러 대로에서 벗어나 구성 역 출입구 근처까지 들어갔다가 나오지, 간선 버스들은 그냥 용구대로만 달리면서 구성 역을 비껴 가 버린다.

구성 역은 비록 대로에서의 접근성을 희생했지만 이 때문에 여느 분당선의 역답지 않게 주변이 아주 전원적이며, 공간이 넉넉하여 공영 주차장 같은 시설도 갖추고 있다.
하지만 이 역은 생긴 지 얼마 안 된 관계로 다음, 네이버, 구글 등의 지도의 항공 사진들은 다 2013년 7월 현재까지도 역 주변의 실제 모습이 아니라 그냥 허허벌판만을 표시하고 있다. 하긴, 2010년 말에 생긴 신경주 역조차도 항공 사진에서 주변이 제대로 표시되기 시작한 건 정말 얼마 안 됐다.

그래서 궁금해졌다. 본인은 구성 역으로 직접 답사를 떠났다.

사용자 삽입 이미지사용자 삽입 이미지
이 역에서 내린 후 1번이나 2번 출구로 나가면 이런 지하 마당이 이용객을 반긴다. 고도상으로는 지하여서 에스컬레이터를 타고 올라가야 완전한 지상이 나오지만, 이곳은 하늘 위가 완전히 뚫려 있다. 분위기가 이색적이다.
사용자 삽입 이미지
사용자 삽입 이미지
지상으로 나오면 옆엔 좁은 도로가 뚫려 있고, 저 건너편에는 넓은 부지를 이용한 공영 주차장이 있다. 주차장 너머로는 그냥 들판이며, 더 건너편은 경부 고속도로이다.
공간이 넉넉한 덕분에 주차장 이용료는 어지간한 상업용 건물보다는 저렴한 편이다. 하루 종일 주차해도 6000원이면 된다.

사용자 삽입 이미지사용자 삽입 이미지
자동차라면 역의 좌우로 나 있는 도로로 빙 둘러서 다리를 건너야 옆의 용구대로로 진입할 수 있다. 그러나 사람은 그냥 센터에 있는 샛길을 이용해서 대로로 가도 된다.

사용자 삽입 이미지
구성 역 바로 옆에는 신기하게도 역세권 버프를 받은 절이 하나 있다. <전통사>.

사용자 삽입 이미지
그리고 역과 대로 사이에는 이런 게 있더라.
포장도 되지 않은 이런 공터는 이미 무료 주차장으로 널리 이용되고 있었다.

사용자 삽입 이미지
주말의 용구대로는 차들로 몸살을 앓고 있었다.

사용자 삽입 이미지
번화가인 용구대로 주변과는 달리, 구성 역 주변은 정말 한가롭고 전원적이었다.
구성 역은 공간이 넉넉하고 부지에 여유가 있는 덕분에 훗날 고심도 급행 전철(GTX) 같은 철도가 건설될 때 환승역으로 성장할 가능성도 있다고 한다.
구성 역 주변이 언제까지 이렇게 한가할지는 모르겠는데 앞으로도 발전 가능성을 좀 지켜봐야겠다.

Posted by 사무엘

2013/07/31 08:32 2013/07/31 08:32
, ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/861

한자어로 '원'이라고 부르는 동그라미라는 도형은 시각적으로나 수학적으로나 아주 신기한 도형이다.
둥글다는 게 무슨 의미인지 기계가 계산으로 표현할 수 있을 정도로 엄밀하게 정의하자면 결국 '어떤 점에서 거리가 같은 점들의 집합'이라는 정의가 등장하게 되고, n차원 직교 좌표에서 거리라는 건 결국 차원을 구성하는 각 축의 거리들의 제곱의 합의 제곱근이라고 정의된다.

원의 지름과 원의 둘레의 비율은 그 이름도 유명한 '파이'이며, 3.141592... 로 시작하는 이 값은 무리수인 동시에 초월수라는 것도 상식이다.

그런데 이 원을 정사각형 격자 모양의 래스터 그래픽 장치에서 어떻게 하면 효율적으로 그릴 수 있을까? 그런 물건의 내부엔 컴퍼스 같은 직관적인 도구가 없는데 말이다.
중심이 x, y이고 반지름이 r인 원을 구성하는 좌표들을 어떤 계산을 통해 얻어 올 수 있을까?
원점이 중심인 원의 방정식은 x^2+y^2=r^2. 따라서 y=sqrt(r^2-x^2) 방정식을 이용하면 사분원 내지 반원을 구성하는 점을 구할 수 있다. 그리고 이 값을 바탕으로 나머지 방향의 점을 그리면 될 것이다.

#include <math.h>
template<typename T>
void Draw_Circle(int x, int y, int r, T f)
{
    double R_2 = r*r;
    for(int i=0;i<r;i++) {
        int v = (int)(sqrt( R_2 - i*i )+0.5);
        f(x-i, y-v); f(x-i, y+v);
        f(x+i, y-v); f(x+i, y+v);
    }
}

for문 자체는 0부터 r까지 사분원의 x좌표만 돌고, 이를 바탕으로 점을 찍는 함수 f를 4개 방향으로 모두 호출한다.
r의 제곱 값은 한 번만 계산하면 되므로 for문 밖에서 별도로 선언해 주는 센스도.
소숫점은 버림이 아니라 반올림이 되도록 0.5를 더한 뒤에 int로 캐스팅하는 게 좋다. 그래야 당장 그려지는 원도 90*n도 부근이 더 탱탱하고 보기 좋아진다.

함수의 사용은 MFC 기준으로 이런 식으로 하면 된다. 함수 안에서 또 다른 함수를 내부적으로 호출할 때 함수 포인터보다 람다가 참 깔끔하긴 하다. (너무 남발한 게 꼬이면 code bloat은 피할 수 없겠지만)

CPaintDC dc(this);
auto x = [&](int x,int y) { dc.SetPixel(x,y,0); };
Draw_Circle(220,220, 200,  x);
Draw_Circle(420,330, 160,  x);

사용자 삽입 이미지

그런데 아뿔싸, 역시 기울기가 1보다 더 커지는 곳에는 점이 듬성듬성 떨어져 있게 된다.
이 틈을 점 찍기가 아니라 선 그리기 같은 다른 함수로 메운다는 건 있을 수 없는 일이고..
결국, 우리의 원 그리기 알고리즘은 언제나 기울기가 1보다 작은 구간에서만 동작하게 loop 구조를 바꿀 필요가 있다.
우리는 원을 4등분했는데, 그렇게 4등분된 조각도 한쪽 끝과 맞은편 끝이 완벽하게 대칭으로 이들을 동시에 그려 보자.

가령, 1사분면에서는 x좌표를 1씩 증가시키면서 r로 근접하고(위의 코드에서 i) y좌표는 r이다가 점점 0으로 작아지는데(위의 코드에서 v),
이와 동시에 반대편에서는 y좌표를 1씩 증가시키면서 r로 근접하고, x좌표는 r에서 0으로 근접시키도록 점을 같이 그리는 것이다.
이제 loop는 변수 i의 값이 r에 도달한 지점에서 끝나는 게 아니라 v와 값이 같아지는 지점에서 끝나면 된다. (정확히는 sqrt(2)*r/2 지점이 됨)

{
    double R_2 = r*r;
    for(int i=0; ;i++) {
        int v = (int)(sqrt( R_2 - i*i )+0.5);
        f(x-i, y-v); f(x-v, y-i);
        f(x-i, y+v); f(x-v, y+i);

        f(x+i, y-v); f(x+v, y-i);
        f(x+i, y+v); f(x+v, y+i);
        if(i>v) break;
    }
}

사용자 삽입 이미지
와, 이로써 굉장히 찰진 모양의 원이 그려졌다. 한 번 루프를 돌 때마다 점이 8개가 그려지는 것이다.
그러나 이런 원 하나 그리는데 부동소숫점에, 곱셈에, 심지어 제곱근까지 꽤 부담스러운 연산이 많이 들어갔다.
이걸 좀 줄일 수는 없을까?

...
    int R_2 = r*r;
    int v = r;
    for(int i=0; ;i++) {
        if(i*i + v*v > R_2) --v;
...

loop의 앞부분을 이렇게 고쳐 보자.
x축에 속하는 i의 값이 1증가할 때마다 y축에 속하는 v의 값은 그대로 유지되거나 1 감소하거나 둘 중 한 변화만을 겪을 것이다.
i가 증가함에 따라 원점에서 i, v까지의 거리가 R보다 확 커지게 됐다면, 이 궤적은 원의 범위를 벗어나는 것이므로 y축에 속하는 v를 1 줄여 준다.

실질적으로 행해지는 연산을 이렇게 최적화해 주면 최소한 부동소숫점과 제곱근 연산은 없어진다.
그러나 최적화의 여지는 그래도 여전히 남아 있다. 저 꼴도 보기 싫은 곱셈을 없애려면 어떡하면 좋을까?

방법이 있다.
결국, i*i는 0, 1, 4, 9, 16 ...의 순열을 생성해 낼 텐데, 얘는 덧셈을 두 번 하는 걸로 대체할 수 있다. 한 번 덧셈을 한 뒤엔 증가치가 2씩 늘어나니까 말이다(1과 4의 차는 3, 4와 9의 차는 5, 9와 16의 차는 7). x^2의 도함수가 괜히 2*x가 아니다.

그리고 v는 초기값이 아예 R_2와 같으니 약분이 가능하다. 그 뒤에 v의 값이 줄어들면서 차이만이 발생할 뿐이다. 그런데 얼마를 빼 줘야 할까?
x^2가 (x-1)^2로 바뀌었을 때 감소하는 값은 잘 알다시피 2*x-1이다. 따라서 이 값만 초기에 계산해 놓은 뒤, v가 1 감소하게 됐을 때 가상의 v_square은 그만치 빼 주고, 그 델타값 자체도 2 감소시키면 된다.

...
    int v = r, i_square = 0, i_delta = 1;
    int v_delta=2*r-1, v_square_delta = 0;
    for(int i=0; ;i++) {
        if(i_square + v_square_delta > 0) {
            --v; v_square_delta-=v_delta, v_delta-=2;
        }

... //점 여덟 군데를 찍어 준 뒤

        if(i>v) break;
        else i_square+=i_delta, i_delta+=2;
    }

이로써 그 부드러운 원을 오로지 정수의 덧셈만으로, 그리고 곱셈이라고는 loop 돌기 전에 *2 단 한 번밖에 안 하는 깔끔한 원 그리기 알고리즘이 완성되었다. 놀랍지 않은가? 게다가 고정적인 두 배 연산은 잘 알다시피 bit shift로도 수행 가능한 아주 가벼운 연산이기도 하고 말이다.

GWBASIC, Windows GDI API, 옛날 볼랜드 BGI 등 모든 그래픽 라이브러리에 들어있는 원 그리기 함수는 기본적으로 이 알고리즘을 이용하여 원을 그린다. 각종 알고리즘 서적에 예제로 실려 있는 소스들도 세부적인 변수 활용이나 계수 계산에 차이가 있을지언정 기본적인 아이디어는 동일하다.

사실, 이건 거의 대학교 학부 수준이고 정보 올림피아드 공부라도 했다면 중· 고등학교 시절에라도 접했을 기초적인 내용이다. 진짜 어려운 건 이걸 응용하여 안티앨리어싱을 적용한다거나 타원을 그리거나, 아예 부채꼴 내지 회전된 원을 그리는 알고리즘이다.

단, Windows GDI가 그리는 원은 왠지 좀 엉성하고 덜 예쁜 것 같다. 비교를 할 때 반올림 보정을 안 하는지 경계가 아주 약간 덜 통통하며, 특히 기울기가 1(45도)에 가까워지는 지점에 점의 배치가 지저분하다.
차이를 보이기 위해 움짤을 만들어 보았다. 파란색 원은 GDI 함수로 그린 것이고, 빨간색 원은 우리가 작성한 함수로 그린 것이다.

사용자 삽입 이미지

Posted by 사무엘

2013/07/28 08:27 2013/07/28 08:27
, , ,
Response
No Trackback , 6 Comments
RSS :
http://moogi.new21.org/tc/rss/response/860

6· 25에 대한 팩트들

1. 옛날에는 6· 25사변, 동란이라는 말을 자주 썼는데 요즘은 그런 단어를 접하기 힘들다. 그리고 옛날에는 적군을 북한군 대신 괴뢰군, 공산군이라고도 많이 불렀다.

2. 6· 25는 선악 구도가 매우 분명한 전쟁이었다. UN이 창립 이래로 거의 처음이자 마지막으로 유일하게 적극적으로 한 진영의 편을 들어서 반대편 진영을 군사력으로 퇴치했다.
역사상 이렇게 많은 나라들이 한 작은 나라 편을 들었던 전쟁은 없었다. 그러나 미래에 성경이 말하는 아마겟돈 전쟁 때는 많은 나라들이 똘똘 뭉쳐서 오로지 한 나라를 대적하게 될 것이다. 그게 어느 나라인지는 이 자리에서 설명하지 않겠다.

3. 일요일 새벽에 감행된 너무 갑작스러운 공격에 대한민국은 당시 제대로 허를 찔렸다. 모든 것이 열세였고 겨우 사흘 만에 수도 서울을 허무하게 빼앗겼으며, 정부의 대처도 우왕좌왕 허술하기 짝이 없었다. 특히 개전 직후에 타이밍이 어긋난 대국민 안내방송과 한강 다리 폭파는 정말 뼈아픈 실책으로 기록되었다.

4. 그러나 다소 출혈을 감수하고라도 나라에서 정말 혹독하게 군 내부의 숙군 작업을 진행하고 빨치산들을 토벌해 둔 것은, 추후에 더 끔찍한 비극을 예방한 매우 다행스러운 선견지명 조치였다. 국군 내부에서조차 빨갱이가 우글거렸다면 얘들은 전쟁 났을 때 맞서 싸우기는커녕 지시를 어기고 적에게 모조리 항복하거나 이적행위를 저질렀을 것이다.

내가 대통령이었다 해도 그런 위급하고 절박한 상황에선, 사상만 올바른 게 확실하다면 친일 경력 군경이라도 적극 채용해서 안 쓸 수가 없었겠다. 내가 늘 강조하지만, 우리나라의 친일 청산을 방해하고 원천 봉쇄한 건 북한이며 북한 역시 내부적으로 군경 한 명이 아쉬운 마당에 친일 청산 안 한 건 마찬가지였다.

5. 중공군은 우리나라에 1· 4 후퇴를 안기고 멸공 북진 통일의 절호의 기회를 영원히 박탈해 버린 원흉이다. 생각해 보면 굉장히 열받고 통탄할 일인데, 우리가 당한 악행에 '비해서' 우리나라 내부에서의 반중 정서는 그만치 크지 않다.
1· 4 후퇴 당시에는 믿어지지 않겠지만 한강이 자동차가 지나갈 수 있을 정도로 꽁꽁 얼었다고 한다. 그래서 강 앞에 피난민들의 발이 묶인 적은 없었다. 하지만 이때 이산가족이 가장 많이 발생했다.

6. 1951년 3월경에 남한이 서울을 재탈환한 뒤부터 전쟁은 휴전 협정이 맺어질 때까지 계속 38선 부근에서 국지전만 진행되었다. 38선이 지금의 휴전선으로 바뀌면서 영토 자체는 대한민국이 과거보다 더 많이 수복했다. 그러나 서울이 북한과 더욱 가까워졌고 서해 NLL의 군사 긴장도가 더 높아졌다.
 
7. 흔히 '종북 좌파'라고 불리는 정권 시절에 <독도는 우리 땅>과 <6· 25 노래>가 금지곡이 됐다는 말이 떠돈다. 허나 검색을 해 보면 도대체 무슨 금지 조치를 당해서 언제 다시 풀렸는지가 분명치 않고 출처가 불분명하며, 이건 그냥 루머일 가능성이 높다. 이것은 마치 “이 승만이 시민들한테는 피난 가지 말라고 속이고는 자기 고의로 혼자 튀었다” 같은 급의 거짓 중상모략이 될 수도 있으니 난 그 점은 섣불리 판단하지 않겠다.

하지만, 가사가 희석되고 변개된 심 재방 작사의 <신 6· 25 노래>는 정서상 절대 용납할 수 없으며, 그거야말로 개사의 저의를 의심하는 바이다.

오리지널이 무엇이냐고?

사용자 삽입 이미지

아아 잊으랴, 어찌 우리 이 날을
조국을 원수들이 짓밟아 오던 날을
맨주먹 붉은 피로 원수를 막아내어
발을 굴러 땅을 치며 의분에 떤 날을

단조로 시작해서 장조로 끝나는 이 노래이다.
갓 태어난 가난한 신생 독립국이던 대한민국이라는 나라가 지구상에서 사라질 뻔했던 절체절명의 위기를 정말 숨 막히고 섬뜩하고 처절한 가사로 표현했다. 6· 25를 직접 겪은 사람만이 쓸 수 있는 가사이다.

그런데, 이 노래 가사를 불순한 의도로 변개한 놈들이 있다.
가해자의 정체와 전쟁의 근본 원인을 쏙 감춰서 “원수들이 내 조국을 짓밟던 날”을 “국토가 두동강 나고 동족끼리 총부리를 들이댄 날”로.. 오로지 결과만 부각되어 보이게 바꾼 新 6·25 노래가 만들어져 한때 보급되었었다.

이건 일본이 자기네 전쟁 범죄는 입 싹 씻고 사과도 제대로 안 하고 그저 “다시는 실수를 반복하지 않겠습니다”로 얼버무리기만 하고, 자기들도 원폭 피해자일 뿐이라고 궤변을 늘어놓는 것과 하나도 다를 게 없다. 진짜 똑같다.

잘 들어라. 난 분명하고 단호하게 내 이름과 명예를 걸고 선언한다.
6·25 노래에서 북한의 정체성과 그들의 범죄 행위를 제거하는 것은
성경에서 지옥에 대한 묘사를 없애고 예수 그리스도의 피를 없애고 동성애를 정죄하는 표현을 희석시키는 식으로 말씀을 변개하는 것과 완전히 똑같은 맥락이다!

난 이런 짓을 조장하는 진영을 매우 싫어한다.
성경이 말하는 근본주의 교리를 믿고 절대적인 선과 악 관념과 내세관을 갖춘 신자라면 종북 좌빨의 영하고는 도저히 같이 상종을 할 수 없다.

Posted by 사무엘

2013/07/25 19:23 2013/07/25 19:23
, , ,
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/859

북한의 한글 서체

북한에서 만든 한글 서체의 중요한 특징 중 하나:
ㅌ을 E 모양이 아니라 ㅡ+ㄷ 모양으로 쓰는 걸 선호한다! 즉, 위의 가로줄을 아래의 몸통과 분리해서 따로 적는다는 뜻이다.

사용자 삽입 이미지

물론, 북한 서체 중에도 E자 모양인 물건이 없는 건 아니다.
그러나 일단 북한의 굴림-바탕-돋움-궁서에 해당하는 4대 기본 글꼴인 천리마-청봉-광명-붓글이 모두 ㅌ이 ㅡ+ㄷ모양이니, 북한에서는 이걸 ㅌ의 공식적인 기본형으로 간주하는 듯하다.

남한에서 쓰는 글꼴 중에는 ㅌ이 그렇게 돼 있는 물건은 진짜 궁서체밖에 없지 싶다.
북한이야 원래 문화가 196, 70년대-_-의 복고풍을 추구하고 서체도 순명조 내지 붓글씨 계열을 쓰는 걸 좋아하는 동네이긴 하지만, 명조 계열 서체까지 ㅌ을 그렇게 적는 관행은 남한에서는 좀체 찾아볼 수 없다.

북한과 한글 서체라는 두 분야에 모두 평균 이상으로 관심이 많은 나조차 이걸 눈치채는 데 시간이 굉장히 오래 걸린 이유는, 역시 ㅌ이 자주 쓰이는 자음이 아니기 때문이어서인 것 같다.

두음법칙만큼이나 한글의 자형의 미세한 차이도 남북의 문화 차이를 나타낼 수 있는 잣대가 된 듯하다.
원래 ㅌ의 모양은 ㅡ+ㄷ이 맞다는 설명도 옛날에 본 것 같으나, 그 근거를 모르겠다. 당장 훈민정음해례 같은 엄청 옛날 문헌을 봐도 ㅌ은 E 모양으로 그려져 있는데?

Posted by 사무엘

2013/07/23 08:36 2013/07/23 08:36
, ,
Response
No Trackback , 4 Comments
RSS :
http://moogi.new21.org/tc/rss/response/858

철도 사진 분석 - 2

1. 다음 사진은 현직 초등학교 교사인, 본인의 모 지인이 최근에 보내 준 것이다. (내가 철도광이라는 걸 알고 친히..ㅠㅠ ㄱㅅㄱㅅ!)
애들이 보는 교과서가 아니라 교사용 지도서라서 사진이 흑백이라고 한다.

사용자 삽입 이미지

철덕이라면 철도 관련 사진을 보는 순간 눈에 불꽃이 튀면서 일반인들이 보지 못하는 주변 정보를 모조리 상상하고 유추해 낼 수 있어야 마땅하다.

1번은 대전 국립 중앙 과학관과 엑스포 과학 공원을 잇는 자기 부상 열차이다. 딱 보자마자 알아보시겠는가?

2번은 미카나 파시 같은 기관차는 아니고, 관광용으로 국내에서 최후에 도입했다가 지금은 운행 중지+정태보존 상태로 바뀐 '901호 증기 기관차'이다. 보일러의 양옆에 흰색 금속줄이 놓여 있는 걸 보면 바로 분간 가능하다.
현재 얘는 중앙선 풍기 역에 놓여 있지만, 주변 배경으로나 저 사진이 촬영되던 시기를 감안하면 저 사진은 더 옛날에 경북선 점촌 역에서 촬영된 것이다.

3번은 외형이 워낙 특이하니 인천 지하철 전동차임을 쉽게 분간할 수 있을 것이다. 지상 구간인 귤현-계양 사이에서 촬영되었을 것이다.

기관차라고 이름을 붙여 놓고는 동차를 소개해 놓은 건 3번뿐만 아니라 4번도 마찬가지다.
새마을호 전후동력형 디젤 동차인 건 더 말하면 입만 아플 것이고, 촬영 장소는 아마 승강장의 천장 모양으로 보건대 그냥 서울 역이 아닌가 싶다.

난 그 지인에게 부탁했다.
혹시 가르치는 아이들 중에 철도에 대해 에디슨 같은 호기심과 비범함을 보이는 미래의 꿈나무가 있다면, 날 생각해서라도 최대한 자상하게 지도하고 철덕으로 이끌어 달라고 말이다. ^____^;;

2. 다음 지도를 꺼내서 여기저기 지형을 살펴보고 있었다.
불과 몇 달 전까지만 해도 국내외 주요 지도 사이트들은 신경주 역 주변의 항공 사진이 업데이트되지 않아서 현재의 모습과 지도의 모습이 맞지 않은 듯했는데 지금은 다들 개선되었다.

그런데, 부산 역 주변을 보니 선로에 KTX 한 편성이 놓인 게 보였다. (산천 말고, 1세대 TGV-K 차종)
요놈의 길이를 재 봤더니.. 역시나 진짜로 딱 388m가 맞았다.
항공 사진이란 게 있어서 이런 것도 확인이 가능하다. ^^

사용자 삽입 이미지
 오른쪽의 KTX 제원은 한국어 위키백과 페이지의 화면 캡처이다.

인천 역은 승강장이 두단식일 정도로 진짜 선로의 말단에 자리잡고 있는 반면, 오늘날의 부산 역은 승강장 이남으로도 거의 1km에 가깝게 선로가 더 뻗어 있다.
이것은 원래 부산 역은 지금의 부산 역보다 더 남쪽에 있었기 때문에 그렇다. 지금의 부산 역은 초량 역이었다.
그러던 것이 1953년에 부산 역 대화재 사건이 발생하면서 그 역이 없어지고, 초량 역이 있던 자리에 부산 역이 새로 세워진 것이다.

3. 이건 내가 찍은 사진은 아니다만,
딱 보면.. KTX 경부 고속선 서울-대전 구간이고, 역방향 좌석에서 찍은 것임을 알 수 있다.
전자는 좀 guessing이 가미된 요소이지만, 후자는 사람이 보통 사진을 찍는 방향을 생각했을 때 명확하다.

사용자 삽입 이미지

4. 지금은 수도권 전철 1호선에 [노량진] - 대방 - 신길 - [영등포] - 신도림 - 구로 - 가산디지털단지 - 독산 - [금천구청](구 시흥)으로 역이 무진장 많이 있지만, 경부선 개통 당시에는 [ ]친 역밖에 없었으며 그 구도가 1960년대까지 계속되었다.
노량진과 영등포는 아예 경인선 개통 당시부터 있었던 역이요, (since 1899) 시흥은 부산역과 같이 개통하여 경부선 개통 초기부터 있어 왔다(since 1908).

사용자 삽입 이미지

그러니 1968년에 구로공단에서 제1회 한국 무역 박람회가 개최되었을 때는 '박람회 역'이라는 임시 승강장이 잠시 만들어지기도 했을 정도였다. 주변에 하도 역이 없어서..

대방, 구로, 가리봉(지금의 가산디지털단지) 역은 수도권 전철 1호선과 함께 개통했으며(since 1974)
신도림은 잘 알다시피 서울 지하철 2호선과 함께 환승역으로 개통(since 1984).
신길은 서울 지하철 5호선과 함께 환승역으로 개통(since 1998)했고 독산역도 이때 같이 개통했다.

사진을 보면 영등포-시흥 사이의 역간거리 숫자가 5인지 6인지 8인지 좀 희미한 편인데, 저건 8.2km이다. 어떻게 아냐고? 철도 영업거리표를 찾아보면 된다. 서울 기점으로 영등포가 9.1km이고 금천구청은 17.3km 지점이다.

Posted by 사무엘

2013/07/20 08:37 2013/07/20 08:37
,
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/857

3. 더 기괴하고 잉여력마저 의심되는 오버로딩

(1) 비트 연산자도 아니고 논리 연산자 && || !는 오버로딩할 일이 거의 없으며, 각종 C++ 디자인 패턴 책에서도 오버로딩을 권하지 않는 물건들이다. 그 연산자를 건드릴 게 아니라 개체를 건드리는 게 순리이다. 논리 연산자들이 취급할 수 있는 정수나 boolean 값으로 형변환하는 연산자를 제공하는 게 이치에 맞다.

굳이 논리 연산자를 오버로딩해 버리면, 일단 언어가 원래 제공하는 단축연산 기능이 사라지게 된다. 즉, A && B에서 A가 이미 false이면 B의 값은 아예 계산하지 않고 함수를 호출하지도 않는 것 말이다. 오버로딩된 함수는 논리 연산자라도 언제나 A와 B의 값을 먼저 계산한 뒤에 실행된다.

(2) 어떤 개체가 메모리에 차지하는 주소를 얻어 오는 기능은 그 어떤 타입이나 클래스에 대해서도 절대불변으로 동작해야 하는 기능이지 않은지? 마치 개체의 고정된 크기를 얻어 오는 sizeof 연산자처럼 말이다.
그럼에도 불구하고 C++의 단항 & (address-of) 연산자는 오버로딩 가능하다!

class Foo {
    //...
public
    int operator&() { return 0; }
};

void Bar(Foo *p)
{
    //...
}

이렇게 선언하거나 더 얄밉게 연산자 함수를 아예 private로 감춰 버리고 나면,
지역변수나 클래스/구조체의 멤버로 직접 선언된 Foo 개체는 Bar라는 함수에다 넘겨주는 게 불가능해진다. =_=;;;

Foo a; Bar(&a);

이런 테크닉이 무슨 필요나 의의가 있는지는 난 잘 모르겠다.

전통적인 &는 변수의 주소라는 R-value만 되돌리는 연산자인데, 이를 오버로딩하면 &는 참조자 같은 걸 되돌릴 경우 L-value를 되돌리는 것도 가능해진다. 따라서 그 값에 대해서 또 주소를 얻는 &를 적용하는 게 덩달아 가능해진다.
그러나 이 경우 &&를 연달아 쓸 수는 없으며, & &a 같은 식으로 토큰을 분리해 줘야 한다. 예전에 중첩 템플릿 인자를 닫을 때 > 사이에 공백을 넣어 줬던 것처럼 말이다.

(3) 게다가, 우선순위가 가장 낮으며 그저 여러 연산자들을 한데 나열하는 역할만을 하는 콤마 연산자도 오버로딩 가능하다! (,는 오버로딩 없이도 원래 아무 피연산자에 그 어떤 타입이 와도 무조건 괜찮은 유일한 이항 연산자임)
콤마는 함수 인자 구분용으로도 쓰인다는 특성상, 이 연산자는 가변 인자 함수 호출을 흉내 내는 용도로 쓰일 수 있을 것 같다. list, 3, 2, 1, 8, 4; 이라고 써 주고 list.add(3); list.add(2); ... 같은 효과를 낼 수도 있다는 뜻이다. 하지만 이걸 남발하는 건 좀 사악한 짓인 듯.

(4) 기괴한 오버로딩의 진정한 종결자로 내가 최후까지 남겨 둔 건 바로 ->* (pointer-to-member) 이다. 얘는 유사품인 ->하고는 오버로딩을 하는 방식이 사뭇 다르다!
-> 연산자가 아무 인자가 없는 멤버 함수인 반면, ->*는 단 하나의 인자를 받는다. 그 인자는 아무 타입이나 될 수 있으며, ->* 연산자 함수 자체도 다양한 타입으로 오버로딩될 수 있다. 가령,

POINT& operator->*(int x) { return m_pt[x]; }

이렇게 오버로딩이 된 클래스가 있다면

(obj->*0).x = 100;

이런 식으로 활용이 가능하다. 0이 연산자 함수의 인자로 전달된다. 0뿐만이 아니라 당연히 int 변수 n 같은 것도 줄 수 있다. .이나 -> 연산자 다음에는 구조체/클래스의 멤버가 뒤따라야 하는 반면, .*이나 ->* 연산자 다음에는 임의의 타입에 속하는 value가 올 수 있는 구조인 것이다. ->는 가리키는 대상 포인터이지만 .*는 대상으로부터 얻을 오프셋 자체가 고정이 아니라 동적이며, ->*는 대상과 오프셋이 모두 동적임을 뜻한다.

struct A { int x,y; };

struct B { A m_Obj; };

이렇게 A를 멤버로 갖는 B라는 클래스가 있다고 치자.
클래스의 멤버 포인터는 클래스에 종속적이다.
그러므로 클래스 B에 대해서 A에 소속된 멤버 포인터를 적용하고 싶다면 ->* 연산자를 오버로딩하여 다음과 같은 연산자 함수를 써 주면 된다.

int& operator->*(int A::*t) { return m_Obj.*t; }

그러면

B bar;
int A::*temp = &A::x;

bar->*temp = 100;
bar.m_Obj.*temp = 100;

위의 두 구문은

bar.m_Obj.x = 100;

과 동일한 의미를 지니게 된다. 실무에서 이걸 오버로딩할 일이 있을지는 잘 모르겠지만..;;
멤버 변수가 저렇고, 멤버 함수의 포인터에 대해서는 머리가 터질 것 같아서 생략하련다.
C++의 세계가 더욱 심오하게 느껴지지 않는가?

4. C++ 연산자 오버로딩의 한계

(1) 당연한 말이지만 원래 C++ 언어에 없는 새로운 토큰을 만들어 낼 수는 없다. 가령, @, ** 같은 듣보잡 기호를 연산자로 정의할 수는 없다. 특히 *는 포인터의 연쇄 역참조용으로도 쓰이기 때문에 ** 같은 건 C++에서 절대로 토큰으로 쓰일 수 없는 문자열이다.

(2) .(구조체 멤버 참조) .*(멤버 포인터) ::(scope) ?:(조건 판단. 유일한 삼항 연산자) sizeof 연산자는 의미가 완전히 고정되어 있으며 재정의할 수 없다.

(3) C/C++이 원래 정의하고 있는 연산자의 우선순위와 피연산자 결합 방향을 변경할 수는 없다. 그리고 built-in type에 대해 이미 정의되어 있는 연산의 의미를 재정의할 수도 없다.

이런 모든 구체적인 디테일들을 다 명시해야 한다면 C++의 참고용 매뉴얼은 정말 상상을 초월하게 두꺼울 수밖에 없겠다는 게 실감이 간다.

Posted by 사무엘

2013/07/17 08:36 2013/07/17 08:36
Response
No Trackback , 5 Comments
RSS :
http://moogi.new21.org/tc/rss/response/856

오늘은 C++에서 기초적이면서도 아주 심오하고 기괴한 문법 중 하나인 연산자 오버로딩에 대해서 좀 살펴보도록 하자.
정수, 포인터 같은 기본 자료형뿐만 아니라 사용자가 만든 임의의 개체도 연산자를 통해 조작할 수 있으면 천편일률적인 함수 호출 형태보다 코드가 더 깔끔하고 보기 좋아지는 효과가 있다. 물론, 아무 논리적 개연성 없이 오버로딩을 남발하면 코드를 알아보기 힘들어지겠지만 말이다.

C++은 연산자 오버로딩을 위해 operator라는 키워드를 제공한다. 그리고 개체에 대한 연산자 적용은 연산자 함수를 호출하는 것과 문법상 동치이다(syntactic sugar). 가령, a+b는 a.operator +(b) 또는 ::operator +(a,b)와 동치라는 뜻이다. 연산자 함수는 표준 규격이 없이 C++ 컴파일러마다 제각각으로 제정한 방식으로 심벌 명칭이 인코딩된다.

앞에서 예를 들었듯이 연산자 함수는 클래스에 소속될 수도 있고, 특정 클래스에 소속되지 않은 중립적인 전역 함수로 선언될 수도 있다. 클래스의 멤버 함수인 경우 this가 선행 피연산자에 포함된 것으로 간주되어 함수의 인자 개수가 하나 줄어든다. 연산자 함수는 명백한 특성상 default 인자를 갖는 게 허용되지 않는다.

한 클래스와 관련된 처리는 당연히 가능한 한 클래스의 내부에서 다 알아서 하는 게 좋다. 그러니 전역 함수 형태의 연산자 함수는 이항 연산자에서 우리 타입이 앞이 아니라 뒤에 나올 때 정도에나 필요하다. 가령, obj+1이 아니라 1+obj 같은 형태일 때 말이다. 그리고 이 전역 함수는 당연히 클래스의 내부에 접근이 가능해야 연산을 수행할 수 있을 것이므로 보통 friend 속성이 붙는다.

연산자가 클래스 함수와 전역 함수 형태로 모두 존재할 경우엔 모호성 에러가 나는 게 아니라 전역 함수가 우선적으로 선택되는 듯하다. a+b에 대해서 ::operator +(a,b)와 a.operator +(b)가 모두 존재하면 전자가 선택된다는 뜻. 언어의 설계가 원래 그렇게 된 건지는 잘 모르겠다.

본인은 연산자 오버로딩을 다음과 같은 양상으로 분류해 보았다.

1. 쉽고 직관적인 오버로딩

(1) 사칙연산, 대입, 비교, 형변환 정도가 여기에 속한다. 기본 중의 기본이다.
사칙연산을 재정의함으로써 C++에서도 문자열을 +를 써서 합치는 게 가능해졌다(동적 메모리 관리까지 하면서). 그리고 복소수, 유리수, 벡터 같은 복합적인 수를 표현하는 자료형을 만들 수 있게 되었다.

(2) 내부적으로 포인터 같은 외부 리소스를 참조하고 있기 때문에 단순 memcpy, memcmp 알고리즘으로는 대입이나 비교를 할 수 없는 클래스의 경우, 해당 연산자의 오버로딩이 필수이다. 이 역시 문자열 클래스가 좋은 예이다. 대입 연산자는 복사 생성자와도 비슷한 구석이 있다.
대입 연산자는 생성자와 역할이 비슷하다는 특성상, 연산자들 중 상속이 되지 않는다. 모든 클래스들은 상속에 의존하지 말고 자신만의 대입 연산자를 갖추고 있어야 한다는 뜻이다. 단, 순수 대입인 = 말고 +=, -= 같은 부류들은 상속됨.

(3) 비교 연산은 같은 클래스에 속하는 여러 원소들을 다른 컨테이너 클래스에다 집어넣어서 순서대로 나열해야 할 때 매우 요긴하게 쓰인다. 전통적인 C 스타일의 비교 함수보다 훨씬 더 직관적이다.

(4) 형변환 연산자야 상황에 따라 어떤 개체의 형태를 카멜레온처럼 바꿔 주고 개체의 사용 호환성을 높여 주는 기능이다. 포인터나 핸들 하나로만 달랑 표시되는 자료형에 대해서 자동으로 메모리 관리를 하고 여러 편의 기능을 수행하는 wrapper 클래스를 만든다면, 본디 자료형과 나 자신 사이의 호환성을 이런 형변환 연산자로 표현할 수 있다.
문자열 클래스는 const char/wchar_t * 같은 재래식 문자 포인터로 자신을 자동 변환하는 기능을 제공하며, MFC의 CWnd도 이런 맥락에서 HWND 형변환을 지원하는 것이다.

(5) 한편, 비트 연산자는 산술 연산보다야 쓰이는 빈도가 아무래도 낮다. 비트 벡터나 집합 같은 걸 표현하는 클래스를 만드는 게 아닌 이상 말이다.
단, 뭔가 직렬화 기능을 제공하는 스트림 개체들이 << >>를 오버로딩한 것은 굉장히 적절한 선택인 것 같다. MFC의 CArchive라든가 C++의 iostream 말이다. input, output이라는 방향성을 표현할 수 있고 또 비트 shift라는 개념 자체가 뭔가 '이동, 입출력'이라는 심상과 잘 맞아떨어지기 때문이다.

다른 연산자들보다 적당히 우선순위가 낮은 것도 장점. 하지만 산술 연산 말고 비교나 비트 연산자는 <<, >>보다도 우선순위가 낮으므로 사용할 때 유의할 필요가 있다.

2. 아까 것보다는 좀 더 생소하고 어려운 오버로딩

(1) 필요한 경우, operator new와 operator delete를 오버로딩할 수 있다. 물론, 객체를 생성하고 소멸하는 new/delete 본연의 기능을 완전히 뒤바꿀 수는 없지만, 이 연산자가 하는 일 중에 메모리를 할당하여 포인터를 되돌리는 기능 정도는 바꿔치기가 가능하다는 뜻이다. C/C++ 라이브러리가 기본 제공하는 heap이 아닌 특정 메모리 할당 함수를 써서 특정 메모리 위치에다가 객체를 배체하고 싶을 때 말이다.

특히 new/delete는 여러 개의 인자를 받을 수 있는 유일한 연산자 함수이다. size_t 형태로 정의되는 메모리 크기 말고 다른 인자도 받는 다양한 버전을 만들어서 new(arg) Object; 형태의 문법을 만들 수 있으며, 이때 arg는 Object의 생성자가 아니라 operator new라는 함수에다 전달된다. (delete 연산자는 어떤지 잘 모르겠다만..;;)

그리고 이놈은 전역 함수와 클래스 멤버 함수 모두 가능하다. 클래스 멤버 함수일 때는 굳이 static을 붙이지 않아도 이놈은 static이라고 간주된다. 당연히.. malloc/free 함수의 C++ 버전인데 this 포인터가 전혀 의미가 없는 문맥이기 때문이다..

사실, C++은 단일 개체를 할당하는 new/delete와 개체의 배열을 할당하는 new []/delete []도 구분되어 있다. 후자의 경우, 생성자나 소멸자 함수를 정확한 개수만치 호출해야 하기 때문에 배열 원소의 개수를 몰래 보관해 놓을 공간도 감안하여 메모리가 할당된다.
그러나 이것은 언제까지나 컴파일러가 알아서 계산을 하는 것이기 때문에 배열이냐 아니냐에 따라서 메모리 할당/해제 함수의 동작이 딱히 달라져야 할 것은 없다. 굳이 둘을 구분해야 할 필요가 있나 모르겠다.

(2) ++, --는 정수나 포인터 같은 아주 간단한 자료형과 어울리는 단항 연산자이다. 그런데 개체의 내부 상태를 한 단계 전진시킨다거나 할 때... 가령, 연결 리스트 같은 자료형에서 iterator를 다음 노드로 이동시키는 것을 표현할 때는 요런 물건이 유용하다. 난 파일 탐색 함수를 클래스로 만들면서 FindNextFile 함수 호출을 해당 클래스에 대한 ++ 연산으로 표현한 적이 있다. ㅎㅎ

++, --는 전위형(prefix ++a)과 후위형(postfix a++)이 둘 존재한다. 후위형 연산자 함수는 전위형 연산자 함수에 비해 잉여 인자 int 하나가 추가로 붙는다. 그리고 후위형 연산자는 자신의 상태는 바꾸면서 바뀌기 전의 자기 상태를 담고 있는 임시 객체를 되돌려야 하기 때문에 처리의 오버헤드가 전위형보다 더 크다. 아래의 코드를 참고하라.

A& A::operator++() //++a. a+=1과 동일하다.
{
    increment_myself();
    return *this;
}

A A::operator++(int) //a++.
{
    A temp(*this);
    increment_myself();
    return temp;
}

보다시피, 전위형과 후위형의 실제 동작 방식은 전적으로 관행에 따른 것이다. 전위형이 후위형처럼 동작하고 후위형이 전위형처럼 반대로 동작하게 하는 것도 프로그래머가 마음만 먹으면 엿장수 마음대로 얼마든지 가능하다.
정수의 경우 프로그래머가 for문 같은 데서 a++이라고 쓴다 해도 똑똑한 컴파일러가 굳이 임시 변수 안 만들고 ++a처럼 최적화를 할 수 있다. 그러나 사용자가 오버로딩한 연산자는 실제 용례가 어찌 될지 알 수 없으므로 컴파일러가 선뜻 최적화를 못 할 것이다.

일반적으로 a=a++의 실행 결과는 컴파일러의 구현 내지 최적화 방식에 따라 들쭉날쭉 달라지는 것으로 잘 알려져 있다. (당장 비주얼 C++와 xcode부터가 다르다!) 또한 a가 일반 정수일 때와, 클래스일 때도 동작이 서로 달라진다. 이식성은 완전히 안드로메다로 간다는 뜻 되겠다. 더구나 한 함수 호출의 인자 안에서 a와 a++이 동시에 존재하는 경우, 어떤 값이 들어가는지는 같은 컴파일러 안에서도 debug/release 빌드에 따라 차이가 생길 수 있으므로 이런 모호한 코드는 작성하지 않아야 한다.

(3) 포인터를 역참조하는 연산자인 * ->는 원래는 포인터가 아닌 일반 개체에 대해서 쓰일 수 없다. 그러나 C++은 이런 연산자를 오버로딩함으로써 인위적인 '포인터 역참조' 단계를 만들 수 있으며, 이로써 포인터가 아닌 개체를 마치 포인터처럼 취급할 수 있다. 형변환 연산자와 비슷한 역할을 하는 셈이다.

C++ STL의 iterator가 좋은 예이고, COM 포인터를 템플릿 형태로 감싸는 smart pointer도 .을 찍으면 자신의 고유한 함수를 호출하고, ->를 찍으면 자신이 갖고 있는 인터페이스의 함수를 곧장 호출하는 형태이다.
-> 연산자는 전역 함수가 아니라 인자가 없는 클래스 멤버 함수 형태로 딱 하나만 존재할 수 있다. 다음 토큰으로는 구조체나 클래스의 멤버 변수/함수가 와야 하기 때문에 리턴 타입은 구조체나 클래스의 포인터만이 가능하다.

(4) []도 재정의 가능하고 심지어 함수 호출을 나타내는 ()조차도 연산자의 일종으로서 재정의 가능하다!
[]의 경우 클래스가 포인터형으로의 형변환을 제공한다면 정수 인덱스의 참조는 컴파일러가 자동으로 알아서 그 포인터의 인덱스 참조로 처리한다. 그러나 [] 연산자 함수는 굳이 정수를 받지 않아도 되기 때문에 특히 hash 같은 자료구조를 만들 때 mapObject[ "H2O" ] = "산소"; 같은 구문도 가능해진다. 단, 진짜 고급 언어들처럼 인자를 여러 개 줄 수는 없다. map[2, 5] = 100; 처럼 할 수는 없다는 뜻.

()도 사정이 비슷한지라, 클래스가 함수 포인터로의 형변환을 제공한다면 굳이 () 연산자를 재정의하지 않아도 개체 다음에 곧장 ()를 붙이는 게 가능은 하다. 그러나 그 함수 포인터는 this 포인터가 없는 반면, () 연산자 함수는 this를 갖는 엄연한 멤버 함수이다.
함수 포인터로의 형변환 연산자 함수는 언어 문법 차원에서 토큰 배열의 한계 때문에 대상 타입을 typedef로 먼저 선언해서 한 단어로 만들어 놔야 한다.

Posted by 사무엘

2013/07/15 08:35 2013/07/15 08:35
Response
No Trackback , 5 Comments
RSS :
http://moogi.new21.org/tc/rss/response/854

본인은 처음에 새마을호에서 시작되었던 철도 관심사가 KTX, 전기 기관차를 거쳐 요즘은 증기 기관차로까지 이어지고 있다.

“옛날에는 기관차의 이름을 지금처럼 0000호대라는 번호로 붙인 게 아니라 미카, 파시, 마터, 허기 등의 이름으로 붙였구나.
고 김 재현 기관사가 몰았던 증기 기관차(미카)와, 경의선 장단 역에 방치되어 있던 녹슨 증기 기관차(마터)는 차종이 다른 것이었구나.”
같은 식이다.

증기 기관차까지 마스터해야 우리나라 근현대사와 철도를 연계하여 다 꿰뚫은 진정한 철덕이 될 수 있다는 걸 깨우쳤다. 이렇듯, 한번 철도의 맛을 접한 철덕은 차량, 시설, 지리, 역사 등 분야별로 철도 안에서 계속 골고루 자라야 한다. 그 성장이라는 게 금방 빨리 되는 게 아니다.

철도 기관차의 동력원은 크게 증기, 디젤, 전기로 나뉜다. 내연기관 기반인 디젤은 동력비 조절을 어떻게 하느냐에 따라서 기어/유압/전기 변환식으로 분류되고, 전기는 전기대로 사용하는 전기의 종류와 집전 방식에 따라 다양한 분류 기준이 존재한다.

이런 것처럼 증기 기관차에도 증기에만 적용되는 중요한 분류 기준이 하나 있다.
바로, 구동하는 데 필요한 물과 석탄을 기관차 내부에 보관하느냐, 혹은 기관차의 바로 뒤에 연결된 별도의 탄수차로부터 공급받느냐는 것이다. 전자식은 탱크식(tank)이라고 하고, 후자는 텐더식(tender)이라고 부른다.

이것은 기관차가 사용하는 물과 석탄의 부피가, 내연 기관이 사용하는 기름의 그것보다 크다는 점 때문에 생긴 특성이다. 보일러의 부피도 내연기관의 엔진보다 더 커야 겨우 그 집채만 한 쇳덩어리를 굴릴 만한 수증기를 만들 수 있다. 그 이유는 증기 기관은 디젤 엔진보다 태생적으로 출력이 약하기 때문이다.

그러니 물과 석탄을 기관차 안의 좁은 공간에다가만 싣고 다녀서는 도저히 장거리를 달릴 수 없다. 우리가 기억하는 거의 모든 증기 기관차는 텐더식이며, 이는 심지어 <은하철도 999>에 그려진 열차도 마찬가지이다.
탱크식은 단거리 소형 열차에 한정된 형태로만 쓰인다. 이는 디젤로 치면 자동차처럼 기어로 변속하는 소형 기관차와 같은 위상이라고 볼 수 있다.

믿거나 말거나, 1899년에 개통한 우리나라 최초의 철도인 경인선 노량진-제물포(오늘날의 동인천)에 처음으로 다녔던 열차는 '모가'(mogul의 변형) 형 증기 기관차인데, 이 역시 아담한 탱크식이다. 더 큰 텐더식 열차는 경부선과 경의선이 뚫린 1906년 이후에야 슬금슬금 국내에 도입되기 시작했다.

사용자 삽입 이미지

사용자 삽입 이미지사용자 삽입 이미지
한반도를 최초로 달렸던 철마가 바로 이 물건이라니! 오오오오~~ 굴뚝이 깔때기 같은 모양이다.
오늘날 경인선을 달리고 있는 VVVF 전동차와 비교해 보면 가히 격세지감이 아닐 수 없다. 같은 1435mm 표준궤를 달리는 철도 차량이 100년 사이에 이렇게 변할 수가 있을까?
철도에 대해서 좀 아는 사람이라면, 증기 기관차를 구경할 일이 있을 때 이놈이 탱크식인지 텐더식인지부터 먼저 눈여겨보게 될 것이다.

여담으로 영어 단어 공부를 좀 하자면,
tender는 ‘부드러운, 여린’을 뜻하는 형용사의 뜻이 있으며 특히 영어 성경에서는 tender mercies(친절한 긍휼)라고 해서 아주 즐겨 쓰인다. 그 반면, tender는 거룻배, 관리자, 탄수차 등을 뜻하는 명사도 되며, 둘은 어원상 아무 관계가 없는 동명이의어이다.

그리고 보너스.
얼마 전에 본인은 6·25 전쟁 당시에 총격을 받고 장단 역 구내에 버려졌던 '마터' 형 증기 기관차의 사진을 공개한 적이 있다.
'모가'가 우리나라 최초의 증기 기관차라면, '마터'는 일제 강점기 말기인 1940년대에 처음 제작되었으며, 크기도 크고 힘도 좋은 신형(?) 증기 기관차이다. 탱크식이 아니라 텐더식인 것은 두 말할 나위도 없다.

당시 '마터'는 한반도 중· 북부 지방에서만 운행되었으며 이들은 전부 오늘날의 북한에 속한 지역이기 때문에 남한과는 영 인연이 없는 열차였다. 지금이야 우리나라에도 태백선, 영동선 같은 산악 철도가 있지만, 그건 예전 글에서도 알 수 있듯, 해방과 분단 후 한참이 지나서야 우리나라가 독자적으로 만든 철도이기 때문에 일제 강점기나 북한하고는 별 상관이 없다.

다음은 이 '마터'형 증기 기관차가 파괴되고 녹슬기 전의 원래 모습이 어떠했을지를 유추하게 해 주는 아주 좋은 자료이다. 북한에서 이 증기 기관차를 모델로 우표를 만든 적이 있다. 모든 증기 기관차는 원래 검은색 도색이며, 이는 저 기관차도 예외가 아니다. 또한, 파괴된 기관차 잔해는 앞부분의 좌우에 씌워진 철판 벽이 없음을 알 수 있다.

사용자 삽입 이미지

Posted by 사무엘

2013/07/13 08:28 2013/07/13 08:28
,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/853

1. 협동체와 광동체

철도에는 잘 알다시피 궤간(케이프/협궤 1067, 스티븐슨/표준궤 1435 등)이라는 게 존재한다. 그런데 여객용 비행기에도 기체의 폭(그리고 크기도 덩달아)을 구분하는 간략한 잣대가 존재한다.
바로 협동체와 광동체.
객실에 복도가 한 줄로만 존재하는 기체는 협동체이고, 두 줄 존재하는 기체는 광동체이다.

이 기준에서 보면, 육상 교통수단들은 버스든 열차든 선택의 여지 없이 복도가 한 줄만 존재하는 협동체이다. 차로의 폭과 궤간의 제약에 곧장 걸리기 때문이다. 2-2가 가장 무난하고 보편적이며, 우등 고속버스나 KTX 특실 정도만이 2-1이다. 그러고 보니 옛날에 우리나라 철도엔 2-3짜리 아주 불편하고 열악한 객차도 있긴 했는데 다 지나간 옛날 이야기이다.

배야 그런 구분이 무의미할 정도로 넘사벽급의 대형화가 가능하니 논외이다.
그 반면 비행기는 폭에 관한 한, 둘의 중간 위상에 속하는지라 한 줄 아니면 두 줄이라는 구분이 존재하는 것이다.
협동체는 2-2 또는 끽해야 3-3이 보통이다. 그러나 광동체는 2-4-2, 3-3-3, 3-4-3 등의 좌석 배치가 가능하다.

세월이 흐르면서 항공 교통 시장이 커지고, 한번에 승객을 최대한 많이 태우는 비행기가 개발되어야만 했다.
허나 비행기는 무슨 열차처럼 길이를 무한정 길게 할 수 없다. 비행기를 무슨 굴절 아코디언 버스 같은 형태로 만들 수는 없는 노릇이니, 기체가 너무 길어지면 택싱 때 활주로의 최소 회전 반경에 걸리고 공항 격납고 같은 주기(駐機) 시설의 크기에도 부담을 끼친다.

그럼 높이는 어떻냐는 발상이 나온다.
육상 교통수단 중에는 2층 버스도 있고 국내엔 열차 중에 ITX-청춘 같은 2층 열차가 있다.
더구나 활주로 같은 공항 시설들도 위쪽은 뻥 뚫린 하늘이니, 비행기의 높이를 살짝 높이는 것은 항공역학적인 문제만 없으면 현실적으로 가장 제약이 덜한 시도일지도 모른다.

오늘날은 기술이 발달한 덕분에 드디어 실제로 에어버스 A380 같은 2층 여객기가 등장하기도 했다.
하지만 옛날에는 비행기에서 2층 객실은 전좌석의 비상 탈출구 설치 요구조건을 만족할 수 없어서 이마저도 여의찮았다.

민항기에는 “비상시에는 인근의 비상구를 이용하여 기내의 모든 승객이 90초 안에 밖으로 탈출이 가능하게 설계되어야 한다”라는 규정이 있다. 최근의 아시아나 항공 소속 여객기의 착륙 사고(추락 사고가 아니다)를 통해서도 이 규정의 중요성이 잘 부각되어 있듯이 말이다. 그런데 객실이 2층이 되면 이게 쉽사리 가능해질까?

그러니 길이와 높이 다음으로 비행기의 몸집을 미묘하게 더 키우기 위해 폭이 고려되었으며, 그 결과 한 줄에 10명 정도를 실을 수 있는 광동체 여객기가 개발되었다. 비행기는 무슨 열차 수준으로 폭을 꽉 맞춰야 할 필요는 없으니 말이다.

어차피 비행기의 실질적인 크기를 결정하는 것은 날개의 폭이나 수직미익의 높이 같은 극단적인 요소이다. 그런 규격을 건드리지 않는 한도 내에서 동체의 크기만 살짝 키운 것은(나머지는 엔진의 성능 같은 걸로 보강?) 기존 공항이나 격납고에서의 운용에 별다른 문제를 끼치지도 않았다고 한다.

역사상 최초로 상업용 양산에 성공한 ‘광동체’ 여객기는 그 이름도 유명한 보잉 747이다. 그러고 보니 인텔의 80x86 CPU만큼이나 보잉도 그냥 숫자만으로 제품명을 정하는 걸 좋아하는 것 같다! 경쟁사인 에어버스는 앞에A자라도 붙이는데.. (에어버스가 그럼 AMD인 거냐!)

보잉 747은 에어버스 A340, A380과 더불어 엔진이 4개 달린 얼마 안 되는 비행기이기도 하다. (외형을 보면, 날개 하나에 팬이 2개 달려서 총 4개. 단, 엔진들이 양 날개에 균일한 간격과 위치에 놓여 있지는 않은 듯.)

사용자 삽입 이미지
나머지 거의 모든 비행기들은 양쪽에 하나씩 그냥 2개이며, 요즘은 광동체급의 대형 여객기도 그러하다.
자동차 엔진도 기술이 워낙 발달해서 2000cc만으로 30년 전의 3000cc 이상급 엔진의 출력을 내는데, 이와 같은 맥락으로 보면 되겠다.

물론 A380은 워낙 덩치가 크기 때문에 응당 4엔진이다. 보잉 747을 제치고 세계에서 가장 큰 여객기의 타이틀을 차지했다.

2. A380

747은 1등석-비즈니스석을 없애고 전부 이코노미로 개조할 경우 520여 명이 탈 수 있다. 일본은 실제로 그렇게 개조를 한 뒤 747을 국내선에다 굴리고 있다.
일본은 신칸센 열차를 5분 간격으로 지하철처럼 굴리고, 지하철도 출퇴근 시간엔 좌석을 접고 모든 승객을 입석으로 만들어서 굴리기까지 하는 콩나물 시루 같은 나라이다. 비행기도 그런 식으로 운영하는 게 그다지 놀랄 일은 아닐 듯.

그런데 A380은 800에서 무려 1000명까지도 탑승이 가능하다고 한다. 이 정도면 진짜 KTX 수준이다. 그 인원을 태우고 선로 위를 달리는 게 아니라 아음속으로 하늘을 난다니..;; 747은 조종석과 특실만 2층이지만, A380은 아까도 말했듯이 실제 2층 객실이 있다.

사용자 삽입 이미지
사실, 1985년에 일본에서 JAL123 추락 사고가 난 뒤로는 단일 기체에 500명이 넘는 너무 많은 인원을 태우는 건 안전상 꺼리는 분위기가 일어났었다. 하지만 지금은 그것도 다 옛날 이야기가 됐나 보다.

3. An-225 화물기

그럼, A380보다 더 큰 비행기가 설마 있을까?
항덕이라면 이미 알고 있겠지만, 안토노프(Antonov) An-225라는 수송기가 오늘날 세계에서 가장 큰 비행기이다.
이건 크고 아름다운 걸 추구했던 구소련 시절의 산물이다. 냉전 시절에 차르 봄베라고 해서 세계에서 가장 큰 핵무기를 만들어 실험용으로 터뜨린 동네도 저기이다.

사용자 삽입 이미지

An-225는 1988년에 단 한 대밖에 생산되지 않은 명물이다.
저 정도 크기면 탱크, 우주왕복선 등.. 무거운 기계류들을 못 실을 게 없었을 것이다. 정작 미국은 거대한 자기네 우주왕복선을 747 개조 수송기로 날랐는데 말이다.
사진을 통해 알 수 있듯.. An-255는 엔진이 무려 6개가 달려 있다!

활주로에 끼치는 무게 부담을 줄이려고 랜딩기어는 7열로 늘어서 있었다.
그럼에도 불구하고 이 비행기가 한번 착륙하고 나면 어지간한 공항의 활주로는 열과 충격 때문에 남아나질 못했다고 한다. 이륙하는 데도 3km가 훨씬 넘는 긴 활주거리가 필요하다.

저게 우리나라에 올 일이 있을지는 모르겠지만, An-225급의 비행기가 무사히 뜨고 내릴 수 있는 공항은 국내에서는 인천 공항의 4km짜리 제3활주로밖에 없다고 한다. 원래 이 광활한 활주로는 A380을 모시려고 만들어진 신설 활주로이다.

4. 비행기 조종 면허

자동차의 운전 면허 체계는 최소한의 유동성이 있다. 기본적으로 대형차 면허는 소형차 면허도 덩달아 포함하는 구조이다.
그리고 면허는 차체의 크기뿐만 아니라 차량의 성격(개인용/영업용), 법적으로 책임질 수 있는 승차 인원수에도 영향을 받는다. 그렇기 때문에 1종 보통 면허로 승합차는 15인승까지밖에 못 몰지만, 트럭은 대형 버스급의 11톤까지도 몰 수 있다.

그러나 비행기 조종은 그렇지 않다. 소형이든 대형이든 무조건 단일 기종만 몰 수 있다. 747로 면허를 딴 파일럿은 오로지 747만 조종할 수 있지, 비슷한 급의 광동체 여객기라고 해서 787이나 767 같은 건 조종할 수 없다. 그렇게 조종 면허를 상호 호환시키기에는 비행기의 내부 구조가 너무 다르기 때문이 아닌가 싶다.

이런 이유로 인해 영세/저가 항공사들은 보유 기종을 무조건 보잉 737 같은 식으로 통일하는 게 필수이다. 다양한 기종이 존재하면, 골치 아파지는 게 한둘이 아니기 때문이다.
또한, 민항기 시장에 신규 업체가 진출하려면 “자사의 기체는 보잉 xxx 면허와 완전 호환” 이런 식으로 선전을 했어야 하지 않나 싶다. 과거에 에어버스가 처음 끼고 들어올 때는 어땠는지 모르겠다.

Posted by 사무엘

2013/07/10 08:38 2013/07/10 08:38
,
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/852

« Previous : 1 : ... 142 : 143 : 144 : 145 : 146 : 147 : 148 : 149 : 150 : ... 221 : Next »

블로그 이미지

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

- 사무엘

Archives

Authors

  1. 사무엘

Calendar

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

Site Stats

Total hits:
3050049
Today:
1069
Yesterday:
2142