This post is part of a content series coming out of a partnership betweenSweetcode.io and Holberton School.
In 1988, the International Obfuscated C Code Contest (IOCCC) decided a winner under the category of “Best Abuse of the Rules.” The C code that was submitted was a single line: #include “/dev/tty”.¹
The author, Diomidis Spinellis, had this to say about his code:
“This program can be configured to do almost everything. The configuration is done during compile time by typing in C code what one would like the program to perform. A trivial example is given in the ‘how to compile’ section, but the possibilities are clearly limited only by your imagination and programming skills.”
To which the competition had this polite response:
“We enjoyed this entry this year, however in the future, programs must be able to be compiled from within a makefile without the need of human intervention.²”
The compilation process is as follows: gcc spinellis.c
The user manually types in the C program to be compiled followed by an EOF character ^D.
When run, the program we’ve configured to print “Hello, World!” does just that.
The C pre-processor’s role:
One of the functions of the pre-processor is to insert the contents of a file determined by #include. The #include directive is almost always at the beginning of the file. The compiler requires header file contents when calling functions from the standard library during the linking phase of compilation. If prototypes (like int putchar(char c);) or macro definitions like #define NULL ((void *)0) are not defined before they are called, the program might not compile, or work as intended. However, #include is not required to be at the beginning of the code, and can be placed anywhere; the content is inserted at the point in the file where the directive appears.
There are two ways to include a file. Directives like #include
The /dev/tty “file”:
Spinellis won this category because the included file path was pointing to a special device file in the /dev directory that is connected to physical hardware functionality through the kernel. On Linux, everything is considered to be a file. Even directories are a type of file that contains other files. Devices can be read from and written to, just like a file. The /dev directory contains representations of hardware functionality in the form of files that data can be read from and/or written to. A tangible example is the representation of the physical speakers:/dev/dsp. Any text sent to this file is output through the speakers.
The device file tty is a representation of the Teletype console, which can be either kernel-emulated or hardware-implemented. Simply put, a terminal or console is an interface to issue commands to a computer. Teletype consoles were devices that resembled typewriters and could be used to issue commands, hence tele as in telephone, and type as in typewriter, or tty for short. The modern Linux file tty represents the primary input and output of an application’s process.
When the command gcc spinellis.c is executed, the pre-processor calls the file /dev/tty, which opens up a new controlling terminal for the process and waits for the user to write something for it to compile. After the user enters an EOF (CTL + d), the program complies as it normally would. This places the burden of programming functionality on the one compiling. How very tricky!