I just tried this in x86_64 Linux, which probably isn't much different to MinGW at this level, although you never know.
Basically, the problem is that, even though nothing gets pulled in from the C library unless it's referenced, the CRT "startfiles" do reference a small selection of things, which in turn reference some other things, and "Hello world" ends up looking bad. This is not a problem worth fixing because all real programs would reference those core functions anyway.
The source for the start files is available, and quite small, and the compiler allows you to override the standard ones if you choose to, so optimizing them is not a massive deal. They're written in assembler code, but you can probably remove most of the extraneous garbage by simply deleting lines.
But, there's a hack for cutting the start-files out of the equation altogether:
#include <unistd.h>
void _start (void) {
write(1,"Hello world!", 12);
_exit(0);
}
Compile: gcc -nostartfiles t.c -s -static
Which works (by chance, see below), and gives me a file size of 1792 bytes.
For comparison, your original codes gives 738624 bytes, with the same compiler, which drops to 4400 bytes when I remove -static
, but then that's cheating! (My code actually gets larger without -static
, because the dynamic linker meta-data outweighs to code of write
and _exit
).
The by chance part, is that the program now has no stack pointer initialized. Likewise for all other global state the start-files usually take care of. As it happens, on x86_64 Linux, this isn't a fatal problem (just don't do it in production, right?) However, when I tried it with -m32
I get a segmentation fault inside write
.
The problem can be fixed by adding your own initialization for that stuff, but then the code would no longer be as portable (it isn't absolutely portable already). Alternatively, call the write system call directly.