Portability Bug in Windows OS for C Programming

I recently made a switch to Emacs and I stumbled upon a "bug" in the Windows OS when it comes to writing portable code in C. I will explain this bug in great detail in this article.

Let's first examine the bug.

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char * argv[]){
    int age;

    printf("Enter age: ");

    scanf("%d", &age);

    printf("You are %d years old.", age);

    return EXIT_SUCCESS;
}

So, I ran this code in Emacs on Windows. This should work right?

It doesn't.

How so?

When you run the program, the printf() statement does not show up in the console. It immediately is reading input. Then, when you enter the age, you get:

23
Enter Age: You are 23 years old

The problem is how Windows handles I/O.

On Unix-like systems, inclusing MacOS, stdout has line based buffering. This means that stdout is flushed after seeing a newline.

However, Windows does not support line-based buffering. Newlines are no longer special and and full buffering is used.

stdout on Windows is either always unbuffered when attached to the console or fully buffered.

This is the options for stdout on Windows:

Alt Text

This stems from the expectation that stdout buffering should behave like in POSIX systems, which is not necessarily the case on 'windows-nt.

MacOS and Linux are mostly POSIX compliant while Windows is not.

Alt Text

Under "stderr, stdin, stdout - standard I/O streams", POSIX standard reads:

At program start-up, three streams shall be predefined and need not be opened explicitly: standard input (for reading conventional input), standard output (for writing conventional output), and standard error (for writing diagnostic output). When opened, the standard error stream is not fully buffered; the standard input and standard output streams are fully buffered if and only if the stream can be determined not to refer to an interactive device.

Windows uses pipes to communicate with subprocesses, while on Posix platforms use PTYs, which are a kind of console device.

However, Windows came out with WSL to solve this. WSL is POSIX compliant. But if you were to not use WSL, you would run into this portability issue. The resolution to this problem is to explicitly flush using:

  1. fflush() or
  2. setvbuf(stdout, NULL, _IONBF, 0); // turn off buffering for stdout