Mark Polyakov

MinGW Gotchas

You carefully crafted, tested, and documented something written in C for Linux. It will compile and “just work” in MinGW, right? It turns out MinGW does not just use glibc wholesale as I somehow expected. It uses a mix of code from glibc, Windows’ built-in POSIX libraries (believe it or not, Windows used to be POSIX compliant), and probably some stuff they developed themselves. As such, it’s not really POSIX-compliant and there are a few things you should know about before trying to compile at a MinGW target.

Regex

error: regcomp() not found. Yep, POSIX specifies regex.h and the functions therein, and it’s in glibc, but MinGW doesn’t provide for it. Look into the matter and you will no doubt stumble upon a stackoverflow answer. Out of the two regex libs listed there, libgnurx seems to be the one that works. But only halfway. I can’t speak for actually using MinGW on Windows, but the cross-compilation package of libgnux provided for openSUSE (and Ubuntu, I believe) only works when linking dynamically. The statically linked version will statically link something into the binary, but it still needs the dll to function. I also tried compiling libgnurx from source, and it generates a static library very similar to the one in openSUSE’s package – it still complains about a missing dll when running the binary. I am not sure how, but Fedora worked around the issue, and the libregex.a library they provide will properly statically link libgnurx. You can find a link to the rpm for libgnurx-static from pkgs.org then simply extract libregex.a, including it as you see fit.

fopen

It will seem like fread is broken: 16338 bytes expected, 473 bytes read. I thought at first that non-blocking I/O (in which you must call fread in a loop until all data has been read) was being used by default. This is incorrect. Rather, if the file being read is binary, Windows is detecting some random string of bytes as an EOF indicator. It’s safe to blame newlines in part as well. Solution? Use the rb mode when fopen‘ing the file instead of r. The fopen(3p) manpage calls rb obsolete and identical in function to r, but on Windows it lets it know that the file is binary and to not stop reading prematurely.

But wait, there’s more!

No, I’m not talking about using wb as the mode when writing (though you should do that too), but rather freopen! While trying to read then subsequently truncate and write to a file, I called freopen(NULL, "wb", my_fh) only for it to spit an indeterminite error at me. The POSIX manpage freopen(3p) claims:

If pathname is a null pointer, the freopen() function shall attempt to change the mode of the stream to that specified by mode, as if the name of the file currently associated with the stream had been used. if the call to freopen() succeeds. It is implementation-defined which changes of mode are permitted (if any), and under what circumstances.

Clear enough. But read further:

Since implementations are not required to support any stream mode changes when the pathname argument is NULL, portable applications cannot rely on the use of freopen() to change the stream mode, and use of this feature is discouraged.

Indeed, freopen without a pathname fails on Windows. Always be sure to pass the pathname as the first argument and you shouldn’t run into issues.

64-bit printf tokens

printf("%llu", my_long_long_unsigned_integer_with_an_equally_long_name) is ugly as hell but it should work. The Windows libc implementation fails on this front, and you will find conflicting information on the internet about how MinGW handle this. There seems to be a concensus that MinGW used to default to using the Windows implementation but has begun to use their own implementation in recent versions. This is not the case. Even with MinGW 8 in October 2018 it uses the Windows printf implementation by default and will not work right for 64-bit integers. To use the MinGW implementation, which properly supports these special substitutions, you must define the __USE_MINGW_ANSI_STDIO preprocessor variable before including stdio.h. MinGW GCC will warn you if you use any unsupported patterns, which is when you know you will need this.

#define __USE_MINGW_ANSI_STDIO
#include <stdio.h>
int main() {
        printf("I have %llu donuts", 12345678901234567890LL);
}