The most stupid C bug ever

原文地址:http://www.elpauer.org/?p=971

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.


 

你可能感兴趣的:(c,windows,Microsoft,File,application,optimization)