I’ve been using the C programming language for more than 24 years now. Despite its occasional Simon Says rules (I’m looking at you, prototypes), it has become a very comfortable language for me. But it wasn’t that way in the beginning.
My introduction to C coincided with my first use of a Unixish system: Xenix running on an Altos 586. It was so liberating to be free of the restrictions of DEC operating systems — but I was to learn that freedom comes at a price.
My colleague Dave and I decided to learn the language together. For our first program in C, we eschewed the unchallenging and pedestrian ”hello world”. No, we decided, only a wuss would start with something that has no side effects. We’ll write a program to create a file!
I had previously worked with a language that supported pointers (DG/L, an ALGOL 60 derivative), and both Dave and I had worked in assembly language. But the pointer syntax in C was quite different from either of those, so when fopen called for a pointer to a filename we weren’t quite sure what to pass. After considerable discussion and numerous compilation errors, we ended up with the following:
#include <stdio.h>
main()
{
FILE *fp;
char filename[6] = “xyzzy”;
fp = fopen(filename[0], “w”);
fclose(fp);
}
Can you spot the mistake? The C compiler that came with Xenix couldn’t. The program happily compiled and even ran without complaint.
But the subsequent shell command “ls xyzzy” returned nothing. The broader command “ls”, however, dumped unprintable garbage all over the screen.
An octal dump of the directory file revealed that it contained an entry whose filename was a series of random characters, many of which had special meaning for the CRT terminal we were using. Then the light bulb came on.
We had erroneously thought that passing filename[0] to fopen would pass the address of the 0th character of filename, when it actually passed the character at that location. The fopen function interpreted that character’s value (’x’ = 170 octal) as an address, then merrily looked there and used whatever it found as a filename, up to the first null.
Now we understood our error — but how to delete the file? Trying to enter the filename at the shell prompt proved impossible. We tried ‘rm -i *’ to have rm prompt us for each file. But when we got to the one that messed up the display and we answered ‘y’, it barked back ‘not found’. Analyzing the garbage that preceded ‘not found’ told us that rm had stripped the eighth bit on each character, so it no longer matched.
Finally, we hit on a solution. Our second C program initialized its filename array using the numeric values of the characters we could see in the octal dump, then correctly passed the address of that array to unlink(). That second C program was perhaps the last C program I ever wrote that worked on the first try.
Most of today’s C compilers would at least give you a warning for passing a char where a pointer was expected. Most of today’s operating systems would disallow access to memory addresses in that range. And I suspect that most of today’s file systems wouldn’t create a filename containing those characters. But sometimes I miss those old days when you were trusted to know what you were doing and severely punished if you didn’t — you learned so much more along the way.