C++: Difference between revisions

980 bytes removed ,  24 days ago
m
Bot: Replace deprecated source tag with syntaxhighlight
[unchecked revision][unchecked revision]
No edit summary
m (Bot: Replace deprecated source tag with syntaxhighlight)
 
(10 intermediate revisions by 9 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 198:
};
 
forwhile ( ; i >= 0; --i)
{
/*
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 481:
See [[C++ Exception Support]].
 
== Standard Library ==
== Solving .rodata Related Issues (GCC Only) ==
Note that the C++ Standard Library (stdlib) is not the same as the C++ Standard Template Library (STL). The STL was designed in 1994 and largely influenced the C++ Standard Library, but it's not a part of the ISO C++ standard. The C++ Standard Library is part of the C++ ISO specification, however, and is what you're using when you use ''std::vector'', ''std::string'', etc. Be wary of misusing the term STL and, ideally, avoid it completely. Anyone using it almost certainly means the C++ stdlib.
After a while, it may happen that your kernel starts crashing because of corrupt data on the .rodata section, typically missing or corrupt strings or even corrupt vtables, this usually happens because not all the sections GCC generates were properly linked in the final binary.
If your kernel happens to crash on a specific line of code that is not doing anything potentially harmful (ie: a function call), and after commenting some unrelated lines (probably lines that declare string constants), the kernel starts working as intended again, you should check your Linker Script for the following:
 
You cannot use stdlib functions or classes without porting a stdlib implementation. A lot of existing code depending on the stdlib is OS-dependent, so you must port an stdlib implementation to your OS if you want to use them.
*sections should be page aligned
*text, data, rodata and bss input sections should be declared with a wildcard at the end *(.section_name*), since GCC may just use those names as a prefix for the actual section names, the wildcard character takes care of that.
*Put the (.rodata*) input sections insde the .text output section
*(.gnu.linkonce.t.*) and *(.gnu.linkonce.r.*) should also be included in .text
*(.ctor*) and (.dtor*) sections should be added to .text for constructors and destructors
*(.gnu.linkonce.d.*) should be included in .data
*(.gnu.linkonce.b.*) should be included in .bss
 
To gain access to the stdlib in your OS you can do either of the following:
Here is a sample link.ld file:
* Write your own implementation of a few of the required class templates (std::string, std::list, std::cout, ...).
<pre>
* Port a stdlib implementation to your OS (e.g. [[STLport]]).
OUTPUT_FORMAT("elf64-x86-64")
ENTRY(start)
SECTIONS
{
.text 0x100000 :
{
*(.text*)
*(.gnu.linkonce.t.*)
*(.rodata*)
*(.gnu.linkonce.r.*)
*(SORT(.ctor*))
*(SORT(.dtor*))
. = ALIGN(4096);
}
data = .;
.data :
{
*(.data*)
*(.gnu.linkonce.d.*)
. = ALIGN(4096);
}
bss = .;
.bss :
{
*(.bss*)
*(.gnu.linkonce.b.*)
*(COMMON)
. = ALIGN(4096);
}
}
</pre>
 
A lot of the stdlib classes require '''new''' and '''delete''' to be implemented in your OS. File access requires your OS to support reading and wrapping. Console functions require your OS to already have working console I/O.
== Standard Template Library ==
You cannot use STL ([[Standard Template Library]]) functions or classes without porting an STL implementation. Note that C++ classes and templates (e.g. std::vector, std::string) actually aren't 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 if you want to use them.
 
Porting the C++ stdlib (like porting the [[C_Library|C Standard Library]]) does not automatically make your OS able to read from and write to the disk or get data straight from the keyboard. These are simply wrappers around your OS' functions, and must be implemented by in your kernel.
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, ...).
* Port an STL implementation to your OS (e.g. [[STLport]]).
 
Note that it is generally not a good idea to port the entire stdlib to your kernel, although it is reasonable to port a few class templates, such as <tt>std::vector</tt> and <tt>std::string</tt> if you wish to. As for your user applications: the more the merrier! :)
A lot of the STL classes require '''new''' and '''delete''' to be implemented in your OS. File access requires your OS to support reading and wrapping. Console functions require your OS to already have working console I/O.
 
Here is a list of a the most commonly used stdlib implementations:
Porting the STL (like porting the [[C Standard Library]]) does not automatically make your OS able to read from and write to the disk or get data straight from the keyboard. These are simply wrappers around your OS' functions, and must be implemented by in your kernel.
 
Note that it is generally not a good idea to port the entire STL to your kernel, although it is reasonable to port a few classes, such as <tt>std::vector</tt> and <tt>std::string</tt> if you wish to. As for your user applications: the more the merrier! :)
 
Here is a list of a the most commonly used STL implementations:
 
* [http://incubator.apache.org/stdcxx/ STDCXX] (a.k.a Apache C++ Standard Library, formally Rogue Wave C++ Standard Library)
* [https://www.dinkumware.com/cpp.aspx Dinkumware C++ Standard Library]
* [http://msdn2.microsoft.com/en-us/library/cscc687y%28VS.80%29.aspx Microsoft C++ Standard Library] (closed source)
* [http://gcc.gnu.org/libstdc++/ libstdc++] (a.k.a. GNU Standard C++ Library)
* [http://www.stlport.org/ STLport]
* [http://ustl.sourceforge.net/ uSTL]
* [http://libcxx.llvm.org/ libc++] (LLVM C++ Standard library)
 
== Full C++ Runtime Support Using libgcc And libsupc++ ==
Line 553 ⟶ 510:
 
If you want Exceptions, RTTI, new and delete altogether, you should use [[libgcc]] and libsupc++. libgcc contains the unwinder (for exceptions), while libsupc++ contains the C++ support.
 
You may run into problems with libsupc++, but there are [[GCC_and_Libc++|alternative libraries]].
 
== Optimizations ==
Line 566 ⟶ 525:
[[Category:C++]]
[[de:C++]]
 
===External===
* [http://geezer.osdevbrasil.net/osd/cpp/index.htm Common C++ Kernel issues]