原文地址: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.