C++: Difference between revisions

195 bytes added ,  26 days ago
m
Bot: Replace deprecated source tag with syntaxhighlight
[unchecked revision][unchecked revision]
m (Bot: Replace deprecated source tag with syntaxhighlight)
 
(2 intermediate revisions by 2 users not shown)
Line 21:
Below you will find an example of an implementation in respectively GCC.
 
<sourcesyntaxhighlight lang="cpp">
extern "C" void __cxa_pure_virtual()
{
// Do nothing or print an error message.
}
</syntaxhighlight>
</source>
 
Or, if you happen to use Visual Studio:
 
<sourcesyntaxhighlight lang="cpp">
int __cdecl _purecall()
{
// Do nothing or print an error message.
}
</syntaxhighlight>
</source>
 
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.
Line 47:
Global objects must have their constructors called before they are used. Usually, they are called by the start-up code (which you just disabled). So, in order to be able to use them, you have to write your own start-up code for them. All objects have a constructor and a destructor. When an executable is loaded into memory and the program jumps straight to the entry point, the constructors of global objects will not have been called. One solution is to do this manually. You could put this code first when your C++ entry point is called:
 
<sourcesyntaxhighlight lang="cpp">
object1.object1();
object2.object2();
object3.object3();
// ...
</syntaxhighlight>
</source>
 
Global or static objects have to be constructed by the environment before they are available to C++. Care should be taken if global/static objects need '''new''' and '''delete''' in their constructors. In this case it is best to construct these objects only after your kernel heap is ready for use (and you have access to dynamic memory allocation). 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.
Line 64:
In the example implementation of '''__cxa_atexit''', the '''__atexit_funcs[ATEXIT_MAX_FUNCS]''' array acts as the table. This is why the '''__cxa_atexit''' function is defined as:
 
<sourcesyntaxhighlight lang="cpp">
int __cxa_atexit(void (*destructor) (void *), void *arg, void *__dso_handle);
</syntaxhighlight>
</source>
 
So that the '''destructor''' function pointer is the handle for a destructor function and '''arg''' is the single argument it may take. Finally, '''__dso_handle''' is a handle for the DSO (Dynamic Shared Object).
Line 72:
So summarized, you are required to define two functions and one symbol in order to use global objects in your C++ files:
 
<sourcesyntaxhighlight lang="cpp">
void *__dso_handle;
 
int __cxa_atexit(void (*destructor) (void *), void *arg, void *dso);
void __cxa_finalize(void *f);
</syntaxhighlight>
</source>
 
After you have called the objects constructor GCC automatically calls the function
 
<sourcesyntaxhighlight lang="cpp">
int __cxa_atexit(void (*destructor) (void *), void *arg, void *dso);
</syntaxhighlight>
</source>
 
This function should save all three parameters and if successful return zero, on failure non-zero. When your kernel exits you should call '''__cxa_finalize(0)'''. According to the ABI specification, calling this with 0 as the parameter instead of the address of a function (to be called and removed from the list) causes ''all'' destructors in the list to be called and removed from the list.
Line 89:
Since you will be calling this function from your Assembly source right after your kernel exits, you could use the following code:
 
<sourcesyntaxhighlight lang="asm">
; This is NASM source, mind you.
sub esp, 4
Line 97:
 
add esp, 4
</syntaxhighlight>
</source>
 
The following is tested, working, fully commented source that gives a more detailed explanation than the source previously found here. It also highlights what improvements can be implemented and where they can be inserted. To use it, just include '''icxxabi.h''' in any '''one''' file of your C++ kernel source (preferably the file where your kernel's main statements begin).
 
'''File: icxxabi.h'''
<sourcesyntaxhighlight lang="cpp">
#ifndef _ICXXABI_H
#define _ICXXABI_H
Line 133:
 
#endif
</syntaxhighlight>
</source>
 
 
'''File: icxxabi.cpp'''
<sourcesyntaxhighlight lang="cpp">
 
#include "./icxxabi.h"
Line 242:
};
#endif
</syntaxhighlight>
</source>
 
=== Visual C++ ===
Line 254:
Below you will find some example code. Simply call '''runInit()''' if you want to initialize any static objects and then call '''runTerm()''' if static object destructors are to be run.
 
<sourcesyntaxhighlight lang="cpp">
typedef void (*_PVFV)(void);
typedef int (*_PIFV)(void);
Line 348:
__declspec(allocate(".CRT$XIB")) static _PIFV pinit = onexitinit;
#pragma data_seg()
</syntaxhighlight>
</source>
 
== Local Static Variables (GCC Only) ==
Line 355:
''Note that these are only stubs to get the code compiled, and you should implement them yourself. Simply add a mutex-like guard with a test-and-set primitive.''
 
<sourcesyntaxhighlight lang="cpp">
namespace __cxxabiv1
{
Line 382:
}
}
</syntaxhighlight>
</source>
 
The actual code emitted by GCC to call a local static variable's constructor looks something like this:
 
<sourcesyntaxhighlight lang="cpp">
static <type> guard;
 
Line 413:
}
}
</syntaxhighlight>
</source>
 
== The Operators 'new' and 'delete' ==
Line 420:
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 your kernel's '''malloc''' and '''free'''. For example:
 
<sourcesyntaxhighlight lang="cpp">
#include <stddef.h>
 
Line 442:
free(p);
}
</syntaxhighlight>
</source>
 
You could also let '''new''' use '''calloc''' (allocate and zero). This way, newly allocated memory will always be zeroed (thus, not contain garbage). The standard '''new''' implementations do however not clear the returned memory.
Line 451:
In C++ (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'. For example, say you wanted to create an APIC object at address '''0x09FFF0000''', then this snippet of code will use placement new to do the trick:
 
<sourcesyntaxhighlight lang="cpp">
void *apic_address = reinterpret_cast<void *>(0x09FFF0000);
APIC *apic = new (apic_address) APIC;
</syntaxhighlight>
</source>
 
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''').
 
<sourcesyntaxhighlight lang="cpp">
inline void *operator new(size_t, void *p) throw() { return p; }
inline void *operator new[](size_t, void *p) throw() { return p; }
inline void operator delete (void *, void *) throw() { };
inline void operator delete[](void *, void *) throw() { };
</syntaxhighlight>
</source>
 
The above implementation can potentially be 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.
Line 469:
You never call placement delete explicitly (it's only required for certain implementation detail reasons). Instead, you simply invoke your object's destructor explicitly.
 
<sourcesyntaxhighlight lang="cpp">
apic->~APIC();
</syntaxhighlight>
</source>
 
== RTTI (Run-Time Type Information) ==
Line 525:
[[Category:C++]]
[[de:C++]]
 
===External===
* [http://geezer.osdevbrasil.net/osd/cpp/index.htm Common C++ Kernel issues]