C++: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content added Content deleted
(Added "Local Static Variables")
No edit summary
Line 8: Line 8:
By default, G++ attempts to link in startup code - the stuff usually done before <tt>main()</tt> is called, and after it exits / returns. This is all fine in a hosted environment, but you don't want to have it in your kernel (and it would not compile, either). Disable this by setting <tt>-nostartfiles</tt>.
By default, G++ attempts to link in startup code - the stuff usually done before <tt>main()</tt> is called, and after it exits / returns. This is all fine in a hosted environment, but you don't want to have it in your kernel (and it would not compile, either). Disable this by setting <tt>-nostartfiles</tt>.


===Pure virtual functions===
====Why?====
TODO: Fill this in.



===Pure virtual functions===
If you want to use pure virtual functions, your compiler needs one support function. It is only called in case a pure virtual function call cannot be made (e.g. if you have overridden the virtual function table of an object). But nonetheless your linker will complain about unresolved symbols, if you use pure virtual functions and don't provide that support routine.
If you want to use pure virtual functions, your compiler needs one support function. It is only called in case a pure virtual function call cannot be made (e.g. if you have overridden the virtual function table of an object). But nonetheless your linker will complain about unresolved symbols, if you use pure virtual functions and don't provide that support routine.


===Global objects===
====Why?====
It is a requirement of C++ to provide a backup back up function to call when a virtual function cannot be called.
Global objects must have their constructors called before they are used... and they would usually be called by the startup code you just disabled. So, stay away from global objects until you have set up your own kernel startup code.


===new and delete===
====Enabling pure virtual functions====
To enable the use of virtual functions in GCC, you simply need the following function in one of your .cpp files. You do not need to place a prototype or anything in any of your headers. The contents of the function itself does not need to print an error message or do anything at all, since most implementations simply do nothing if the pure virtual function call cannot be made.
Before you can use new and delete, you have to implement some memory management, and the operator <tt>new()</tt> and operator <tt>delete()</tt> functions (including their array counterparts).


===Builtins===
GCC provides several standard library functions as builtins, which you most likely do not want in your kernel binary either. Disable them with -nostdlib


The following code applies to GCC:
Note: the option <tt>-ffreestanding</tt>, usually recommended in kernel tutorials, cannot be used with G++.

===RTTI===
Run-time type information is used for <tt>typeid</tt> and <tt>dynamic_cast</tt>, and requires run-time support as well. Disable it with <tt>-fno-rtti</tt>.

Note that RTTI is required for some C++ features. If you disable it, you won't be able to use <tt>typeid</tt> or <tt>dynamic_cast</tt>. Virtual functions should work without RTTI, though.

===Exceptions===
Another feature that requires run-time support. Disable them with <tt>-fno-exceptions</tt>.


==Enabling run time support==
===Pure virtual functions===
Pure virtual functions have to be overridden in every class, that should be instantiated. If during runtime your kernel detects that a call to a pure virtual function couldn't be made, it calls the following function:

====GCC====
<pre>
<pre>
extern "C" void __cxa_pure_virtual()
extern "C" void __cxa_pure_virtual()
Line 44: Line 30:
</pre>
</pre>


The following code applies to Visual C++:
====Visual C====
<pre>
<pre>
int __cdecl _purecall()
int __cdecl _purecall()
Line 52: Line 38:
</pre>
</pre>


These functions should actually never be called, because without hacks (or through undefined behaviour of your kernel) it is not possible to instantiate a class that doesn't define all pure virtual functions. But nonetheless you have to define this function or your linker will complain about unresolved symbols.
If during runtime your kernel detects that a call to a pure virtual function couldn't be made, it calls the above functions. These functions should actually never be called, because without hacks (or through undefined behaviour of your kernel) it is not possible to instantiate a class that doesn't define all pure virtual functions. But nonetheless you have to define these functions or your linker will complain about unresolved symbols.



===Global objects===
===Global objects===
Global objects must have their constructors called before they are used... and they would usually be called by the startup code you just disabled. So, stay away from global objects until you have set up your own kernel startup code.

====Why?====
All objects in have constructor and deconstructor code. When an executable code is loaded into memory, and the program jumps straight into main, the constructor code for each global object has not be run. You could do this manually, by calling in the top of <tt>main()</tt>:
<pre>
object1.object1();
object2.object2();
object3.object3();
// etc
</pre>


====Enabling global objects====
Global or static objects have to be constructed by the environment before they are available for C++ code. Care should be taken if global/static objects need new and delete in their constructors. In this case it is best to construct global/static objects only after your kernel heap is ready for use. Not doing so can cause an object to attempt to allocate memory via the non-working new operator. This also simplifies the storing of the destructor functions in <tt>__cxa_atexit</tt>, because you don't have to use a static and fixed-size structure.
Global or static objects have to be constructed by the environment before they are available for C++ code. Care should be taken if global/static objects need new and delete in their constructors. In this case it is best to construct global/static objects only after your kernel heap is ready for use. Not doing so can cause an object to attempt to allocate memory via the non-working new operator. This also simplifies the storing of the destructor functions in <tt>__cxa_atexit</tt>, because you don't have to use a static and fixed-size structure.
GCC (version < 3.2)
GCC (version < 3.2)
Line 63: Line 61:
There also is a "dtors*" list of destructors; if your kernel returns, the exit / cleanup code should also call them in turn. Remember to destruct your objects in the opposite order you have constructed them.
There also is a "dtors*" list of destructors; if your kernel returns, the exit / cleanup code should also call them in turn. Remember to destruct your objects in the opposite order you have constructed them.


====GCC >= 3.2====
=====GCC >= 3.2=====
:GCC 4.0.2 seems to follow the same convention as GCC versions below 3.2. This seems to be independent of what is given with <tt>-fabi-version</tt>. I do get a dtors section.
:GCC 4.0.2 seems to follow the same convention as GCC versions below 3.2. This seems to be independent of what is given with <tt>-fabi-version</tt>. I do get a dtors section.


Line 115: Line 113:
</pre>
</pre>


====Visual C====
=====Visual C=====


Running constructors and destructors is covered in MSDN help and in the C runtime library sources. See <tt>#pragma init_seg</tt> on MSDN for some more information.
Running constructors and destructors is covered in MSDN help and in the C runtime library sources. See <tt>#pragma init_seg</tt> on MSDN for some more information.
Line 221: Line 219:
</pre>
</pre>


===Local Static Variables===


===Local static variables (GCC only)===
When you declare local static variable, at least GCC compiler, puts a guard around variable's constructor call. This ensures that only one thread can call constructor at the same time to initialize it.
When you declare local static variable, at least GCC compiler, puts a guard around variable's constructor call. This ensures that only one thread can call constructor at the same time to initialize it.


====Why?====
====GCC (Don't know from what version. Mine is 4.1.1)====
TODO: Fill this in.

====Enabling local static variables====


Note, that these are only stubs to get the code compiled, and you should implement them yourself. Simply add a mutex like guard with test and set primitive.
Note, that these are only stubs to get the code compiled, and you should implement them yourself. Simply add a mutex like guard with test and set primitive.
Line 277: Line 278:
}
}
</pre>
</pre>



===new and delete===
===new and delete===
Before you can use new and delete, you have to implement some memory management, and the operator <tt>new()</tt> and operator <tt>delete()</tt> functions (including their array counterparts).



Every time you call one of the operators <tt>new()</tt>, <tt>new[]()</tt>, <tt>delete()</tt>, or <tt>delete[]()</tt>, the compiler inserts a call to them. The most simple implementation would be to map them to <tt>kmalloc()</tt> / <tt>kfree()</tt>:
====Why?====
<tt>new()</tt> and <tt>delete()</tt> allocate memory and free memory, respectively. For your kernel to allocate memory, it must somehow store what part of memory is used and what part of memory is free to be divided and allocated.


====Enabling new and delete====
Every time you call one of the operators <tt>new()</tt>, <tt>new[]()</tt>, <tt>delete()</tt>, or <tt>delete[]()</tt>, the compiler inserts a call to them. The most simple implementation would be to map them to <tt>kmalloc()</tt> / <tt>kfree()</tt>: (or malloc() and free() depending on your implementation)


<pre>
<pre>
Line 308: Line 317:
</pre>
</pre>


An easy malloc implementation you can port to your OS is [[liballoc]]. It only requires basic page management (that is, store a list of used and free pages, and have a function to find the next free page) to work.
Note that new can use <tt>kcalloc</tt> (allocate and zero) otherwise the variables will be filled with garbage which you will then need to clear manually. (The standard implementations of <tt>operator new()</tt> and <tt>operator new[]()</tt> do not initialize the memory returned.)

Note the use of the "operator" keyword. A nice option is that <tt>new()</tt> can be overloaded (non-standard but potentially useful).

GCC sometimes emits code that calls operator delete even when you haven't used delete yourself. It seems to sometimes emit a "normal" version of a destructor and a separate version for delete. So you might need to define operator delete even before you have <tt>kmalloc</tt>:

<pre>
void operator delete(void *)
{
error("Someone called operator delete");
}
</pre>


====Other things you can try: Allocate and initialise memory====
This won't be called until you use <tt>new</tt>/<tt>delete</tt>, but it might be needed for linking. This problem seems to appear when classes with pure virtual functions are used.
New can use <tt>kcalloc</tt> (allocate and zero) instead of <tt>kalloc</tt> to allocate memory and intialise it (that is, fill it with '\0's) otherwise the variables will be filled with garbage which you will then need to clear manually. (The standard implementations of <tt>operator new()</tt> and <tt>operator new[]()</tt> do not initialize the memory returned.)


===Placement new===
====Other things you can try: Placement new====
In C++, and especially in OS code where structures can be found at fixed addresses, it can be useful to construct an object in memory obtained elsewhere. This is accomplished through a technique known as placement new. As an example, say you wanted to create an APIC object at address <tt>0x09fff0000</tt>. This snippet of code will use placement new to do the trick:
In C++, and especially in OS code where structures can be found at fixed addresses, it can be useful to construct an object in memory obtained elsewhere. This is accomplished through a technique known as placement new. As an example, say you wanted to create an APIC object at address <tt>0x09fff0000</tt>. This snippet of code will use placement new to do the trick:


<pre>
void* apic_address = reinterpret_cast<void*>(0x09fff0000);
void* apic_address = reinterpret_cast<void*>(0x09fff0000);
APIC* apic = new (apic_address) APIC;
APIC* apic = new (apic_address) APIC;
</pre>


In order to use placement new, you need special overloads of the new and delete operators defined in scope. Fortunately, the required definitions are simple and can be inlined in a header file (the C++ standard puts them in a header called <new>).
In order to use placement new, you need special overloads of the new and delete operators defined in scope. Fortunately, the required definitions are simple and can be inlined in a header file (the C++ standard puts them in a header called <new>).


<pre>
inline void* operator new(uint_t, void* p) throw() { return p; }
inline void* operator new(uint_t, void* p) throw() { return p; }
inline void* operator new[](uint_t, void* p) throw() { return p; }
inline void* operator new[](uint_t, void* p) throw() { return p; }
inline void operator delete (void*, void*) throw() { };
inline void operator delete (void*, void*) throw() { };
inline void operator delete[](void*, void*) throw() { };
inline void operator delete[](void*, void*) throw() { };
</pre>

The above implementation can be potentially unsafe for allocating memory, since your kernel does not mark the memory that was allocated as being used. Placement new is hardly ever used, and if you wish to read an object from a specified address in memory, it is usually easier to create a pointer to that address. [[liballoc]] does not support placement new.


You never call placement delete explicitly (it's only required for certain implementation detail reasons). Instead, you simply invoke your object's destructor explicitly.
You never call placement delete explicitly (it's only required for certain implementation detail reasons). Instead, you simply invoke your object's destructor explicitly.


<pre>
apic->~APIC();
apic->~APIC();
</pre>

===Builtins===
GCC provides several standard library functions as builtins, which you most likely do not want in your kernel binary either. Disable them with -nostdlib

Note: the option <tt>-ffreestanding</tt>, usually recommended in kernel tutorials, cannot be used with G++.

====Why?====
TODO: Fill this in.

===Run-time type information===
Run-time type information is used for <tt>typeid</tt> and <tt>dynamic_cast</tt>, and requires run-time support as well. Disable it with <tt>-fno-rtti</tt>.

Note that RTTI is required for some C++ features. If you disable it, you won't be able to use <tt>typeid</tt> or <tt>dynamic_cast</tt>. Virtual functions should work without RTTI, though.

====Why?====
TODO: Fill this in.




===Exceptions===
===Exceptions===
Another feature that requires run-time support. Disable them with <tt>-fno-exceptions</tt>.

====Why?====
TODO: Fill this in.

====Enabling exceptions====
TODO: Fill this in, rather than just dump links.

* http://www.codesourcery.com/cxx-abi/abi-eh.html (sounds like being Itanium specific, but that's actually the base for the common C++ ABI)
* http://www.codesourcery.com/cxx-abi/abi-eh.html (sounds like being Itanium specific, but that's actually the base for the common C++ ABI)
* http://www.codeproject.com/cpp/exceptionhandler.asp (explaining the stuff, but for VC++. Note that, on x86, VC++ and most other PC compilers use a [[Stack#Unwinding the stack|stack-based unwinding]] and handling mechanism known as SEH, common to OS/2, Windows and Windows NT and described in detail in a famous MSJ article, http://www.microsoft.com/msj/0197/Exception/Exception.aspx. GCC and most other UNIX compilers, instead, use the same table-based mechanism that is the rule on RISC architectures on x86 too. Also note that any use of stack-based SEH may or may not be covered by USPTO patent #5,628,016, held by Borland International, Inc. SEH on RISC architectures is table-based, thus unaffected by the patent)
* http://www.codeproject.com/cpp/exceptionhandler.asp (explaining the stuff, but for VC++. Note that, on x86, VC++ and most other PC compilers use a [[Stack#Unwinding the stack|stack-based unwinding]] and handling mechanism known as SEH, common to OS/2, Windows and Windows NT and described in detail in a famous MSJ article, http://www.microsoft.com/msj/0197/Exception/Exception.aspx. GCC and most other UNIX compilers, instead, use the same table-based mechanism that is the rule on RISC architectures on x86 too. Also note that any use of stack-based SEH may or may not be covered by USPTO patent #5,628,016, held by Borland International, Inc. SEH on RISC architectures is table-based, thus unaffected by the patent)
Line 348: Line 379:
Note that there is a standard header <exception>, declaring several support functions.
Note that there is a standard header <exception>, declaring several support functions.



===Full C++ Runtime support with libgcc and libsupc++===
===Standard Template Library===
You cannot use [[Standard Template Library]] (or STL for shor) functions and classes without porting a Standard Template Library implementation. These include <tt>std::vector</tt>, <tt>std::list</tt>, <tt>std::cin</tt>, <tt>std::cout</tt>, etc.

====Why?====
C++ classes and templates such as <tt>std::vector</tt>, <tt>std::list</tt>, <tt>std::cout</tt>, <tt>std::string</t>, to name a few, are not actually part of the C++ language. They are part of a library called the Standard Template Library. A lot of the code depending on STL is OS-dependent, so you must port an STL implementation to your OS.

====Porting a Standard Template Library====
TODO: Create an article on porting STLport.

To gain access to the STL in your OS you can do either of the following:
- Write your own implementation of a few of the required templates classes (std::string, std::list, std::cout, etc).
- Port an STL implementation to your OS (e.g. [[STLport]]).

A lot of the STD classes require <tt>new</tt> and <tt>delete</tt> implemented in your OS. File access requires your OS to support reading and wrapping. Console functions require your OS to already have working console input/output.

Porting STL, the same with the [[C Standard Library]], do not automatically make your OS to be able to read from and write to the disk, or to get data straight from the keyboard. These are simply wrappers around your OS's functions, and must be implemented by you.

Note that it is generally not a good idea to port the entire OS to your kernel, although it is reasonable to port a few classes, such as <tt>std::list</tt> and <tt>std::string</tt> if you wish to. As for your user applications; the more the merrier! :)


==Full C++ Runtime support with libgcc and libsupc++==
The following description is true for i386, gcc 3.2 and libgcc/libsupc++ compiled for Linux/glibc (you can use the static gcc/supc++ libraries compiled for your Linux for your kernel).
The following description is true for i386, gcc 3.2 and libgcc/libsupc++ compiled for Linux/glibc (you can use the static gcc/supc++ libraries compiled for your Linux for your kernel).


Line 367: Line 419:


You could also cross compile [[libsupcxx|libsupc++]] for your kernel.
You could also cross compile [[libsupcxx|libsupc++]] for your kernel.



== Things you should know about optimizations ==
== Things you should know about optimizations ==

Revision as of 01:56, 30 November 2007

For a quick tutorial on getting a C++ kernel running see C++ kernel

One thing up front: you can do a kernel in C++. It has been done before. This page will not discuss the pros and cons, just tell you about the things you have to be aware of when doing it.

Most features of C++ come "for free", i.e. require no additional supporting work to be used in kernel space. Templates, for example, do not require any additional logic; neither do classes, even when inheritance and virtual functions are involved. Other things do require runtime support. This document will first explain how to turn these features off, and then, gradually, how to implement the required support so you can turn them on again.

Features requiring run time support

Startup code

By default, G++ attempts to link in startup code - the stuff usually done before main() is called, and after it exits / returns. This is all fine in a hosted environment, but you don't want to have it in your kernel (and it would not compile, either). Disable this by setting -nostartfiles.

Why?

TODO: Fill this in.


Pure virtual functions

If you want to use pure virtual functions, your compiler needs one support function. It is only called in case a pure virtual function call cannot be made (e.g. if you have overridden the virtual function table of an object). But nonetheless your linker will complain about unresolved symbols, if you use pure virtual functions and don't provide that support routine.

Why?

It is a requirement of C++ to provide a backup back up function to call when a virtual function cannot be called.

Enabling pure virtual functions

To enable the use of virtual functions in GCC, you simply need the following function in one of your .cpp files. You do not need to place a prototype or anything in any of your headers. The contents of the function itself does not need to print an error message or do anything at all, since most implementations simply do nothing if the pure virtual function call cannot be made.


The following code applies to GCC:

extern "C" void __cxa_pure_virtual()
{
    // print error message
}

The following code applies to Visual C++:

int __cdecl _purecall()
{
    // print error message
}

If during runtime your kernel detects that a call to a pure virtual function couldn't be made, it calls the above functions. These functions should actually never be called, because without hacks (or through undefined behaviour of your kernel) it is not possible to instantiate a class that doesn't define all pure virtual functions. But nonetheless you have to define these functions or your linker will complain about unresolved symbols.


Global objects

Global objects must have their constructors called before they are used... and they would usually be called by the startup code you just disabled. So, stay away from global objects until you have set up your own kernel startup code.

Why?

All objects in have constructor and deconstructor code. When an executable code is loaded into memory, and the program jumps straight into main, the constructor code for each global object has not be run. You could do this manually, by calling in the top of main():

object1.object1();
object2.object2();
object3.object3();
// etc

Enabling global objects

Global or static objects have to be constructed by the environment before they are available for C++ code. Care should be taken if global/static objects need new and delete in their constructors. In this case it is best to construct global/static objects only after your kernel heap is ready for use. Not doing so can cause an object to attempt to allocate memory via the non-working new operator. This also simplifies the storing of the destructor functions in __cxa_atexit, because you don't have to use a static and fixed-size structure. GCC (version < 3.2)

GCC inserts an array of pointers into the object file: Look for the ELF sections called "ctors*". Each pointer indicates the constructor of a global / static object. Your ASM startup code should call them in turn before passing control to your C++ kernel code.

There also is a "dtors*" list of destructors; if your kernel returns, the exit / cleanup code should also call them in turn. Remember to destruct your objects in the opposite order you have constructed them.

GCC >= 3.2
GCC 4.0.2 seems to follow the same convention as GCC versions below 3.2. This seems to be independent of what is given with -fabi-version. I do get a dtors section.

The construction of global/static objects is the same as of older versions of GCC. After you have called the objects constructor GCC automatically calls the function

int __cxa_atexit(void (* f)(void *), void *p, void *d);

f is a function-pointer to the destructor, p is the parameter for the destructor and d is the "home DSO" (DSO = dynamic shared object). This function should save all three parameters and if successful return zero, on failure nonzero. When your kernel exits you should call

void __cxa_finalize(void *d);

with d = 0 in order to destroy all with __cxa_atexit registered objects. Objects, which were registered first with __cxa_atexit, must be destroyed last by __cxa_finalize. You must provide the symbol __dso_handle in your executable. Only the address of this symbol is needed, because GCC calls __cxa_atexit with &__dso_handle.

extern "C"
        {
        int __cxa_atexit(void (*f)(void *), void *p, void *d);
        void __cxa_finalize(void *d);
        };

void *__dso_handle; /*only the address of this symbol is taken by gcc*/

struct object
{
        void (*f)(void*);
        void *p;
        void *d;
} object[32] = {0};
unsigned int iObject = 0;

int __cxa_atexit(void (*f)(void *), void *p, void *d)
{
        if (iObject >= 32) return -1;
        object[iObject].f = f;
        object[iObject].p = p;
        object[iObject].d = d;
        ++iObject;
        return 0;
}

/* This currently destroys all objects */
void __cxa_finalize(void *d)
{
        unsigned int i = iObject;
        for (; i > 0; --i)
        {
                --iObject;
                object[iObject].f(object[iObject].p);
        }
}
Visual C

Running constructors and destructors is covered in MSDN help and in the C runtime library sources. See #pragma init_seg on MSDN for some more information.

Basically what happens is that pointers to functions are placed in .CRT$XIC, $XIL, $XIU based on the value of init_seg. The linker then merges everything together in the .CRT section, in the order of the letters after the $. The pointers between the XIA (xi_a) and XIZ (xi_z) are then called if nonzero. The .CRT section is merged with the .data section to avoid a whole separate section.

One problem with C++ support is the horrible name-mangling that is impossible to read in the map file. A build script should be set up that runs the map file through the undname.exe tool, so that names like ??2@YAPAXI@Z (operator new - I think...) and others are readable.

Here is some code. Sorry for its length, but it is hard to explain any other way. Simply call runInit() when you want to initialize any static objects and then call runTerm() when static object destructors are to be run.

typedef void (*_PVFV)(void);
typedef int  (*_PIFV)(void);
typedef void (*_PVFI)(int);

#pragma data_seg(".CRT$XIA")
__declspec(allocate(".CRT$XIA")) _PIFV __xi_a[] = {0};
#pragma data_seg(".CRT$XIZ")
__declspec(allocate(".CRT$XIZ")) _PIFV __xi_z[] = {0};
#pragma data_seg(".CRT$XCA")
__declspec(allocate(".CRT$XCA")) _PVFV __xc_a[] = {0};
#pragma data_seg(".CRT$XCZ")
__declspec(allocate(".CRT$XCZ")) _PVFV __xc_z[] = {0};
#pragma data_seg(".CRT$XPA")
__declspec(allocate(".CRT$XPA")) _PVFV __xp_a[] = {0};
#pragma data_seg(".CRT$XPZ")
__declspec(allocate(".CRT$XPZ")) _PVFV __xp_z[] = {0};
#pragma data_seg(".CRT$XTA")
__declspec(allocate(".CRT$XTA")) _PVFV __xt_a[] = {0};
#pragma data_seg(".CRT$XTZ")
__declspec(allocate(".CRT$XTZ")) _PVFV __xt_z[] = {0};
#pragma data_seg()
#pragma comment(linker, "/merge:.CRT=.data")

static _PVFV onexitarray[32];
static _PVFV *onexitbegin, *onexitend;

int __cdecl _purecall()
{
        // print error message
}

int __cdecl atexit(_PVFV fn)
{
        if (32*4 < ((int)onexitend-(int)onexitbegin)+4)
                return 1;
        else
                *(onexitend++) = fn;
        return 0;
}

EXTERN int runInit()
{
        // init the __xi_a to __xi_z:  __initex(__xi_a, __xi_z);
        // init __xc_a to __xc_z
}

static void __init(_PVFV *pfbegin, _PVFV *pfend)
{
    while (pfbegin < pfend)
    {
        if (*pfbegin != 0)
            (**pfbegin)();
        ++pfbegin;
    }
}

static int __initex(_PIFV *pfbegin, _PIFV *pfend)
{
        int ret = 0;

        while (pfbegin < pfend && ret == 0)
        {
            if (*pfbegin != 0)
                ret = (**pfbegin)();
            ++pfbegin;
        }

        return ret;
}

EXTERN void runUninit()
{
        if (onexitbegin)
        {
                while (--onexitend >= onexitbegin)
                        if (*onexitend != 0)
                                (**onexitend)();
        }

        __init(__xp_a, __xp_z);
        __init(__xt_a, __xt_z);
}

EXTERN int onexitinit()
{
        onexitend = onexitbegin = onexitarray;
        *onexitbegin = 0;
        return 0;
}

#pragma data_seg(".CRT$XIB")      // run onexitinit automatically
__declspec(allocate(".CRT$XIB")) static _PIFV pinit = onexitinit;
#pragma data_seg()


Local static variables (GCC only)

When you declare local static variable, at least GCC compiler, puts a guard around variable's constructor call. This ensures that only one thread can call constructor at the same time to initialize it.

Why?

TODO: Fill this in.

Enabling local static variables

Note, that these are only stubs to get the code compiled, and you should implement them yourself. Simply add a mutex like guard with test and set primitive.

namespace __cxxabiv1 
{
	/* guard variables */

	/* The ABI requires a 64-bit type.  */
	__extension__ typedef int __guard __attribute__((mode (__DI__)));

	extern "C" int __cxa_guard_acquire (__guard *);
	extern "C" void __cxa_guard_release (__guard *);
	extern "C" void __cxa_guard_abort (__guard *);

	extern "C" int __cxa_guard_acquire (__guard *g) 
	{
		return !*(char *)(g);
	}

	extern "C" void __cxa_guard_release (__guard *g)
	{
		*(char *)g = 1;
	}

	extern "C" void __cxa_guard_abort (__guard *)
	{
	}
}

Actual code, emited by GCC, to call local static variable's constructor looks something like this:

static <type> guard;
if (!guard.first_byte) {
	if (__cxa_guard_acquire (&guard)) {
		bool flag = false;
		try {
			// Do initialization.
			flag = true;
			__cxa_guard_release (&guard);
			// Register variable for destruction at end of program.
		} catch {
			if (!flag) {
				__cxa_guard_abort (&guard);
			}
		}
	}
}


new and delete

Before you can use new and delete, you have to implement some memory management, and the operator new() and operator delete() functions (including their array counterparts).


Why?

new() and delete() allocate memory and free memory, respectively. For your kernel to allocate memory, it must somehow store what part of memory is used and what part of memory is free to be divided and allocated.


Enabling new and delete

Every time you call one of the operators new(), new[](), delete(), or delete[](), the compiler inserts a call to them. The most simple implementation would be to map them to kmalloc() / kfree(): (or malloc() and free() depending on your implementation)

//overload the operator "new"
void * operator new (uint_t size)
{
    return kmalloc(size);
}

//overload the operator "new[]"
void * operator new[] (uint_t size)
{
    return kmalloc(size);
}

//overload the operator "delete"
void operator delete (void * p)
{
    kfree(p);
}

//overload the operator "delete[]"
void operator delete[] (void * p)
{
    kfree(p);
}

An easy malloc implementation you can port to your OS is liballoc. It only requires basic page management (that is, store a list of used and free pages, and have a function to find the next free page) to work.

Other things you can try: Allocate and initialise memory

New can use kcalloc (allocate and zero) instead of kalloc to allocate memory and intialise it (that is, fill it with '\0's) otherwise the variables will be filled with garbage which you will then need to clear manually. (The standard implementations of operator new() and operator new[]() do not initialize the memory returned.)

Other things you can try: Placement new

In C++, and especially in OS code where structures can be found at fixed addresses, it can be useful to construct an object in memory obtained elsewhere. This is accomplished through a technique known as placement new. As an example, say you wanted to create an APIC object at address 0x09fff0000. This snippet of code will use placement new to do the trick:

 void* apic_address = reinterpret_cast<void*>(0x09fff0000);
 APIC* apic = new (apic_address) APIC;

In order to use placement new, you need special overloads of the new and delete operators defined in scope. Fortunately, the required definitions are simple and can be inlined in a header file (the C++ standard puts them in a header called <new>).

 inline void* operator new(uint_t, void* p)   throw() { return p; }
 inline void* operator new[](uint_t, void* p) throw() { return p; }
 inline void  operator delete  (void*, void*) throw() { };
 inline void  operator delete[](void*, void*) throw() { };

The above implementation can be potentially unsafe for allocating memory, since your kernel does not mark the memory that was allocated as being used. Placement new is hardly ever used, and if you wish to read an object from a specified address in memory, it is usually easier to create a pointer to that address. liballoc does not support placement new.

You never call placement delete explicitly (it's only required for certain implementation detail reasons). Instead, you simply invoke your object's destructor explicitly.

 apic->~APIC();

Builtins

GCC provides several standard library functions as builtins, which you most likely do not want in your kernel binary either. Disable them with -nostdlib

Note: the option -ffreestanding, usually recommended in kernel tutorials, cannot be used with G++.

Why?

TODO: Fill this in.

Run-time type information

Run-time type information is used for typeid and dynamic_cast, and requires run-time support as well. Disable it with -fno-rtti.

Note that RTTI is required for some C++ features. If you disable it, you won't be able to use typeid or dynamic_cast. Virtual functions should work without RTTI, though.

Why?

TODO: Fill this in.


Exceptions

Another feature that requires run-time support. Disable them with -fno-exceptions.

Why?

TODO: Fill this in.

Enabling exceptions

TODO: Fill this in, rather than just dump links.

Note that there is a standard header <exception>, declaring several support functions.


Standard Template Library

You cannot use Standard Template Library (or STL for shor) functions and classes without porting a Standard Template Library implementation. These include std::vector, std::list, std::cin, std::cout, etc.

Why?

C++ classes and templates such as std::vector, std::list, std::cout, std::string</t>, to name a few, are not actually part of the C++ language. They are part of a library called the Standard Template Library. A lot of the code depending on STL is OS-dependent, so you must port an STL implementation to your OS.

Porting a Standard Template Library

TODO: Create an article on porting STLport.

To gain access to the STL in your OS you can do either of the following: - Write your own implementation of a few of the required templates classes (std::string, std::list, std::cout, etc). - Port an STL implementation to your OS (e.g. STLport).

A lot of the STD classes require new and delete implemented in your OS. File access requires your OS to support reading and wrapping. Console functions require your OS to already have working console input/output.

Porting STL, the same with the C Standard Library, do not automatically make your OS to be able to read from and write to the disk, or to get data straight from the keyboard. These are simply wrappers around your OS's functions, and must be implemented by you.

Note that it is generally not a good idea to port the entire OS to your kernel, although it is reasonable to port a few classes, such as std::list and std::string if you wish to. As for your user applications; the more the merrier! :)


Full C++ Runtime support with libgcc and libsupc++

The following description is true for i386, gcc 3.2 and libgcc/libsupc++ compiled for Linux/glibc (you can use the static gcc/supc++ libraries compiled for your Linux for your kernel).

If you want Exceptions, RTTI, nice new and delete operators altogether, you also could use libgcc and libsupc++. libgcc contains the unwinder (for exceptions), while libsupc++ contains the C++ support.

These functions look very complex (gcc_sources/gcc/unwind*, gcc_sources/libstdc++-v3/libsupc++/*), so maybe you shouldn't try to write an own unwinder or such, since there would be no benefit...

To get full C++ support, you only have to do the following:

  • provide some libc functions like abort, malloc, free (and some more, maybe). libsupc++ needs them. There are even more functions you could support, like pthread_*, but since these are weak symbols, you don't need to define them.
  • there's also a strange function dl_iterate_phdrs; you don't need this, let it simply return -1. It's normally used to find exception frames for dynamical linked objects... You also could compile libsupc++ in such a way, that this function isn't called anymore.
  • To make use of exception handling, you also have to tell libsupc++ where the .eh_frames section begins, before you throw any exception: <verbatim> __register_frame(address_of_eh_frames); </verbatim>.
  • Terminate the .eh_frame section with 4 bytes of zeros, somehow. If you forget this, libsupc++ will never find the end of .eh_frame and generate stupid pagefaults.

That's all. Please note that you still have to call the constructors/destructors by yourself.

Additionally, this sadly enlarges your kernel by ca. 50K (or even more).

You could also cross compile libsupc++ for your kernel.


Things you should know about optimizations

There are things you should know about optimizations that also affect C++ because it is an extension of the C language. You should know about them even if you don't plan to to use the optimizer of your C++ compiler in the near future.