The most stupid C bug ever

  pgquiles        2012-04-22 03:40:49       2,674        0    

I have been programming for a number of years already. I have seen others introduce bugs, and I have also introduced (and solved!) many bugs while coding. Off-by-one, buffer-overflow, treating pointers as pointees, different behaviors or the same function (this is specially true for cross-platform applications), race conditions, deadlocks, threading issues. I think I have seen quite a few of the typical issues.

Yet recently I lost a lot of time to what I would call the most stupid C bug in my career so far, and probably ever.

I am porting a Unix-only application which uses tmpfile() to create temporary files:

1
2
3
4
else if (code == 200) {     // Downloading whole file
    /* Write new file (plus allow reading once we finish) */
    g = fname ? fopen(fname, "w+") : tmpfile();
}

For some awkward reason, Microsoft decided their implementation of tmpfile() would create temporary files in C:\, which is totally broken for normal (non-Administrator) users, and even for Administrator users under Windows 7.

In addition to that, the application did not work due to another same-function-diffent-behavior problem.

To make sure I did not forget about the tmpfile() issue, I dutifully added a FIXME in the code:

1
2
3
4
5
else if (code == 200) {     // Downloading whole file
    /* Write new file (plus allow reading once we finish) */
    // FIXME Win32 native version fails here because Microsoft's version of tmpfile() creates the file in C:\
    g = fname ? fopen(fname, "w+") : tmpfile();
}


After fixing the other issue, I went back to tmpfile(). I needed to replace it with a custom one that worked for regular users. Instead of replacing the tmpfile() call with a mytmpfile() like this:

1
2
3
4
5
6
7
FILE * mytmpfile ( void ) {
#ifndef _WIN32
    return tmpfile();
#else
    code for Windows;
#endif
}

That has a performance impact on all platforms: the direct call to tmpfile() is now an indirect, which defeats optimization, and well, everything. So I did the typical dirty #define that works like a charm:

1
2
3
4
5
6
7
#ifdef _WIN32
#  define tmpfile w32_tmpfile
#endif
 
FILE * w32_tmpfile ( void ) {
    code for Windows;
}

Problem solved, right? Wrong. It did not work. My w32_tmpfile() was not being called. Surprisingly, when I replaced the ternary operator with an if-then-else, it worked fine:

1
2
3
4
5
if(NULL != fname) {
    g = fopen(fname, "w+");
} else {
    g = tmpfile();
}

Given the fragile status of MinGW (very few developers, releases lag significantly behind gcc’s, a myriad of variants to choose from -TDM, MinGW32, MinGW64, etc-), I was wondering whether I had found a bug in the preprocessor.

After all, what was the possible reason for this to work:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifdef _WIN32
#  define tmpfile w32_tmpfile
#endif
 
FILE * w32_tmpfile ( void ) {
    code for Windows;
}
 
else if (code == 200) {     // Downloading whole file
    /* Write new file (plus allow reading once we finish) */
    // FIXME Win32 native version fails here because Microsoft's version of tmpfile() creates the file in C:\
    //g = fname ? fopen(fname, "w+") : tmpfile();
    if(NULL != fname) {
        g = fopen(fname, "w+");
    } else {
        g = tmpfile();
    }
}

when this was failing?

1
2
3
4
5
6
7
8
9
10
11
12
13
#ifdef _WIN32
#  define tmpfile w32_tmpfile
#endif
 
FILE * w32_tmpfile ( void ) {
    code for Windows;
}
 
else if (code == 200) {     // Downloading whole file
    /* Write new file (plus allow reading once we finish) */
    // FIXME Win32 native version fails here because Microsoft's version of tmpfile() creates the file in C:\
    g = fname ? fopen(fname, "w+") : tmpfile();
}

I’m sure some of you spotted the problem at the very beginning of my post. I did not. It took me days to realize. The key is this:

1
2
/* Write new file (plus allow reading once we finish) */
// FIXME Win32 native version fails here because Microsoft's version of tmpfile() creates the file in C:\

Single-line C++ comment ending in backslash? Comment continues in the next line! This is what gcc was seeing:

1
2
/* Write new file (plus allow reading once we finish) */
// FIXME Win32 native version fails here because Microsoft's version of tmpfile() creates the file in C:    g = fname ? fopen(fname, "w+") : tmpfile();

No code to be executed at all!

The only reason replacing the ternary operator with if-then-else made the code work was I was commenting out one line of code (the ternary operator’s), therefore gcc saw this:

1
2
3
4
5
6
7
/* Write new file (plus allow reading once we finish) */
// FIXME Win32 native version fails here because Microsoft's version of tmpfile() creates the file in C:    //g = fname ? fopen(fname, "w+") : tmpfile();
if(NULL != fname) {
    g = fopen(fname, "w+");
} else {
    g = tmpfile();
}

Ouch.

Author : Source : http://www.elpauer.org/?p=971

C  BUG  COMMENT  BACK SLASH 

       

  RELATED


  0 COMMENT


No comment for this article.



  RANDOM FUN

Unexpected behavior due to a bug