The code submissions were whittled down to a handful of participants, among whom it was difficult to choose a winner. Below we list the winner, and the ones that entertained us.
Please note: there are multiple winning critera, including:
That being said, here is the winner.
This entry employed no super-clever trick: it simply used the extremely common coding mistake of calling strlen() in a loop:
long unsigned int maxwordsize(char *inputFromStdIn)
{
long unsigned int tmpwordsize=0,maxword=1,i;
for (i=0; i <= strlen(inputFromStdIn); ++i,++tmpwordsize)
{
[etc etc]
The string inputFromStdIn held the entire input file.
Within the loop the string is not altered, so the strlen is optimized out when compiled under linux. It is computed under Cygwin gcc for any level of optimization. What if the string is altered by another thread? I'm not sure. This creates an O(n^2) versus O(n) diference in complexity, using the whole file to make the biggest difference possible.
These were submissions that entertained us enough to make it into the final judging process. We mention them here to illustrate some of the techniques used to write bad code.
This code is short enough to quote in its entirety, right here:
void main(void)
{
system("/usr/bin/tr \"[:space:]\" \"[\\n*]\" | /usr/bin/sort | \
( while true; do read A || exit; if [ \".$A\" != . ]; then ( echo $A; ) fi; done ) | uniq -c");
}
It exploits the inability of Cygwin to fork properly. This isn't really in C, and more importantly it wasn't the most convincingly innocent code. However, shorter programs are more likely to win. This is because there are fewer places to hide badness. Basically, any submission over 300 lines didn't have much of a chance.
This dude was close.
/* getnumwords - Return the number of unique words in the list. */
void getnumwords(short *result) {
struct WordCount *L;
int N = 0;
for (L = List; L != NULL; L = L->Next)
++N;
*result = N;
}
[...]
int main() {
int NumWords = 0;
/* Read all of the words. */
while (!handleword(stdin))
;
/* sort all of the words. */
getnumwords(&NumWords);
sortwords(NumWords);
/* Print them out */
printwords();
return 0;
}
The code tried to evade the compiler warning with specific compilation instructions to build one function in a separate library.
The humor value in this submission is that there were two programs, one that ran mysteriously slow on Windows Vista, and one on Mac OSX. The pro-vista submission had an interesting bad hash algorithm:
size_t hash(wchar_t* word) {
char* s=(char*)word;
size_t x;
ssize_t i;
/* looping backwards is more efficient */
for (x=i=wcslen(word)*sizeof(wchar_t); i>=0; i--) {
x += ~(s[i+1]) + s[i];
}
return x;
}
Since -a = ~a + 1, this hash is simply equal to the first byte in the string. That's a bad hash on either platform, but on a little-endian system, the first byte is a character value; on a big-endian system it is amost always zero.
Clever, but ballsy.
void strip_newlines(char *line)
{
int pos, i;
/* remove trailing newline characters */
i = strlen(line) - 1;
do
{
if((line[i] == '\n') || (line[i] == '\r'))
{
line[i] = '\0';
}
pos ++;
}
while(pos > 0);
}
Another bad hash. Check it out:
unsigned int hash(char* str, int len)
{
unsigned long d = 987654321;
int i;
for (i = 0;i <= len; i++) d = str[i] ^ (d >> 8 | d << 24);
for (i = 0;i <= len; i++) d = str[i] ^ (d >> 8 | d << 24);
for (i = 0;i <= len; i++) d = str[i] ^ (d >> 8 | d << 24);
return d;
}
Another bad hash.
/* rotate x (m bits) left by y bits */
#define ROL(x, y, m) (((x) >> ((m) - (y))) | ((x) << (y)))
int hash (wchar_t * word) {
int hc = 0xDEADBEEF;
int i = 0;
while(word[i] != 0) {
wchar_t c = ~word[i];
c = ROL(c, 1, 8 * sizeof(wchar_t));
hc = hc * 31137;
hc ^= c;
i++;
}
return hc;
}
On GNU/Linux, wchar_t is signed, and the ROL sign-extends the 1 bit for any negative input. Very clever, but it is a bit suspicious that word[i] is complemented right before calling this.
This one deserves a mention because it actually exploits an API difference.
static ssize_t
read_async (int fd, char *buf, size_t len)
{
fd_set rfds;
int ret;
static struct timeval tv = { 0, TIMEOUT_MS };
FD_ZERO (&rfds);
FD_SET (fd, &rfds);
do
ret = select (fd, &rfds, NULL, NULL, &tv);
while (-1 == ret && EINTR == errno);
if (ret != -1) {
do
ret = read (fd, buf, len);
while (-1 == ret && EINTR == errno);
}
return (ret);
}
Under Linux, select() behaves differently. Unfortunately, there isn't much of a reason for all this code to be present in a simple data processing application. Or to put it another way, our MAYBE IT'S THERE alarm pointed right at the place where the problem was.