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 : http://www.elpauer.org/?p=971