Monday, July 16, 2007

64 KB Demos: Replacing the CRT heap

Every so often I visit Pouet.net to see what the demo scene is up to. I've been looking in on the scene for ten years or so, and it's generally inspiring. My current favorite category is 64KB Windows intros (the executable cannot exceed 64 kilobytes in size). Heaven Seven is a good example (disregard the quasi-profundity), although there are some other good ones. The Farbrausch group produces many of the best.

A few years ago I finally decided to see what I could do in 64 KB. I managed to get the basics of a first-person shooter up and running in around 16 KB, and I'll share a few of the things I learned along the way.

I wrote a framework in assembly language but I don't think that's necessarily required. What is necessary is to examine the assembly output of your compiler so you know where the bytes are going. Also, grab an executable compressor. I used UPX but I also recently found out about Crinkler.

One of the big space users when you're coding in C/C++ is the C Runtime Library, a set of commonly-used functions that are statically linked into every executable. Getting rid of this saves a big chunk of executable size.

The primary thing the C Runtime Library provides is a heap manager. Fortunately, the Windows API also provides a heap manager, so all that's necessary to replace it is to write a few glue functions to hook up C++ to the Windows heap. Here's my header file win32_new.h:

#include <new>
#include <exception>

// Overload global heap allocation functions

void* __cdecl operator new (size_t num_bytes);
void* __cdecl operator new[](size_t num_bytes);
void __cdecl operator delete (void* memory);
void __cdecl operator delete[](void* memory);

inline void __cdecl std::_Throw(const std::exception &)
{
error_exit("std::_Throw called.");
}


And the corresponding source file, win32_new.cpp:

#include "win32_new.h"
#include <windows.h>
#include <string>

typedef unsigned short wchar_t;

void* __cdecl operator new(size_t num_bytes)
{
return HeapAlloc(GetProcessHeap(), 0, num_bytes);
}

void* __cdecl operator new[](size_t num_bytes)
{
return HeapAlloc(GetProcessHeap(), 0, num_bytes);
}

void __cdecl operator delete(void* memory)
{
HeapFree(GetProcessHeap(), 0, memory);
}

void __cdecl operator delete[](void* memory)
{
HeapFree(GetProcessHeap(), 0, memory);
}

void __stdcall raise_handler(const std::exception &)
{
error_exit("raise_handler called.");
}

void (__stdcall* std::_Raise_handler)(const std::exception &) = raise_handler;

void* __cdecl memmove(void *dest, const void *src, size_t count)
{
return wmemmove(reinterpret_cast<wchar_t *> (dest), reinterpret_cast<const wchar_t *>(src), count);
}

void std::_String_base::_Xlen() const // report a length_error
{
error_exit("std::_String_base::_Xlen called.");
}

void std::_String_base::_Xran() const // report an out_of_range error
{
error_exit("std::_String_base::_Xran called.");
}


The C++ new and delete operators are overridden to call the appropriate Win32 functions. I also provided definitions for a few other functions.

No comments:

Post a Comment