Dude, you forgot to fflush! (aka toilet humor is undignified, as are flush puns)

by Michael S. Kaplan, published on 2010/12/17 07:01 -05:00, original URI: http://blogs.msdn.com/b/michkap/archive/2010/12/17/10106288.aspx


it may come as no surprise to regular readers that some of the people in the set of "people who read this Blog" work at Microsoft.

A few of that more restricted set even find one or more of the blogs within this Blog to be useful for their something in their work.

And then a few of that even further restricted set find an interesting problem or issue or bug or design limitation along the way.

In some of those cases, I help them get the bug in and/or help push for a fix in the bug.

This blog is not one of those cases.

In this case, the owners of the component that caused them trouble was deemed a "Won't Fix" bug, since i had a workaround.

This blog you are reading now is going to describe the bug, and the workaround.

First, the simple repro code:

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <fcntl.h>
#include <io.h>

void Test1(int argc) {
    int Mode;

    Mode = _setmode(_fileno(stdout), _O_U16TEXT);
    fwprintf(stdout, L"first\nsecond\n");

    if (argc > 2) {
        fflush(stdout);
    }

    if (argc > 1) {
        _setmode(_fileno(stdout), Mode);
    }
}

void __cdecl wmain(    int argc,  PWSTR *argv) {
    SetThreadUILanguage(0);

    Test1(argc);
    exit(0);
}

So there are three cases here:

  1. When you execute the above with no additional command line arguments, e.g.

          test > out.txt

    the result is fine.
  2. When you execute the above with 1 additional argument, e.g.

          test 1 > out1.txt

    the result contains a spurious 0x0d byte that destroys the integrity of this UTF-16 file.
  3. When you execute the above with 2 additional arguments, e.g.

          test 1 2 > out2.txt

    the result is fine.

 Obviously, proper line breaks are what we expect here, so that case #2 clearly looks like a bug.

Now the most interesting case to me is the first case, and for some developers it is the most common.

It has two features many developers who aren't the sort to be reading this Blog do quite commonly, in that

This first case sees no bugs.

Now the second case, which has no explicit flush (it still relies on the implicit flush) but does restore the mode, because even though the app is about to exit, many developers do like to do that sort of cleanup of settings they change.

There are many reasons for this, but the one I find myself doing this for is that people often copy/paste code samples that they put elsewhere. This way, someone will do the cleanup in their app even if they don't pay the same attention to details....

The third case is an interesting one, because in a console app that does not have its output redirected often does not flush.

This simply doesn't occur to the developer in most cases; if the do think about it, they know that stdout will be implicitly flushed anyway.

And here lies the hint as to the problem:

All of the CRT's pending "text mode autoconvert \n to \r\n" that happen during the implicit fflush are done using the current mode of the stream, not the mode at the time that text was fwprintf'ed. And the pending text in the stream is in a state that is halfway to being handled.

Therefore, to avoid the risk here, it is best to always explicitly flush in the current mode, rather than having the implicit flush happen after the mode has changed.

If I don't get any email from a doc writer (there are some doc writers who also read this blog) in a week or so, then I'll probably put in a bug suggesting the need to explicitly fflush prior to a mode change being added to the _setmode documentation. I'm just saying....

Now I had in mind two different potential punchlines to end this blog on:

But I couldn't think of any way to make either of them work out well (toilet humor is undignified, as are flush puns); this blog has been sitting in my "almost finished" pile for over a month now as I tried various ways to redeem it for a world that finds Jackass-style movies to be entertaining before I simply gave up.

 


no comments

go to newer or older post, or back to index or month or day