메아리

Tour de IOCCC: 1988/spinellis

#include "/dev/tty"

main 함수가 없는 프로그램은 일반적으로 Best abuse of the rules라는 이름으로 수상하게 마련입니다. 이번 프로그램도 마찬가지입니다.

뭘 하는 프로그램인가?

언제나 그렇듯 실행해 보는 게 장땡입니다. 이번 프로그램에서는 유닉스 환경이 무조건 필요합니다.

$ gcc spinellis.c -o spinellis

...프롬포트가 안 나옵니다! 지금껏 컴파일하면서 뭘 입력하라고 나온 적은 한 번도 없었는데 이게 무슨 일일까요? 한 번 아무 거나 넣어 보겠습니다.

foo bar foo bar
^D
In file included from t.c:1:
/dev/tty:1: error: syntax error before ‘bar’
$

(위에 나온 ^D는 Ctrl-D로 입력해야 합니다. 이 문자는 유닉스에서 현재 입력을 끝내라는 뜻으로 쓰입니다.)

왠지 우리가 입력한 게 소스 코드로 처리된 듯이 에러가 나왔네요. 한 번 간단한 Hello, world 프로그램을 넣어 봅시다.

$ gcc spinellis.c -o spinellis
#include <stdio.h>
int main(void) {
    printf("Hello, world!\n");
    return 0;
}
^D
$ ./spinellis 
Hello, world!

볼 수 있듯이 이 프로그램은 컴파일 도중에 사용자에게 소스 코드를 받아서-_- 컴파일해 주는 프로그램이 되겠습니다.

왜 동작하는가?

#include 뒤에 나오는 파일 이름은 실제 파일 시스템과 전혀 대응이 안 될 수도 있습니다만, 거의 모든 컴파일러가 실제 파일 시스템과 대응을 시킵니다. 따라서 이 프로그램은 /dev/tty라는 파일로부터 소스 코드를 읽으려고 하지요.

/dev/ 아래에 있는 파일들은 흔히 유닉스의 철학, "모든 것은 파일이다"를 보여 주는 예로 쓰입니다. 예를 들어서 /dev/random은 랜덤한 문자를 출력하는데, ssh 같이 완전히 랜덤한 데이터를 필요로 하는 곳에서 사용할 수 있습니다. 물론 실제 파일이 아니라 운영체제가 제공해 주는 파일 비스무리한 뭔가죠. /dev/tty도 마찬가지로 현재 사용자가 사용하고 있는 터미널을 직접 접근하는 데 쓰는 파일이 됩니다. 따라서,

#include "/dev/tty"

는 컴파일러가 터미널에서 입력되는 파일을 삽입하도록 하게 합니다.

윈도에서도 비슷한 흉내를 낼 수 있습니다. 예를 들어,

#include "con"

은 윈도용 GCC에서 정상적으로 컴파일되고 똑같은 역할을 합니다. (CON은 윈도에서 현재 콘솔을 나타내는 특수한 파일 이름입니다. C:\NUL\NUL을 기억하세요?1) 어쩌면,

#ifdef _WIN32
#include "con"
#else
#include "/dev/tty"
#endif

같은 식으로 윈도와 유닉스 모두 동작하는 프로그램을 짤 수도 있겠습니다.

아무튼, 이런 종류의 프로그램이 더 나오는 것을 막기 위해서 다음 해 IOCCC 규칙에는 사람의 손 없이 자동으로 컴파일이 끝나야 한다는 조건이 추가되었습니다. 하지만 몇년 후에는 애초에 컴파일이 되지 않는 프로그램이 나와서 심사위원들을 당황케 했죠.


  1. 윈도 95, 98 등의 옛날 운영체제에서는 이런 경로를 만나면 운영체제가 재부팅하는 버그가 있었습니다. 특수한 파일 이름에 대한 예외 처리를 덜 해 놓은 탓이었죠.


Copyright © 1999–2009, Kang Seonghoon.