handmade.network » Wiki » Tutorial/How to avoid C/C++ runtime on Windows

Couple of times Casey mentioned on stream that it would be nice to avoid C/C++ runtime, but it could take too much time explaining and doing that. So I made a guide how to do that. These instructions will make your executable to contain only code you are writing, no hidden code from runtime will be added (we'll add necessary stuff ourselves).

First of all, let's look at empty Windows application:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <windows.h>

int CALLBACK
WinMain(HINSTANCE Instance,
        HINSTANCE PrevInstance,
        LPSTR CommandLine,
        int ShowCode)
{
    return(0);
}

Let's compile this as 64-bit application and look at size of executable (further compiles will be 64-bit only unless it will mention 32-bit compile):

C:\handmade>call "%VS120COMNTOOLS%..\..\VC\vcvarsall.bat" amd64

C:\handmade>cl.exe -nologo -Gm- -GR- -EHa- -Oi win32_handmade.cpp
win32_handmade.cpp

C:\handmade>dir win32_handmade.exe
...
12/12/2014  05:00 PM            68,096 win32_handmade.exe

You can see it produces ~68KB executable.

Main switch to disable usage of C runtime is /NODEFAULTLIB linker argument, this will not pass any libraries linker thinks are "default" for application. This includes "msvcrt.lib" and also "kernel32.lib" libraries. We also want to specify what kind of application we are creating (GUI or console) with /SUBSYSTEM argument.

If we try to do that now we'll get an error:

C:\handmade>cl.exe -nologo -Gm- -GR- -EHa- -Oi win32_handmade.cpp -link -nodefaultlib -subsystem:windows
win32_handmade.cpp
LINK : error LNK2001: unresolved external symbol _WinMainCRTStartup
win32_handmade.exe : fatal error LNK1120: 1 unresolved externals

This is because WinMain is not the entry point of executable. It is WinMainCRTStartup. Visual C runtime provided it before to us and called our WinMain function (Casey talked about this in one of C intro streams). So now we need to implement our own WinMainCRTStartup. Let's do it like this:

1
2
3
4
5
void WinMainCRTStartup()
{
    int Result = WinMain(GetModuleHandle(0), 0, 0, 0);
    ExitProcess(Result);
}

Note that I'm passing NULL pointer to CommandLine argument, because Handmade Hero doesn't use it. In case you want to use it you can call GetCommandLineA() function to get command-line. Also note that you should never return from WinMainCRTStartup function, so I am calling ExitProcess function at end of it. Process will terminate when WinMain returns. Of course you can simplify code a bit and not call WinMain function at all, and just directly write code in WinMainCRTStartup function.

Now if we compile code (and add kernel32.lib import library for GetModuleHandle and ExitProcess functions) it will succeed and executable will be much smaller:

C:\handmade>cl.exe -nologo -Gm- -GR- -EHa- -Oi win32_handmade.cpp -link -nodefaultlib -subsystem:windows kernel32.lib
win32_handmade.cpp

C:\handmade>dir win32_handmade.exe
...
12/12/2014  05:09 PM             2,560 win32_handmade.exe

Just 2.5KB. Now that is very nice! No C runtime overhead!

But we are not quite done. If you apply what's done here till now to bigger programs you'll notice that you cannot use serveral features of C/C++ like:

  • allocating large arrays/structure on stack (>4KB)
  • some calculations with 64-bit integers in 32-bit code
  • using floating point
  • casting floating point to integer and back in 32-bit code
  • initialization and assignment of large arrays/structures

Let's fix these issues.

Allocating large arrays/structure on stack (>4KB)

If you allocate array or structure on stack that is greater that ~4KB, something like this:

1
2
3
4
5
6
7
8
9
#include <windows.h>

void __stdcall WinMainCRTStartup()
{
    char BigArray[4096];
    BigArray[0] = 0;

    ExitProcess(0);
}

Then you'll get following linker errors:

C:\handmade>cl.exe -nologo -Gm- -GR- -EHa- -Oi win32_handmade.cpp -link -subsystem:windows -nodefaultlib kernel32.lib
win32_handmade.cpp
win32_handmade.obj : error LNK2019: unresolved external symbol ___report_rangecheckfailure referenced in function "void __stdcall WinMainCRTStartup(void)" ([email protected]@YGXXZ)
win32_handmade.obj : error LNK2019: unresolved external symbol @[email protected] referenced in function "void __stdcall WinMainCRTStartup(void)" ([email protected]@YGXXZ)
win32_handmade.obj : error LNK2019: unresolved external symbol __chkstk referenced in function "void __stdcall WinMainCRTStartup(void)" ([email protected]@YGXXZ)
win32_handmade.obj : error LNK2019: unresolved external symbol ___security_cookie referenced in function "void __stdcall WinMainCRTStartup(void)" ([email protected]@YGXXZ)
win32_handmade.exe : fatal error LNK1120: 4 unresolved externals[/code]

This is some security feature that could help in debug builds. So maybe you want to keep linking to C runtime in debug builds, but for final shipping executable you don't want any additional overhead inserted in your functions. To disable this, simply add /GS- /Gs9999999 arguments to commandline.

Additionally you need to add /STACK:0x100000,0x100000 to linker options so executable has full 1MiB of stack available to it. By default OS only reserves 1MiB of stack, but commits only few 4KiB pages. Then for each larger function it inserts code to check if more space is needed and actually commits new pages. But assuming we don't care about extra static 1MiB allocation, let's just commit it all at the startup and be done with this.

C:\handmade>cl.exe -nologo -Gm- -GR- -EHa- -Oi -GS- -Gs9999999 win32_handmade.cpp -link -subsystem:windows -nodefaultlib kernel32.lib -stack:0x100000,0x100000
win32_handmade.cpp

Some calculations with 64-bit integers in 32-bit code.

Now this is a bit trickier. 64-bit executable can use 64-bit registers to perform calculations on 64-bit values (int64t and uint64t). But if you compile for 32-bit code, all general purpose registers are only 32-bit long. How compiler can perfom operations then? Let's create test code that does various operations:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdint.h>
#include <windows.h>
#include "win32_crt_float.cpp"

void __stdcall WinMainCRTStartup()
{
    volatile int64_t s = 1;
    volatile uint64_t u = 1;

    s += s;
    s -= s;
    s *= s;
    s /= s;
    s %= s;
    s >>= 33;
    s <<= 33;

    u += u;
    u -= u;
    u *= u;
    u /= u;
    u %= u;
    u >>= 33;
    u <<= 33;

    ExitProcess(0);
}

Now run compiler:

C:\handmade>call "%VS120COMNTOOLS%..\..\VC\vcvarsall.bat" x86

C:\handmade>cl.exe -nologo -Gm- -GR- -EHa- -Oi -GS- -Gs9999999 win32_handmade.cpp -link -subsystem:windows -nodefaultlib kernel32.lib -stack:0x100000,0x100000
win32_handmade.cpp
win32_handmade.obj : error LNK2019: unresolved external symbol __alldiv referenced in function "void __stdcall WinMainCRTStartup(void)" ([email protected]@YGXXZ)
win32_handmade.obj : error LNK2019: unresolved external symbol __allmul referenced in function "void __stdcall WinMainCRTStartup(void)" ([email protected]@YGXXZ)
win32_handmade.obj : error LNK2019: unresolved external symbol __allrem referenced in function "void __stdcall WinMainCRTStartup(void)" ([email protected]@YGXXZ)
win32_handmade.obj : error LNK2019: unresolved external symbol __allshl referenced in function "void __stdcall WinMainCRTStartup(void)" ([email protected]@YGXXZ)
win32_handmade.obj : error LNK2019: unresolved external symbol __allshr referenced in function "void __stdcall WinMainCRTStartup(void)" ([email protected]@YGXXZ)
win32_handmade.obj : error LNK2019: unresolved external symbol __aulldiv referenced in function "void __stdcall WinMainCRTStartup(void)" ([email protected]@YGXXZ)
win32_handmade.obj : error LNK2019: unresolved external symbol __aullrem referenced in function "void __stdcall WinMainCRTStartup(void)" ([email protected]@YGXXZ)
win32_handmade.obj : error LNK2019: unresolved external symbol __aullshr referenced in function "void __stdcall WinMainCRTStartup(void)" ([email protected]@YGXXZ)
win32_handmade.exe : fatal error LNK1120: 8 unresolved externals

As you can see compiler uses bunch of function to perfom these operations. Source for these functions can be found in asm files under C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\crt\src\intel folder. We probably don't want to compile asm files now. Theoretically you could create C version of these functions, but let's just copy & paste assembly implementation into naked inline assembly functions (feel free to optimize them later :) Let's create win32_crt_math.cpp file

Then put #include "win32_crt_math.cpp" in your win32_handmade.cpp file and compile it:

C:\handmade>cl.exe -nologo -Gm- -GR- -EHa- -Oi -GS- -Gs9999999 win32_handmade.cpp -link -subsystem:windows -nodefaultlib kernel32.lib
win32_handmade.cpp

And success! Now you can use 64-bit types in 32-bit code.

Alternatively you can take implementation of these functions from SDL library:

Using floating point

If you are using floating point in your code, then you'll get following linker error:

C:\handmade>cl.exe -nologo -Gm- -GR- -EHa- -Oi -GS- -Gs9999999 win32_handmade.cpp -link -subsystem:windows -nodefaultlib kernel32.lib -stack:0x100000,0x100000
win32_handmade.cpp
win32_handmade.obj : error LNK2001: unresolved external symbol __fltused
win32_handmade.exe : fatal error LNK1120: 1 unresolved externals[/code]

In this case linker wants to see _fltused symbol. It needs just the symbol, it doesn't care about its value. So let's provide it in win32_crt_float.cpp file:

1
    extern "C" int _fltused;

And include this file in our win32_handmade.cpp:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <windows.h>
#include "win32_crt_float.cpp"

void __stdcall WinMainCRTStartup()
{
    float f;
    f = 0.0f;

    ExitProcess(0);
}

Let's run compiler and we'll see that everything works: C:\handmade>cl.exe -nologo -Gm- -GR- -EHa- -Oi -GS- -Gs9999999 win32handmade.cpp -link -subsystem:windows -nodefaultlib kernel32.lib -stack:0x100000,0x100000 win32handmade.cpp

Casting floating point to integer and back in 32-bit code

Following code compiled with SSE instruction set (VS2013 default one) will produce linker errors:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <stdint.h>
#include <windows.h>
#include "win32_crt_float.cpp"
#include "win32_crt_math.cpp"

void __stdcall WinMainCRTStartup()
{
    float f = 1000.0f;
    double d = 1000000000.0;

    int32_t i32f = (int32_t)f;
    int32_t i32d = (int32_t)d;
    uint32_t u32f = (uint32_t)f;
    uint32_t u32d = (uint32_t)d;

    int64_t i64f = (int64_t)f;
    int64_t i64d = (int64_t)d;
    uint64_t u64f = (uint64_t)f;
    uint64_t u64d = (uint64_t)d;

    f = (float)i32f;
    d = (double)i32d;
    f = (float)u32f;
    d = (double)u32d;

    f = (float)i64f;
    d = (double)i64d;
    f = (float)u64f;
    d = (double)u64d;

    ExitProcess(0);
}

Compile it to see linker errors:

c:\handmade>cl.exe -Zi -nologo -Gm- -GR- -EHa- -Oi -GS- -Gs9999999 win32_handmade.cpp -link -subsystem:windows -nodefaultlib kernel32.lib -stack:0x100000,0x100000
win32_handmade.cpp
win32_handmade.obj : error LNK2019: unresolved external symbol __dtol3 referenced in function "void __stdcall WinMainCRTStartup(void)" ([email protected]@YGXXZ)
win32_handmade.obj : error LNK2019: unresolved external symbol __dtoui3 referenced in function "void __stdcall WinMainCRTStartup(void)" ([email protected]@YGXXZ)
win32_handmade.obj : error LNK2019: unresolved external symbol __dtoul3 referenced in function "void __stdcall WinMainCRTStartup(void)" ([email protected]@YGXXZ)
win32_handmade.obj : error LNK2019: unresolved external symbol __ftol3 referenced in function "void __stdcall WinMainCRTStartup(void)" ([email protected]@YGXXZ)
win32_handmade.obj : error LNK2019: unresolved external symbol __ftoui3 referenced in function "void __stdcall WinMainCRTStartup(void)" ([email protected]@YGXXZ)
win32_handmade.obj : error LNK2019: unresolved external symbol __ftoul3 referenced in function "void __stdcall WinMainCRTStartup(void)" ([email protected]@YGXXZ)
win32_handmade.obj : error LNK2019: unresolved external symbol __ltod3 referenced in function "void __stdcall WinMainCRTStartup(void)" ([email protected]@YGXXZ)
win32_handmade.obj : error LNK2019: unresolved external symbol __ultod3 referenced in function "void __stdcall WinMainCRTStartup(void)" ([email protected]@YGXXZ)
win32_handmade.exe : fatal error LNK1120: 8 unresolved externals

If you don't want to depend on SSE instruction set and you specify /arch:IA32 compiler argument, then error will be a bit different:

c:\handmade>cl.exe -arch:IA32 -nologo -Gm- -GR- -EHa- -Oi -GS- -Gs9999999 win32_handmade.cpp -link -subsystem:windows -nodefaultlib kernel32.lib
win32_handmade.cpp
win32_handmade.obj : error LNK2019: unresolved external symbol __ftol2 referenced in function "void __stdcall WinMainCRTStartup(void)" ([email protected]@YGXXZ)
win32_handmade.obj : error LNK2019: unresolved external symbol __ftol2_sse referenced in function "void __stdcall WinMainCRTStartup(void)" ([email protected]@YGXXZ)
win32_handmade.exe : fatal error LNK1120: 2 unresolved externals

I implemented functions only need for FPU path, no SSE stuff for 32-bit code (which you probably want anyway). See win32_crt_float.cpp file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
extern "C"
{
    int _fltused;

#ifdef _M_IX86 // following functions are needed only for 32-bit architecture

    __declspec(naked) void _ftol2()
    {
        __asm
        {
            fistp qword ptr [esp-8]
            mov   edx,[esp-4]
            mov   eax,[esp-8]
            ret
        }
    }

    __declspec(naked) void _ftol2_sse()
    {
        __asm
        {
            fistp dword ptr [esp-4]
            mov   eax,[esp-4]
            ret
        }
    }

#endif
}

Warning! These functions are not exactly as regular casts. They do rounding differently (round to nearest, not truncate). Also they don't process correctly NaNs and don't produce floating point exceptions. But if you are ok with that, then this is good enough. For more accurate implementation consult SDL source I mentioned above. Alternatively you could create your own casting function and avoid C cast. You'll need to do that anyway for all trigonometry and other functions from math.h header.

When that is done your code will compile fine.

c:\handmade>cl.exe -arch:IA32 -nologo -Gm- -GR- -EHa- -Oi -GS- -Gs9999999 win32_handmade.cpp -link -subsystem:windows -nodefaultlib kernel32.lib -stack:0x100000,0x100000
win32_handmade.cpp

So remember to use -arch:IA32 for 32-bit builds.

initialization and assignment of large arrays/structures

If you will initialize big arrays or structures with 0, then compiler will assume it can call memset to clear that space on stack. For example this code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include <stdint.h>
#include <windows.h>
#include "win32_crt_float.cpp"
#include "win32_crt_math.cpp"

void __stdcall WinMainCRTStartup()
{
    char BigArray[100] = {};

    ExitProcess(0);
}

Will call memset:

c:\handmade>cl.exe -Zi -nologo -Gm- -GR- -EHa- -Oi -GS- -Gs9999999 win32_handmade.cpp -link -subsystem:windows -nodefaultlib kernel32.lib
win32_handmade.cpp
win32_handmade.obj : error LNK2019: unresolved external symbol _memset referenced in function "void __stdcall WinMainCRTStartup(void)" ([email protected]@YGXXZ)
win32_handmade.exe : fatal error LNK1120: 1 unresolved externals

To fix that create win32_crt_memory.cpp file and stick #include to it in win32_handmade.cpp. #pragmas before functions tell compiler that these two functions won't be intrinsics, so they can be compiled with -Oi option.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
extern "C"
{
    #pragma function(memset)
    void *memset(void *dest, int c, size_t count)
    {
        char *bytes = (char *)dest;
        while (count--)
        {
            *bytes++ = (char)c;
        }
        return dest;
    }

    #pragma function(memcpy)
    void *memcpy(void *dest, const void *src, size_t count)
    {
        char *dest8 = (char *)dest;
        const char *src8 = (const char *)src;
        while (count--)
        {
            *dest8++ = *src8++;
        }
        return dest;
    }
}

Let's check if BigArray now compiles fine: c:\handmade>cl.exe -Zi -nologo -Gm- -GR- -EHa- -Oi -GS- -Gs9999999 win32handmade.cpp -link -subsystem:windows -nodefaultlib kernel32.lib -stack:0x100000,0x100000 win32handmade.cpp

And we're good!

Now you can write pretty much almost any reasonable code as you wish!

Remember that you are not allowed to use following features:

  1. C++ RTTI (it's turned off by -GR- anyway)
  2. C++ exceptions - try/catch/throw (this it's turned off by -EHa-)
  3. SEH exceptions - you could use them if you implement Cspecifichandler (for 64-bit code) and _excepthandler3 (for 32-bit code) functions. See simple expample how to do that by calling original C runtime functions in [url=https://gist.github.com/mmozeiko/6a365d6c483fc721b63a#file-win32_crt_seh-cpp]win32_crt_seh.cpp[/url] file.
  4. Global objects with C++ constructors and destructors - it's possible to implement it, but it's not needed for us.
  5. Pure virtual functions in C++ classes - for that you'll need to implement __purecall function, but we are also not interested in this.
  6. No new/delete C++ operators, they are using global new/delete functions. You'll need to either override new/delete for each class, or implement global new/delete functions yourself.

Of course you can not use any function from standard C or C++ runtime (stdlib.h, stdio.h, string.h, math.h, etc.. headers). Only safe headers are the ones that provide compiler specific functionality, such as:

  • stddef.h - if you want size_t and NULL
  • stdint.h - various intXXt and uintXXt typedefs
  • stdarg.h - vaarg, vastart, vaend, vaarg intrinsics
  • intrin.h and few other headers for intrinsic functions (rdtsc, cpuid, SSE, SSE2, etc..)