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:

            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:

            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:

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:

#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:

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:

#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?

#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:

/* 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:

/* 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:

    /* 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.


95 Thoughts on “The most stupid C bug ever

  1. Amazing :) I’m glad you found it before I had to !
    That’s one more reason why backslashes are so bad for URIs, if that needed to be argued…

  2. Vim highlighting immediately showed the error. ;)

  3. “That has a performance impact on all platforms: the direct call to tmpfile() is now an indirect, which defeats optimization, and well, everything.”

    Premature optimization. An indirect function call is a couple cycles. The fopen() alone is a few 10000 or more.

  4. Johan Ouwerkerk on Monday 22nd August 2011 at 21:47:50 said:

    Note that your solution could be a lot easier and avoid one nasty redefinition of the meaning of ‘FILE*’ using a more direct ifdef approach:

    FILE * fp;
    #ifdef _win32
    fp = tmpfile_s(&fp);
    #else
    fp = tmpfile();
    #endif

    See also: http://msdn.microsoft.com/en-us/library/b3dz6009(v=vs.80).aspx

  5. More respect please…

  6. Ups, the previous comment was for @whiner.

  7. @Johan,

    Thank you, but that implementation exhibits the same problem: tmpfile_s creates temporary files in C:\.

    Also, please note I am not redefining FILE*: on Unix, tmpfile is not replaced, and on Windows FILE* is directly supported by Microsoft.

  8. There are inline-functions in C99

  9. This is actually a bug in Windows, not GCC. The bug is that Windows uses backslash as the path separator.

    • unlike unix, ok. but why exactly are backslashes inappropriate? I dont get this!

      • Think about it. Almost any significant OS in the years has used slash for it’s path separator. DOS and Windows are probably the only ones to use a backslash. To be honest most programming languages are older than Windows, and most new ones are created with Unix/Unix-like environments in mind.
        Coding Windows specific code is a shitty experience for most programmers. Although there are those .NET gurus which I kind of admire for being good coders in a coder unfriendly environment :)

  10. try to put smth about the last backslash – even a space

  11. i meant after. sorry

  12. Hi, I found this blog post on PlanetKDE and I would like to comment few things.

    Firstly, this has been an amusing post.
    Secondly, No, I didn’t notice where the problem was, until you revealed it. I was so focused on ternary operator line, I didn’t even look at the comment.

    Scintilla based editors will highlight this problem right away. e.g. here is a screen capture from Notepad++ (Scite editor does the same thing, Visual studio also)
    http://i54.tinypic.com/13zbz1g.png

    So, you could have saved yourself some time if you had an editor with a better highlighting ability.

    Also, Ryan, backslashes (while annoying) are not “a bug” in windows. In fact Windows uses forward slashes internally (on kernel level) in NT line (all modern WindowsOS), and it is a good habit to always write Windows file paths with forward slashes (e.g. C:/some/file/path). Even the shell (windows explorer accepts this)

    Another thing, why didn’t compiler complain about the empty code block?

    I am at work and I have access to Visual studio 2005 at this machine, and it gives me this warning:
    warning C4010: single-line comment contains line-continuation character

    Again, an amusing post. I never-ever use line comment continuation character, so this was a nice little remainder for me.

    Best regards
    Loreia

  13. Not only is it premature optimization, it’s bad to do it that way. The compiler optimizer will likely remove the indirection for you anyway, making it no more expensive to call anyway, and the #define version obscures what’s actually happening…

  14. Is it just me or are you missing a closing curly brace for the if(NULL != fname) block? I could swear that there is no closing curly brace in your examples before the else…

  15. “That has a performance impact on all platforms: the direct call to tmpfile() is now an indirect, which defeats optimization, and well, everything.”

    Woa that’s some crazy premature optimisation if ever I saw it. You’ve made the code way less readable for a truly minuscule speed increase.

  16. Great article, interesting read. I must admit I didn’t catch the problem until you explained it ;-)

    Also, I have to agree with the premature optimization statement already made above: Just use a function and deal with the different platforms there. The performance will absolutely not matter in this case.

  17. Most editors i have used highlight the next line in green as a comment, however i have come across one that took me over a year to solve, that is remarkably similar.

    //why does this next line not compile??/
    x = 0;

    because the ??/ operator is a trigraph meaning \
    That was never been highlighted correctly any editor I have used.

  18. @Ryan: A bug is unintended execution of code, i.e. code doing something you didn’t intend it to do. Microsoft designed Windows (/DOS) to use “\” for directories, they, as the developers, intended that behaviour and as such it isn’t a bug just because you don’t like it. The bug here comes (as is often the case) from two different systems interacting in an unexpected way. GCC did what it was meant to, Windows did what it was meant to (and the comments described this), leading to “emergent” behaviour.

    But I agree that escaping new lines in single line comments is silly (note how I’m not calling it a “bug” just because I don’t like it).

  19. Funny story.
    I’d blame the syntax highlighter in this case. ;)

  20. Cale Gibbard on Tuesday 23rd August 2011 at 13:36:36 said:

    Haha, yeah, you definitely don’t want to rely on your C-compiler’s inlining optimisation, because someone might compile the code with -O0 after all, and the preprocessor is far more reliable. ;)

  21. Funny how many people are talking about “premature optimization”.

    It’s actually quite the opposite: replacing tmpfile() with mytmpfile() would have been “premature and cumbersome pessimization” – I would have needed to create a .h and a .c for that function, #include “mytmpfile.h” everywhere tmpfile() is used (which is in several places across several of the binaries of this application), and replace the function calls.

    Or I could just #define tmpfile w32_tmpfile for Windows and be done in 5 seconds (yes, there was already a win32.h).

  22. Only yesterday did I have a similar issue, i.e. ??/ at the end of a comment. But when compiling with gcc it gave a warning:

    warning: trigraph ??/ ignored, use -trigraphs to enable

    So compiler ignored the trigraph, and told me about it.

  23. @Steve B:
    Simply don’t use multiple question marks. :D

  24. there’s also a missing } for if(NULL != fname)

  25. @Robert, anon:

    Thank you, I have added the missing brace to the post (I messed up while editing).

  26. I have to admit, and I’m not the first one, that I didn’t see the error until it was pointed out in the final few code blocks. This worries me, as my first programming language is C.

    Other than that, an interesting article. I think I’m going to start showing this one to friends of mine, see if they spot it. I may also show this to people who are beginning their programming career.

  27. The boxes that show your code don’t render nicely on Chrome Linux 13.0.782.107, they’re always too small for the code snippet. Not sure if it’s as bad on other browsers/platforms.

  28. This is an example of where your editor’s highlighting failed you.

    Years ago I remember someone showing me a bug like:

    int main(void) {
    /* Comment
    int c;
    return c;
    }

    which had been a pain for them to track down (obviously their code was much larger than my example) as the compiler grumbled in an obtuse manner. When they were typing the example for me too look at, the code after the comment was blue making the error clear. You did something similar here – you said there was an error and you already reduced the number of lines that had to be inspected to find the error.

    Few people can read code as their interpreter/preprocessor/compiler can. For the rest of us we are dependent on tools to help guide us…

  29. That’s what the “gcc -E” parameter is for.

  30. “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::”

    You’re worried about an extra function call when _making a temporary file_?

    Why? You’re doing i/o to a disk! Not only that, synchronous i/o as filesystem meta data must be written to create a file — the performance impact of an extra function call will be utterly buried by the performance impact of waiting for a slow ass disk. The replacement function call is superior in this case, I think.

  31. Thank you for this post. It’s entirely conceivable that you’ll save me from hours of similar pain someday.

    Note I often go through the same thought process you describe when fixing my code sometimes: “1. Oops, I created a bug. 2. No wait this bug can’t be my fault. 3. This bug is definitely not my fault! It’s a stupid library/compiler/hardware issue. 4. Oh wait, it is my fault.”

  32. Gotta second others’ thoughts… you’re worried about the performance overhead of an indirection to temp file creation?

    Gotta get your priorities in order!

    Having said that… interesting post. I would have taken ages to find that bug (without syntax highlighting, that is). My general rule is, more sure you are of the correctness of your code while debugging an issue, the more likely that the issue is absolutely trivial.

  33. My poor wife took a programming class in college, and her first assignment was a little program to calculate some junk related to triangles. She finally gave up and asked me for help when she just could not make sense of the errors she was getting. She had comments all through the code like this:

    // get the length of side A of the /

    Took me a while to figure it out too. I always thought this gave her a bad taste for programming from the get-go.

    Thanks for the memories :)

  34. Any decent compiler will warn you in this case, like this: http://msdn.microsoft.com/en-us/library/56ybs0yk.aspx

  35. Very interesting. Do many people use modern IDEs like Netbeans to do C code these days? I feel like Netbeans would have caught this and highlighted that line as a comment.

  36. Very nice. Does anyone use IDEs like Netbeans to edit C code these days? I feel like Netbeans would have caught this and highlighted that line as a comment.

  37. Felipe Contreras on Wednesday 24th August 2011 at 01:31:14 said:

    Yup, vim FTW.

  38. lack of unit test

  39. Perhaps this is an argument against using C++ style comments!

  40. Yes.. Vim worked for me too… Another problem the Author had was developing under Windoze… Microsoft tends to do things backwards now and then…

  41. I’ve seen a variation on this when compiling with VC++ with source that has inline comments in Japanese. Some characters when encoded in Shift-JIS use the backslash as part of the 2 byte character, which when it comes at the end of the line causes the compiler to wrap the line.

    It only happens when:
    1. the source is in Shift-JIS (Japanese legacy 2 byte encoding, but default on Japanese Windows)
    2. compiling with a non-Japanese VC++ compiler

    Painful to figure it out!

  42. @AlexS: You reminded me of Dr. Sheldon Cooper… Did you really not get Ryan’s sarcasm?!?

  43. Luckily gcc (I tried with version 4.6) gives a “multi-line comment” warning with the -Wall switch (and, for seldomseen, it warns you about trigraphs and it converts them only when given the -trigraphs option explicitly).

    Great bug ;-)

  44. $ cc foo.c -o foo
    foo.c:4:40: warning: trigraph ??/ ignored, use -trigraphs to enable
    $ cc –version
    cc (GCC) 4.1.3 20080704 prerelease (NetBSD nb2 20081120)
    Copyright (C) 2006 Free Software Foundation, Inc.

    $ cat foo.c
    int main(){
    int x;

    //why does this next line not compile??/
    x = 0;

    return 0;
    }

  45. apt-get install vim

  46. Pingback: C Trigraph Trap

  47. You should note that using the double slash comment is a C++ addition, so it’s not even a C “bug” (if you are being a purist and not mixing C and C++).

    Although I learned C back in the late 1980’s, I haven’t converted over to using the double-slash when using a modern compiler and working in pure C.

    Oh, and get off my lawn.

  48. @Stephen:

    Double-slash comment is pure C since C99:

    http://en.wikipedia.org/wiki/C99#Design

  49. @Beej:

    Actually, Microsoft Visual C++ 2010 SP1 gets this right. It was Qt Creator 2.2.1 with MinGW (gcc 4.4.0) which failed here.

    And I must say MSVC2010 is much much better and POSIX compliant than any other version of Visual C++ I have ever used. It is actually so POSIX compliant that makes unnecessary most of the hacks people have been using for MSVC, and also breaks them (usually because people only try #ifdef _MSC_VER, not #if defined(_MSC_VER) && _MSC_VER < 1600; and also because many POSIX networking features that were missing in older versions are now present, which causes many “redefinition” errors).

  50. “That has a performance impact on all platforms: the direct call to tmpfile() is now an indirect, which defeats optimization, and well, everything.”

    This couldn’t be any more wrong. First of all, even if there were a performance impact, it would be meaningless compared to the time it takes to perform the system calls required for tmpfile().

    Second, the compiler will inline the function call if it can. You can use a regular “inline” function and if you’re not convinced it’s good enough, then use __attribute__(force_inline) or equivalent. Even if the damn function is not an inline function, it will get inlined if it’s in the same translation unit as where it is called (look at the asm output of gcc -O3). In the (not so far) future we’ll be able to enjoy link time optimizations and don’t have to care about translation units w.r.t. inlining optimizations.

  51. Aaaaaaand that is why a good editor with proper syntax highlighting is definitely a Good Thing :)

  52. One of the most unexpected problem, one can ever think of. Thank you for let me know about this before I encountered this.

  53. Yes, premature optimisation. That “indirect call” will compile out to nothing if its placed somewhere it can be inlined.

  54. You appear to be missing a closing brace in your “working” example (line 15).

  55. @Warren M:

    Thank you, brace added.

  56. @pgquiles

    Truthfully I have not used Visual Studio since version 6 which was many years ago. I was just being a Troll.

    However, I would be interested in hearing your thoughts about QT.. I have heard other developers say wonderful things about it.

    p.s. Great article btw..

  57. To the eye of C# developer it all is just stupid and awkward. Better spend your effort in improving mono. Anyway in a decade or so with multiprocessor nature of modern hardware nobody is going to actually write code in C ;)

  58. Hehe, here’s two lessons for you:

    0. Go read up on the ternary operator
    1. Look at the generated assembly for the code with the ternary operator.

    I didn’t actually read your post, I spotted the bug in the first code listing, scrolled down and confirmed that it was gone in the last one, scrolled up and confirmed that it ‘Surprisingly’ went away when you got rid of the ternary operator.

    fail c/c++ programmers are fail. Hey look, an entire thread of them!

  59. @jf

    Amusing. You did not understand the reason for the “bug”. Did you read the article at all? The ternary operator is not involved whatsoever!

  60. That’s why the MISRA C guidelines was introduced.

  61. I had a similar problem during a job interview and I did not get the job. I think my problem was that I was trying to do fopen(“temp”) and it didn’t work because windows doesn’t like temp as file name. Or something similar, I don’t remember. I discovered the problem but it took me about an hour.

  62. I never have a problem with a backslash at the end of a line in comments. I always make sure that I have a blank line before and after the comment block to improve readability.

  63. Thanks. I couldn’t even see the issue until you pointed it out. You’ve probably saved me hours of time some day in the future.

    because of DOS stupidity, I’ve become so adept at ignoring forward slash/backslash as a part separator that I didn’t even recognize it as a backslash with special behavior in that context.

  64. It happened to me once too, but Kdevelop’s syntax highlighter saved my day :-D

  65. That reminds me of a similair JavaScript issue I once had leaving a french single-line comment (//) ending with an accentuated character (don’t remember which one, but probably “”). In Internet Explorer only (what a surprise!) the next line was ignored. I spent a few hours figuring it out. Ever since, I always leave an extra space at the end of any single-line comment, in any language, when the last character is anything but “a-z” or “0-9″.

  66. Thomas Michaud on Thursday 25th August 2011 at 21:12:08 said:

    Okay…I might be the only person – but this isn’t a bug. And I don’t think it’s limited to Windows either.

    The C preprocess is interpreted. The way to indicate that a line continues on the next line is based on the ‘\’ being the last character.

    So when you have a comment ending with C:\, the code should burp both in Windows and Unix (Linux).

    Don’t have the time to try it myself… but someone can experiment with it.

    • Yes, it “fails” on all platforms because the backslash means “this line is actually not finished, look at the next physical line of text and append it to this one”.

      That’s why I said:
      “Single-line C++ comment ending in backslash? Comment continues in the next line! This is what gcc was seeing:”

  67. For what it’s worth, TextWrangler (Mac editor) colored this correctly.

    Disturbingly (to me), ActiveState Komodo did not.

  68. Pingback: C语言中史上最愚蠢的Bug | 酷壳 - CoolShell.cn

  69. “Microsoft decided their implementation of tmpfile() would create temporary files in C:\”

    This is where your problem started. This is NOT what tmpfile() does: “The temporary file is created in the root directory.”

    They’re not clear on if the “root directory” is the root of the drive the program is run from, or if they mean the root of the system (Windows) partition. Not that it matters for this.

    Your original comment is flawed in that it assumes that the ‘root directory’ is named “C:\”. You shouldn’t assume that. :)

    • In a quick note to self, there is no need to be 100% academic :-)

      “root directory” is C:\ for so many people that is a must-fix problem.

      • Not only that, but it may not even have a drive letter assigned to it (no slash at all), depending on the context. :)

        ˙˙˙pɹɐoqʎəʞ ʎɯ əʞoɹq ı ʞuıɥʇ ı

  70. This is funny, for real! Glad you found it :D

  71. You wrote:

    FILE * tmpfile ( 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.

    NO – This will lead to an stack overflow on non-Windows-systems, ’cause you call tmpfile() from your definition of tmpfile(), so you have a endless recursive call …

  72. Pingback: Всем сотрудникам отдела! «

  73. Pingback: Links for 2011-08-26 | Business Developer Talk

  74. You should use

    g == fname ? fopen(fname, “w+”) : tmpfile();

    instead of

    g = fname ? fopen(fname, “w+”) : tmpfile();

    Sometimes the single ‘=’ gets misinterpreted by the compiler as “assign” instead of “is equal to”.

    • No, it doesn’t get “sometimes misinterpreted”, it is ALWAYS interpreted as the assignment operator, as “=” IS the assignment operator.

      Also, he obviously DOES want to assign the return value to g, not check for equality.

  75. Pingback: lidashuang

  76. Pingback: C语言中史上最愚蠢的Bug - Code hacker

  77. Pingback: C语言中史上最愚蠢的Bug | lidashuang

  78. Pingback: Most stupid C bug ever,Is not it? | Cool , IT Worker !

  79. Pingback: Most stupid C bug ever,Is not it? | Cool , IT Worker !

  80. Pingback: dryland的杂物室 » C语言中史上最愚蠢的Bug(转自coolshell.cn)

  81. Pingback: C语言中史上最愚蠢的Bug | shaojiahao's blog

  82. Pingback: C语言中史上最愚蠢的Bug | IOshared

  83. Pingback: C语言中史上最愚蠢的Bug - Sina App Engine - 开发者第964391个问答

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

 

Post Navigation