멜 이야기는 초기 컴퓨터 시대의 기계어 프로그래밍이 단순한 옛날 무용담이 아니라, 하드웨어 구조와 프로그래머의 미학이 거의 같은 층위에서 움직이던 시절의 기록이라는 점에서 여전히 강하다.
역자 주: 처음에는 Mel’s Loop 쪽에 한국어 번역과 주석판을 기여하려고 했지만, 연락이 닿지 않아 우선 이 블로그에 비공식 번역본으로 정리해 둔다. 원문은 Ed Nather가 1983년 5월 21일 Usenet에 게시한 The Story of Mel, a Real Programmer이며, University of Utah 보존본도 함께 참고할 수 있다.

멜 이야기, 진짜 프로그래머
이 글은 저자인 에드 네이더(utastro!nather)가 1983년 5월 21일 유즈넷에 올린 것이다.
[첨삭]
유즈넷은 웹 이전 시대의 인터넷 게시판 같은 공간이다. 지금의 Reddit, mailing list, 개발자 포럼이 섞인 것처럼 보면 된다. 이 글은 1983년에 올라온 해커 문화의 고전적인 글로 널리 퍼졌다.
프로그래밍의 마초적인 면에 관한 최근의 한 글은 대담하고 꾸밈없는 진술을 했다.
진짜 프로그래머는 FORTRAN으로 작성한다.
아마 지금은 그럴지도 모른다.
라이트 맥주, 휴대용 계산기, 그리고 “사용자 친화적” 소프트웨어의 이 퇴폐적인 시대에는 말이다.
하지만 좋았던 옛날에는,
“소프트웨어”라는 말이 우습게 들리던 시절,
그리고 진짜 컴퓨터가 드럼과 진공관으로 만들어지던 시절에는,
진짜 프로그래머는 기계어로 작성했다.
FORTRAN이 아니었다.
RATFOR도 아니었다.
심지어 어셈블리 언어도 아니었다.
기계어였다.
날것 그대로의, 장식 없는, 이해하기 어려운 16진수 숫자들.
직접.
[첨삭]
여기서 “드럼”은 악기가 아니라 자기 드럼 메모리다. 초기 컴퓨터는 지금의 RAM처럼 원하는 주소를 즉시 읽는 방식이 아니었다. 원통형 금속 드럼이 계속 돌고 있었고, 그 표면에 데이터와 명령어가 저장되어 있었다. 읽기 헤드가 특정 위치를 지나갈 때만 그 데이터를 읽을 수 있었다.
그래서 당시 프로그래머는 “어떤 명령을 실행할지”뿐 아니라, 그 명령이 드럼이 회전하는 어느 순간에 읽기 헤드 밑으로 오는지까지 고려해야 했다.
“16진수 숫자를 직접 쓴다”는 말은, 사람이 읽기 쉬운 ADD, JMP, LOAD 같은 어셈블리 명령도 쓰지 않고, CPU가 이해하는 숫자값 자체를 직접 적어 넣었다는 뜻이다.
예를 들어 현대적인 감각으로 보면 다음과 비슷하다.
ADD R1, 100
JMP 250
LOAD R2, 300
이렇게 쓰는 것이 아니라, 해당 명령어의 실제 숫자값을 알고 있어서 다음과 같은 숫자를 직접 다루는 방식이다.
A3F4
0C20
9B10
새로운 세대의 프로그래머 전체가
이 영광스러운 과거를 모른 채 자라나지 않도록,
나는 세대 차이를 넘어 내가 할 수 있는 한 최선을 다해
진짜 프로그래머가 어떻게 코드를 작성했는지 설명해야 할 의무를 느낀다.
나는 그를 멜이라고 부르겠다.
그것이 그의 이름이었기 때문이다.
나는 로열 맥비 컴퓨터 회사에 일하러 갔을 때 멜을 처음 만났다.
그 회사는 타자기 회사의, 지금은 사라진 자회사였다.
그 회사는 LGP-30을 제조했다.
그 당시 기준으로는 작고 저렴한
드럼 메모리 컴퓨터였다.
그리고 막 RPC-4000을 제조하기 시작한 참이었다.
훨씬 개선된,
더 크고, 더 좋고, 더 빠른 — 드럼 메모리 컴퓨터였다.
코어 메모리는 너무 비쌌고,
어차피 오래 남을 것도 아니었다.
그래서 당신이 그 회사나 그 컴퓨터에 대해 들어본 적이 없는 것이다.
[첨삭]
여기서 “코어”는 CPU 코어가 아니라 자기 코어 메모리를 말한다. 작은 자성 고리들을 이용한 메모리 기술이다. 드럼 메모리보다 빠르고 현대적인 방식이었지만, 당시에는 비쌌다.
글의 농담은 이렇다. 글쓴이는 “코어 메모리는 너무 비싸고 오래가지 않을 것 같았다”고 말하지만, 실제 역사에서는 드럼 메모리 쪽이 사라지고 코어 메모리가 한동안 주류가 되었다. 즉, 당시의 판단이 틀렸다는 아이러니가 들어 있다.
나는 이 새로운 경이로운 기계를 위한 FORTRAN 컴파일러를 작성하기 위해 고용되었고,
멜은 그 경이로움으로 나를 안내해 주는 사람이었다.
멜은 컴파일러를 인정하지 않았다.
“프로그램이 자기 자신의 코드를 다시 쓸 수 없다면,”
그가 물었다.
“그게 무슨 쓸모가 있지?”
[첨삭]
이 말은 **자기 수정 코드(self-modifying code)**를 가리킨다. 프로그램이 실행 중에 자기 자신의 명령어를 바꾸는 방식이다.
현대 소프트웨어 개발에서는 자기 수정 코드는 대체로 위험하고 유지보수하기 어려운 기법으로 본다. 보안상으로도 좋지 않다. 하지만 초기 컴퓨터에서는 메모리가 부족했고, 하드웨어 기능도 제한적이어서 자기 수정 코드가 실용적인 기법이기도 했다.
예를 들어 배열을 순회할 때 오늘날에는 i++처럼 인덱스를 증가시키지만, 당시에는 명령어 자체의 주소 부분을 직접 바꿔서 다음 데이터를 가리키게 만들 수 있었다.
멜은 16진수로
그 회사가 소유한 가장 인기 있는 컴퓨터 프로그램을 작성했다.
그 프로그램은 LGP-30에서 실행되었고
컴퓨터 전시회에서 잠재 고객들과 블랙잭을 했다.
그 효과는 언제나 극적이었다.
모든 전시회에서 LGP-30 부스는 사람들로 가득 찼고,
IBM 영업사원들은 서로 둘러서서
서로 이야기하고 있었다.
이것이 실제로 컴퓨터 판매로 이어졌는지 아닌지는
우리가 한 번도 논의하지 않은 문제였다.
[첨삭]
이 장면은 기술 전시회의 풍경이다. 당시에는 컴퓨터가 매우 낯선 기계였기 때문에, 컴퓨터가 사람과 블랙잭을 한다는 것 자체가 큰 볼거리였다. IBM은 이미 강력한 경쟁자였고, 로열 맥비 같은 회사는 관람객의 관심을 끌기 위해 이런 시연 프로그램이 필요했다.
멜의 일은
RPC-4000용으로 블랙잭 프로그램을 다시 작성하는 것이었다.
포팅이라고? 그게 무슨 뜻이지?
[첨삭]
“포팅(porting)”은 한 시스템에서 돌아가던 프로그램을 다른 시스템에서도 돌아가게 옮기는 작업이다.
그런데 여기서는 농담이 있다. 오늘날에는 포팅이라는 말이 자연스럽지만, 당시에는 컴퓨터마다 구조가 너무 달랐다. 특히 멜은 고급 언어나 어셈블리 수준이 아니라 기계어 숫자와 드럼 위치까지 직접 다루고 있었기 때문에, 단순히 “옮긴다”는 개념이 거의 성립하지 않았다. 사실상 새 기계에 맞춰 다시 짜야 했다.
새 컴퓨터는 one-plus-one 주소 지정 방식을 가지고 있었다.
각 기계어 명령은
연산 코드와
필요한 피연산자의 주소에 더해,
회전하는 드럼의 어느 위치에
다음 명령이 있는지를 나타내는 두 번째 주소를 가지고 있었다.
현대식 표현으로 말하자면,
모든 단일 명령 뒤에는 GO TO가 따라붙는 셈이었다!
그걸 파스칼의 파이프에 넣고 피워 보라고 해라.
[첨삭]
이 부분이 글에서 가장 중요한 배경이다.
일반적으로 명령어는 이렇게 생각하기 쉽다.
연산 코드 + 데이터 주소
예를 들면 “100번지의 값을 더하라” 같은 식이다.
하지만 RPC-4000은 명령어 안에 다음 정보까지 들어 있었다.
연산 코드 + 데이터 주소 + 다음 명령어 주소
즉, 한 명령어가 “무슨 일을 할지”, “어떤 데이터를 쓸지”, “다음에는 어디로 갈지”를 모두 직접 갖고 있었다.
현대 CPU는 보통 한 명령을 실행한 뒤 다음 주소의 명령을 자동으로 읽는다. 하지만 이 기계에서는 매 명령어가 다음 명령어 위치를 직접 지정했다. 그래서 글쓴이는 “모든 명령 뒤에 GO TO가 붙어 있는 셈”이라고 말한다.
“파스칼의 파이프에 넣고 피워 보라”는 말은 구조적 프로그래밍을 중시하던 Pascal 문화에 대한 농담이다. Pascal은 무분별한 goto 사용을 나쁘게 보는 분위기와 연결되어 있었다. 그런데 이 기계는 구조 자체가 모든 명령어마다 goto를 품고 있었던 셈이다.
멜은 RPC-4000을 사랑했다.
왜냐하면 그는 자기 코드를 최적화할 수 있었기 때문이다.
다시 말해, 한 명령이 자기 일을 막 끝내는 바로 그 순간
다음 명령이 “읽기 헤드”에 도착해
즉시 실행 가능하도록
드럼 위에 명령들을 배치할 수 있었기 때문이다.
그 일을 해 주는 프로그램,
즉 “최적화 어셈블러”가 있기는 했다.
하지만 멜은 그것을 쓰기를 거부했다.
“그게 물건들을 어디에 놓을지 결코 알 수 없잖아,”
그가 설명했다.
“그러면 별도의 상수들을 써야 하거든.”
내가 그 말을 이해하기까지는 오랜 시간이 걸렸다.
[첨삭]
드럼 메모리에서는 명령어가 어디에 놓이느냐가 성능에 직접 영향을 준다. 드럼이 계속 돌고 있으므로, 다음 명령어가 읽기 헤드 아래에 바로 도착하면 빠르다. 반대로 방금 지나가 버린 위치에 있으면 드럼이 거의 한 바퀴 돌 때까지 기다려야 한다.
최적화 어셈블러는 이런 배치를 자동으로 해 주는 도구였다. 그런데 멜은 그것을 믿지 않았다. 이유는 그가 명령어를 단순한 명령어로만 보지 않았기 때문이다.
멜에게 명령어는 동시에 숫자 상수였다. 어떤 명령어의 16진수 값이 계산에 필요한 숫자로도 쓰일 수 있었다. 그런데 어셈블러가 명령어 위치를 바꾸면 주소 부분이 바뀌고, 그러면 명령어 전체 숫자값도 달라진다. 그래서 멜의 트릭이 깨질 수 있었다.
멜은 모든 연산 코드의 숫자 값을 알고 있었고,
자기 자신의 드럼 주소를 할당했기 때문에,
그가 작성한 모든 명령은 동시에
하나의 숫자 상수로도 간주될 수 있었다.
그는 예컨대 앞서 나온 “add” 명령 하나를 집어 들고,
그것의 숫자 값이 알맞다면
그것으로 곱셈을 할 수도 있었다.
그의 코드는 다른 누군가가 수정하기 쉬운 것이 아니었다.
[첨삭]
이것은 현대 프로그래밍 관점에서 보면 매우 위험한 방식이다. 코드와 데이터가 강하게 분리되어 있지 않고, 명령어 자체를 숫자 데이터처럼 재사용하기 때문이다.
예를 들어 오늘날 코드에서 x = 3이라는 상수 대신, 우연히 메모리에 들어 있는 어떤 명령어 바이트값이 3과 같으니 그것을 가져다 쓰는 방식에 가깝다. 작성자는 모든 의미를 알고 있지만, 다른 사람은 거의 해독하기 어렵다.
나는 멜이 손으로 최적화한 프로그램들을
최적화 어셈블러 프로그램이 손질한 동일한 코드와 비교해 보았다.
그리고 멜의 프로그램은 언제나 더 빨리 실행되었다.
그 이유는 “하향식” 프로그램 설계 방법이
아직 발명되지 않았기 때문이었고,
설령 발명되었더라도 멜은 어차피 그것을 사용하지 않았을 것이기 때문이다.
그는 프로그램 루프의 가장 안쪽 부분들을 먼저 작성했다.
그 부분들이 드럼에서 최적의 주소 위치를
가장 먼저 선택할 수 있도록 하기 위해서였다.
최적화 어셈블러는 그렇게 할 만큼 똑똑하지 않았다.
[첨삭]
“하향식 설계”는 큰 구조를 먼저 잡고 세부 구현으로 내려가는 방식이다. 현대 소프트웨어 공학에서는 자연스러운 접근이다.
하지만 멜은 반대로 가장 자주 실행되는 안쪽 루프부터 짰다. 성능에 가장 큰 영향을 주는 부분이기 때문이다. 드럼 메모리에서는 그 루프의 명령어들이 가장 좋은 위치를 차지해야 했다.
즉, 멜은 프로그램을 논리 구조 중심이 아니라 물리적 실행 시간 중심으로 배치했다. 오늘날로 치면 CPU 캐시, 분기 예측, 파이프라인까지 사람이 직접 고려해 어셈블리 코드를 배치하는 것과 비슷하다.
멜은 시간 지연 루프도 작성하지 않았다.
말썽 많은 플렉소라이터가 제대로 작동하려면
출력 문자들 사이에 지연이 필요할 때조차도 그랬다.
그는 단지 명령들을 드럼 위에 배치했다.
각각의 다음 명령이 필요해지는 순간
읽기 헤드를 막 지나간 위치에 오도록 말이다.
그러면 드럼은 다음 명령을 찾기 위해
또 한 바퀴를 완전히 돌아야 했다.
그는 이 절차에 잊을 수 없는 용어를 붙였다.
“최적”이라는 말은 “유일한”과 마찬가지로 절대적인 용어이지만,
그것을 상대적인 말처럼 쓰는 것이
일상적인 말버릇이 되었다.
“완전히 최적은 아닌,” 또는 “덜 최적인,”
또는 “별로 최적이지 않은” 같은 식으로 말이다.
멜은 최대 시간 지연 위치들을
“가장 비관적”이라고 불렀다.
[첨삭]
플렉소라이터는 키보드와 프린터가 결합된 입출력 장치로 보면 된다. 기계식 장치라서 문자를 너무 빨리 보내면 제대로 처리하지 못했다. 그래서 문자 사이에 지연 시간이 필요했다.
보통은 다음처럼 지연 루프를 만든다.
아무 일도 하지 않는 명령을 여러 번 반복한다.
그동안 시간이 흐른다.
하지만 멜은 그런 루프를 만들지 않았다. 대신 다음 명령어가 읽기 헤드를 막 지나간 위치에 놓이도록 했다. 그러면 드럼이 한 바퀴 더 돌아야 해서 자연스럽게 지연이 생긴다.
“most pessimum”은 말장난이다. optimum이 “최적”이라면, pessimum은 억지로 만든 “최악”에 가까운 말이다. 멜은 가장 긴 지연을 만드는 위치를 이렇게 불렀다.
그가 블랙잭 프로그램을 끝내고
그것이 실행되게 만든 뒤,
“초기화 루틴까지 최적화되어 있어,”
그가 자랑스럽게 말했다.
그는 영업부로부터 변경 요청을 받았다.
그 프로그램은 우아한, 최적화된 난수 생성기를 사용해
“카드들”을 섞고 “덱”에서 카드를 나누었다.
그리고 일부 영업사원들은 그것이 너무 공정하다고 느꼈다.
왜냐하면 가끔 고객들이 졌기 때문이다.
그들은 멜이 프로그램을 수정해 주기를 원했다.
콘솔의 센스 스위치를 설정하면
그들이 확률을 바꾸어 고객이 이기게 할 수 있도록 말이다.
멜은 반발했다.
그는 이것이 명백히 부정직한 일이라고 느꼈다.
그리고 실제로 그랬다.
또 그것이 프로그래머로서 자신의 개인적 진실성을 침해한다고 느꼈다.
그리고 실제로 그랬다.
그래서 그는 그것을 하기를 거부했다.
[첨삭]
센스 스위치는 컴퓨터 콘솔에 달린 물리적 스위치다. 프로그램이 이 스위치의 ON/OFF 상태를 읽어 실행 방식을 바꿀 수 있었다.
영업부의 요구는 “고객이 이기게 해서 기분 좋게 만들자”는 것이었다. 겉으로는 고객 친화적인 것처럼 보이지만, 사실은 시연 결과를 조작하는 것이다. 멜은 이를 프로그래머 윤리에 어긋난다고 본다.
수석 영업사원이 멜과 이야기했다.
큰 보스도 그렇게 했고,
보스의 재촉에 따라 몇몇 동료 프로그래머들도 그렇게 했다.
멜은 결국 굴복하여 코드를 작성했다.
하지만 그는 테스트를 반대로 만들어 버렸다.
그래서 센스 스위치를 켜면,
프로그램은 속임수를 써서 매번 이기게 되었다.
멜은 이것을 매우 기뻐했다.
그는 자신의 잠재의식이 억제할 수 없을 만큼 윤리적이라고 주장했고,
그것을 고치기를 완강히 거부했다.
[첨삭]
여기서 “테스트”는 조건문을 뜻한다. 예를 들면 이런 식이다.
스위치가 켜져 있으면 고객이 이기게 한다.
멜은 이것을 반대로 만든 것이다.
스위치가 켜져 있으면 컴퓨터가 이기게 한다.
그가 “잠재의식이 윤리적이었다”고 말한 것은 농담이다. 겉으로는 실수라고 주장하지만, 실제로는 의도적으로 반대로 했을 가능성이 강하게 암시된다.
멜이 더 푸른 pa$ture$를 찾아 회사를 떠난 뒤,
큰 보스는 나에게 코드를 들여다보고
그 테스트를 찾아서 뒤집을 수 있는지 봐 달라고 했다.
다소 내키지 않았지만, 나는 살펴보기로 동의했다.
[첨삭]
원문의 greener pa$ture$는 greener pastures, 즉 “더 좋은 기회”라는 표현을 비튼 것이다. s를 $로 바꿔 돈을 암시한다. 따라서 “더 나은 보수나 조건을 찾아 떠났다”는 농담이다.
멜의 코드를 추적하는 일은 진정한 모험이었다.
나는 종종 프로그래밍이 하나의 예술 형식이라고 느껴 왔다.
그 진정한 가치는
같은 신비한 예술에 정통한 다른 사람만이 감상할 수 있는 그런 예술 말이다.
그 과정의 본질 자체 때문에
때로는 영원히,
인간의 시야와 감탄으로부터 숨겨진
사랑스러운 보석들과 눈부신 묘수들이 있다.
당신은 한 개인에 대해 많은 것을 배울 수 있다.
그의 코드를 읽어 내려가는 것만으로도 말이다.
심지어 16진수로 된 코드라 해도.
멜은, 내 생각에, 이름 없이 묻힌 천재였다.
[첨삭]
이 대목은 글의 감정적 중심이다. 글쓴이는 멜의 코드를 “유지보수하기 어려운 나쁜 코드”로만 보지 않는다. 그 안에는 하드웨어에 대한 깊은 이해, 미학, 고집, 윤리가 모두 들어 있다고 본다.
현대 개발 기준으로는 멜의 코드는 협업과 유지보수에 매우 나쁘다. 하지만 이 글은 그와 동시에, 특정 시대의 특정 기계에서만 가능한 극단적인 장인정신을 보여 준다.
아마도 나에게 가장 큰 충격은
테스트가 전혀 없는 순진해 보이는 루프를 발견했을 때였을 것이다.
테스트가 없었다.
하나도 없었다.
상식적으로는 그것이 닫힌 루프여야 했다.
프로그램이 영원히, 끝없이, 그 안을 돌고 있어야 하는 그런 루프 말이다.
그러나 프로그램 제어는 그 루프를 그대로 통과해
다른 쪽으로 안전하게 빠져나갔다.
그것을 알아내는 데 나는 2주가 걸렸다.
[첨삭]
보통 루프에는 종료 조건이 있다.
i가 10보다 작으면 계속 반복한다.
i가 10이 되면 루프를 빠져나간다.
그런데 멜의 루프에는 이런 조건 검사가 없었다. 겉으로 보면 무한 루프여야 했다. 그런데 실제 실행에서는 빠져나갔다.
이것이 글의 기술적 절정이다. 멜은 조건문으로 루프를 끝낸 것이 아니라, 명령어 주소의 오버플로를 이용해 루프가 스스로 다른 명령으로 변하게 만들었다.
RPC-4000 컴퓨터에는
인덱스 레지스터라고 불리는 정말 현대적인 기능이 있었다.
그것은 프로그래머가 내부에 인덱스된 명령을 사용하는
프로그램 루프를 작성할 수 있게 해 주었다.
루프를 한 번 돌 때마다,
인덱스 레지스터에 있는 숫자가
그 명령의 주소에 더해졌다.
그래서 그 명령은
연속된 데이터 가운데 다음 데이터를 참조하게 되었다.
그는 루프를 돌 때마다
인덱스 레지스터만 증가시키면 되었다.
멜은 그것을 전혀 사용하지 않았다.
[첨삭]
인덱스 레지스터는 배열을 처리하기 쉽게 해 주는 장치다.
예를 들어 데이터가 이렇게 있다고 하자.
100번지: 첫 번째 카드
101번지: 두 번째 카드
102번지: 세 번째 카드
인덱스 레지스터를 쓰면 기본 주소 100에 인덱스 값을 더해 다음 데이터를 차례로 읽을 수 있다.
100 + 0
100 + 1
100 + 2
그런데 멜은 이 편리한 기능을 쓰지 않았다. 그는 더 직접적인 방법, 즉 명령어 자체를 바꾸는 방법을 사용했다.
대신 그는 명령을 기계 레지스터 안으로 끌어왔고,
그 주소에 1을 더한 뒤,
그것을 다시 저장했다.
그런 다음 그는 수정된 명령을
바로 그 레지스터에서 실행했다.
그 루프는 이 추가 실행 시간이
고려되도록 작성되어 있었다.
이 명령이 막 끝나는 바로 그 순간,
다음 명령이 드럼의 읽기 헤드 바로 아래에 있었고,
실행될 준비가 되어 있었다.
하지만 그 루프에는 테스트가 없었다.
[첨삭]
멜은 인덱스 값을 따로 두지 않고, 명령어 안의 주소 자체를 바꿨다.
현대식으로 비유하면 이렇다.
일반적인 방식:
LOAD data[i]
i = i + 1
멜의 방식에 가까운 방식:
LOAD data[100]
다음 실행 전에는 위 명령 자체를 LOAD data[101]로 바꾼다.
그 다음에는 LOAD data[102]로 바꾼다.
그리고 이 자기 수정에 걸리는 시간까지 드럼 회전 타이밍에 맞춰 계산했다. 그래서 다음 명령이 정확히 읽기 헤드 아래에 오게 했다.
결정적인 단서는 내가
인덱스 레지스터 비트가 켜져 있다는 것을 알아차렸을 때 왔다.
그 비트는 명령어 워드 안에서
주소와 연산 코드 사이에 놓여 있는 비트였다.
그런데 멜은 인덱스 레지스터를 전혀 사용하지 않았고,
항상 0으로 남겨 두었다.
불이 들어온 순간, 그것은 거의 내 눈을 멀게 할 정도였다.
[첨삭]
여기서 “불이 들어왔다”는 표현은 깨달음의 순간을 말한다. 실제 전구가 켜졌다는 뜻이 아니라, “아, 이거였구나!” 하고 이해했다는 뜻이다.
멜은 인덱스 레지스터를 사용하지 않으면서도, 그 비트가 켜져 있도록 해 두었다. 이 비트의 위치가 중요했다. 주소 부분과 연산 코드 사이에 있었기 때문이다.
즉, 주소가 계속 증가하다가 넘치면, 그 넘친 값이 연산 코드 쪽으로 영향을 줄 수 있었다.
그는 자신이 작업하고 있던 데이터를
메모리의 맨 위쪽 가까이에 배치해 두었다.
명령들이 주소 지정할 수 있는 가장 큰 위치들에 말이다.
그래서 마지막 데이터가 처리된 뒤,
명령 주소를 증가시키면
그것은 오버플로를 일으키게 되어 있었다.
그 자리올림은
연산 코드에 1을 더했고,
그것을 명령어 집합에서 다음 명령으로 바꾸었다.
점프 명령으로 말이다.
아니나 다를까, 다음 프로그램 명령은
주소 위치 0에 있었고,
프로그램은 기분 좋게 제 갈 길을 계속 갔다.
[첨삭]
이 부분을 단계별로 풀면 다음과 같다.
멜은 데이터를 메모리의 끝부분에 배치했다.
최대 주소 근처:
...
997번지: 데이터
998번지: 데이터
999번지: 마지막 데이터
명령어는 매번 자기 주소 부분을 1씩 증가시키며 다음 데이터를 처리했다.
처음: 997번지를 처리
다음: 998번지를 처리
다음: 999번지를 처리
그런데 마지막 주소인 999번지 다음으로 1을 더하면 더 이상 표현할 수 있는 주소가 없다.
999 + 1 = 000으로 돌아감 + 자리올림 발생
이 자리올림이 연산 코드 부분으로 넘어가면서, 명령어 종류 자체가 바뀐다.
원래는 데이터를 처리하는 명령이었는데, 오버플로 뒤에는 점프 명령이 된다.
데이터 처리 명령 → 점프 명령
그리고 점프 대상은 주소 0이다. 그래서 루프는 별도의 조건 검사 없이 빠져나간다.
즉, 멜은 if 문도, 비교 명령도, 종료 조건도 쓰지 않았다. 대신 메모리 주소 오버플로가 명령어 자체를 바꾸도록 설계했다.
나는 멜과 연락을 유지하지 않았기 때문에,
그가 그 오래전에 지나간 시절 이후
프로그래밍 기법 위로 밀려든 변화의 홍수에
끝내 굴복했는지는 알지 못한다.
나는 그가 그러지 않았다고 생각하고 싶다.
어쨌든,
나는 충분히 감명받아서
문제가 된 테스트를 찾는 일을 그만두었고,
큰 보스에게 그것을 찾을 수 없었다고 말했다.
그는 놀라는 것 같지 않았다.
내가 회사를 떠났을 때,
블랙잭 프로그램은 여전히 속임수를 썼다.
올바른 센스 스위치를 켜면 말이다.
그리고 나는 그것이 그래야 한다고 생각한다.
나는 진짜 프로그래머의 코드를
마구 해킹해 놓는 것이 편하지 않았다.
[첨삭]
마지막 문장은 이 글의 핵심 정서다.
화자는 코드를 고치지 않았다. 단순히 못 고친 것이 아니라, 고치고 싶지 않았던 것이다. 멜의 코드는 현대 기준으로 보면 유지보수하기 어렵고 위험한 코드다. 하지만 그 안에는 하드웨어에 대한 완전한 이해, 극단적인 최적화, 그리고 프로그래머로서의 윤리가 들어 있었다.
그래서 화자는 그 코드를 버그 덩어리로 보지 않고, 하나의 예술 작품처럼 본다.
이 글이 해커 문화에서 오래 살아남은 이유도 여기에 있다. 이 이야기는 “옛날 프로그래머가 무조건 더 뛰어났다”는 단순한 향수가 아니다. 오히려 특정 시대의 하드웨어와 소프트웨어가 얼마나 가까웠는지, 그리고 그 경계 위에서 프로그래머가 얼마나 기묘하고 아름다운 일을 할 수 있었는지를 보여 주는 이야기다.
이것은 자유시 형식이든 아니든, 해커 세계의 위대한 영웅 서사시 가운데 하나다. 몇 개의 간결한 이미지 속에서, 그것은 해킹의 미학과 심리에 대해 이 주제에 관한 모든 학술서들을 합친 것보다 더 많은 것을 포착한다.
[1992년 추신 — 저자는 이렇게 썼다. “원래 네트워크에 제출된 글은 자유시도 아니었고, 그것에 가까운 어떤 형태도 아니었다. 그것은 줄맞춤이 되지 않은 문단들로 된 평범한 산문 형식이었다. 네트워크를 떠돌아다니는 동안 그것은 분명 지금 널리 알려진 ‘자유시’ 형식으로 수정된 것 같다. 다시 말해, 그것은 네트워크 위에서 해킹당한 것이다. 어쩐지 그것이 적절해 보인다.”]
[첨삭]
이 추신도 매우 중요하다. 지금 널리 퍼진 버전은 줄바꿈이 많은 “자유시”처럼 보인다. 하지만 저자에 따르면 원래는 평범한 산문이었다. 누군가가 네트워크를 통해 퍼뜨리면서 형식을 바꿨고, 그 결과 지금의 유명한 형태가 되었다.
즉, 이 글 자체도 “해킹”당했다. 글쓴이는 그것이 오히려 이 이야기와 잘 어울린다고 말한다.
전체 기술 요약
이 글에서 이해해야 할 핵심은 다음이다.
멜은 단순히 코드를 잘 짠 사람이 아니다. 그는 다음 네 가지를 동시에 다루었다.
- CPU 명령어의 숫자값
- 드럼 메모리의 물리적 회전 타이밍
- 명령어 안의 비트 배치
- 자기 수정 코드와 오버플로의 부작용
현대 프로그래밍에서는 이런 코드는 거의 금지된 마법에 가깝다. 협업도 어렵고, 유지보수도 어렵고, 보안에도 좋지 않다.
하지만 이 글이 말하는 “진짜 프로그래머”는 현대적 모범 개발자라기보다, 기계의 내부 구조를 거의 물리적으로 이해하고 그것을 직접 조작하던 시대의 장인이다.
그래서 이 글은 기술적으로는 낡았지만, 문화적으로는 여전히 강하다.
프로그래밍이 단순한 업무가 아니라, 때로는 기계와 인간 사이의 은밀한 예술이 될 수 있다는 믿음을 담고 있기 때문이다.
용어집
| English term | 한국어 번역 | 설명 |
|---|---|---|
| Real Programmer | 진짜 프로그래머 | 원문 특유의 풍자와 존경이 섞인 표현이다. |
| FORTRAN | FORTRAN | 초기 고급 프로그래밍 언어. |
| RATFOR | RATFOR | Rational FORTRAN. FORTRAN에 구조적 프로그래밍 요소를 더한 전처리 언어. |
| machine code | 기계어 | CPU가 직접 실행하는 숫자 명령어. |
| hexadecimal | 16진수 | 0-9와 A-F로 숫자를 표현하는 방식. |
| drum memory | 드럼 메모리 / 자기 드럼 메모리 | 회전하는 원통형 자기 저장장치를 이용한 초기 메모리. |
| vacuum tubes | 진공관 | 트랜지스터 이전 시대의 전자 부품. |
| core memory | 코어 메모리 / 자기 코어 메모리 | 작은 자성 고리를 이용한 초기 RAM 계열 메모리. |
| compiler | 컴파일러 | 고급 언어를 기계어 등으로 변환하는 프로그램. |
| self-modifying code | 자기 수정 코드 | 실행 중에 자기 자신의 명령어를 바꾸는 코드. |
| LGP-30 | LGP-30 | Royal McBee가 만든 초기 드럼 메모리 컴퓨터. |
| RPC-4000 | RPC-4000 | LGP-30 이후의 드럼 메모리 컴퓨터. |
| port | 포팅 | 프로그램을 다른 기계나 환경으로 옮기는 작업. |
| one-plus-one addressing | one-plus-one 주소 지정 | 명령어에 피연산자 주소와 다음 명령어 주소가 함께 들어가는 방식. |
| operation code | 연산 코드 | 명령어가 수행할 동작을 나타내는 부분. |
| operand | 피연산자 | 명령어가 처리할 데이터 또는 데이터 주소. |
| read head | 읽기 헤드 | 드럼 표면의 데이터를 읽는 장치. |
| optimizing assembler | 최적화 어셈블러 | 명령어 배치를 최적화해 주는 어셈블러. |
| top-down design | 하향식 설계 | 큰 구조에서 세부 구현으로 내려가는 설계 방식. |
| Flexowriter | 플렉소라이터 | 초기 컴퓨터 입출력용 타자기/프린터 장치. |
| time-delay loop | 시간 지연 루프 | 시간을 끌기 위해 일부러 반복하는 루프. |
| optimum | 최적 | 가장 좋은 상태. |
| pessimum | 최악점 / 비최적 | optimum을 비튼 말장난. |
| sense switch | 센스 스위치 | 콘솔에 달린 물리적 입력 스위치. |
| index register | 인덱스 레지스터 | 주소 계산에 쓰이는 레지스터. |
| instruction word | 명령어 워드 | 하나의 기계어 명령을 구성하는 비트 묶음. |
| overflow | 오버플로 | 표현 가능한 범위를 넘는 현상. |
| carry | 자리올림 | 덧셈 등에서 다음 자리로 넘어가는 값. |
| jump instruction | 점프 명령 | 프로그램 실행 위치를 바꾸는 명령. |
| hacker folklore | 해커 민속 | 해커 문화 안에서 전승되는 이야기와 일화. |