C++: Difference between revisions

Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content deleted Content added
No edit summary
No edit summary
Line 6: Line 6:
==Features requiring run time support==
==Features requiring run time support==
===Startup code===
===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.
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===
===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 overriden 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===
===Global objects===
Line 16: Line 16:


===new and delete===
===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).
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===
===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
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++.
Note: the option <tt>-ffreestanding</tt>, usually recommended in kernel tutorials, cannot be used with G++.


===RTTI===
===RTTI===
Run-time type information is used for typeid and dynamic_cast, and requires run-time support as well. Disable it with -fno-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 typeid or dynamic_cast. Virtual functions should work without RTTI, though.
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===
===Exceptions===
Another feature that requires run-time support. Disable them with -fno-exceptions.
Another feature that requires run-time support. Disable them with <tt>-fno-exceptions</tt>.




Line 56: Line 56:
===Global objects===
===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.
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 64: Line 64:


====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 -fabi-version. 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.


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
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
Line 70: Line 70:
int __cxa_atexit(void (* f)(void *), void *p, void *d);
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
<tt>f</tt> is a function-pointer to the destructor, <tt>p</tt> is the parameter for the destructor and <tt>d</tt> 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);
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.
with d = 0 in order to destroy all with <tt>__cxa_atexit</tt> registered objects. Objects, which were registered first with <tt>__cxa_atexit</tt>, must be destroyed last by <tt>__cxa_finalize</tt>. You must provide the symbol <tt>__dso_handle</tt> in your executable. Only the address of this symbol is needed, because GCC calls <tt>__cxa_atexit</tt> with <tt>&__dso_handle</tt>.


<pre>
<pre>
Line 117: Line 117:
====Visual C====
====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.
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.


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.
Basically what happens is that pointers to functions are placed in <tt>.CRT$XIC, $XIL, $XIU</tt> based on the value of <tt>init_seg</tt>. The linker then merges everything together in the <tt>.CRT</tt> section, in the order of the letters after the <tt>$.</tt> The pointers between the XIA (<tt>xi_a</tt>) and XIZ (<tt>xi_z</tt>) are then called if nonzero. The <tt>.CRT</tt> 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.
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 <tt>undname.exe</tt> tool, so that names like <tt>??2@YAPAXI@Z</tt> (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.
Here is some code. Sorry for its length, but it is hard to explain any other way. Simply call <tt>runInit()</tt> when you want to initialize any static objects and then call <tt>runTerm()</tt> when static object destructors are to be run.


<pre>
<pre>
Line 225: Line 225:
===new and delete===
===new and delete===


Everytime 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():
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>:


<pre>
<pre>
Line 253: Line 253:
</pre>
</pre>


Note that new should actually use kcalloc (allocate and zero) otherwise the variables will be filled with garbage which you will then need to clear manually
Note that new should actually use <tt>kcalloc</tt> (allocate and zero) otherwise the variables will be filled with garbage which you will then need to clear manually


Note the use of the "operator" keyword. A nice option is that new() can be overloaded (non-standard but potentially useful).
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 kmalloc:
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>
<pre>
Line 266: Line 266:
</pre>
</pre>


This won't be called until you use new/delete, but it might be needed for linking. This problem seems to appear when classes with pure virtual functions are used.
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.




===Placement new===
===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:
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:


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