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

Casey has mentioned on the stream that it is nice to avoid the C/C++ runtime, but it would take too much time to do and to explain that. This is a guide on how to avoid the C/C++ runtime. These instructions will make your executable contain only code you have written, no hidden code from the runtime will be added (we'll add the necessary stuff ourselves).

This is the 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);
}

We compile this as a 64-bit application and look at the size of executable (further compiles will be 64-bit only unless the guide indicates a 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

This produces a ~68KB executable.

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

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 the 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). We need to implement our own WinMainCRTStartup.

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

We pass a NULL pointer to the CommandLine argument, because Handmade Hero doesn't use it. In case you want to use it you can call GetCommandLineA() function to get the command-line. You should never return from the WinMainCRTStartup function, so I am calling the ExitProcess function at end of it. The process will terminate when WinMain returns. You can simplify the code a bit by not calling the WinMain function; but directly write code in WinMainCRTStartup function.

Now if we compile code (and add kernel32.lib for GetModuleHandle and ExitProcess functions) it will succeed and the 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!

If you apply what's done here untill now to bigger programs you'll notice that you cannot use several 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 an array or structure on the 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 a security feature that could help in debugging builds. So maybe you want to keep linking to the C runtime in debug builds, but for a final shipping executable you don't want any additional overhead inserted in your functions. To disable this, simply add /GS- /Gs9999999 options to the commandline.

Additionally you need to add /STACK:0x100000,0x100000 to the linker options so that the executable has full 1MiB of stack available to it. By default the 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. A 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 can the compiler perform 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 the compiler uses a bunch of functions to perform these operations. Source for these functions can be found in asm files in the 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 operations in your code, then you'll get the 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 only the functions needed for the FPU path, no SSE stuff for 32-bit code (which you probably don't 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 need to 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: 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 almost any reasonable code!

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..)