I read somewhere that when you develop a kernel in c++, that the new, delete and static_cast keywords require timing references... I was unable to find the site again but my question is, why does these keywords require timing references?
MasterHacker110 -4 Newbie Poster
Ancient Dragon 5,243 Achieved Level 70 Team Colleague Featured Poster
maybe for thread safety? By "kernel" do you mean your own operating system?
mike_2000_17 2,669 21st Century Viking Team Colleague Featured Poster
I think it's slightly off, but close. A typical point that is raised when discussing the idea of writing kernel code (or similar high-performance low-level code) in C++ is that operations such as new
, delete
and dynamic_cast
(not static_cast
) do not have a deterministic timing (or latency). In other words, when you are talking about simple operations, such as an integer addition (c = a + b;
), the operation will translate to a few machine instructions that will execute in a finite and predictable amount of time (number of clock cycles). At the level of kernel code, this kind of predictable latency is a very important thing (e.g., if you are writing the kernel's task scheduler to switch between execution threads, then you need to be able to estimate a guaranteed upper-bound on the time required for your scheduler to do its work or for the time that it will take the wake-up a sleeping thread, and so on...).
Now, operations like new
/ delete
(or malloc / free), which would need to be implemented differently for kernel code, cannot have a predictable latency or even a guaranteed upper-bound on it. This is because the time that it will take for the heap to find a proper chunk of memory to allocate is simply not predictable as it depends on the amount of memory currently being managed, on the amount of memory still available, and on the level of fragmentation of the RAM memory. So, any kernel coder who is coding a critical piece of software must be aware of this fact.
As for dynamic_cast
, this is a matter of invocation of the RTTI (Run-Time Type Identification). A dynamic-cast involves checking that the cast is possible at run-time, i.e., to check if the object is of a sufficiently derived class to be able to be cast to the desired derived class. Classes could be derived infinitely many times and most often the run-time check will involve something similar to a linked-list traversal of the run-time type information (e.g., say I have a A*
pointer that I try to cast to B*
, I invoke a dynamic_cast<B*>(pa)
which identifies (at run-time) that the object is of class E
, derived from D
, derived from C
, and derived from B
, which allows for the cast to happen). This means that there is no way to guarantee that this operation will return within a fixed amount of time, because it depends on too many factors (not the least of which being how the RTTI system is implemented).
You might also hear the same argument about C++ exceptions not having a deterministic latency between throwing an exception and catching it. This is a debatable issue, and people disagree. Error recovery paths are always unpredictable in that manner, whether you use exceptions or C-style error-code mechanisms. In modern exception implementations, there is no reason why exceptions would have more unpredictable latencies as compared to more traditional C-style error recovery paths.
In any case, these are the main things that people point out to be cautious about when using C++ for high-performance low-level low-latency code such as kernel code. It's just part of the art of writing that kind of code.
Edited by mike_2000_17 because: typos
rubberman 1,355 Nearly a Posting Virtuoso Featured Poster
If you are writing Linux kernel code and drivers (or for Solaris/BSD Unix), it is STRONGLY recommended that you stick with C and not C++. C is often considered a high-level assembler language, whereas though there are C++ implementations that are targeted at real-time applications and possibly kernel development, they remove use of things like virtual functions (including destructors) that cannot be deterministic. RTTI is also deprecated for the same reason.
Edited by rubberman
mike_2000_17 2,669 21st Century Viking Team Colleague Featured Poster
they remove use of things like virtual functions (including destructors) that cannot be deterministic.
I'm not sure of that. It would depend on what parts of the kernel you are talking about. For example, the Linux kernel does use dynamic polymorphism (dynamic dispatching / virtual functions) for its file-system management. Virtual functions are not generally excluded from hard real-time applications or things like kernel or embedded code, because virtual function calls are deterministic. A virtual function call is no different from calling a function pointer, which will at most (AFAIK) cause one cache miss. And AFAIK, function pointers are heavily used in kernel code.
Multiple inheritance and virtual inheritance are different though, because they allow for arbitrary long dynamic dispatching paths, and thus, non-deterministic latencies.
When it comes to latency, the main thing to avoid is dispatching paths that have a non-constant dynamic complexity. A simple dynamic dispatch (virtual call) is O(1) with a statically predictable upper-bound on the latency. A cast from a derived class to a virtual base is usually O(N) in the number of occurrences of the virtual base within the inheritance graph (which is determined at run-time). For the same reason, a dynamic-cast from a base class to a derived class is usually O(N) in the total number of derived classes (also only known at run-time). I say "usually" because it's probably possible to do better (with some memory overhead) but the assumption is that it is not better than O(N) for these things.
RTTI is also deprecated for the same reason.
I'm not entirely sure about this. I know that in resource-deprived environments such as embedded systems, the RTTI is excluded (turned off and forbidden) to avoid the fairly large overhead it causes in terms of executable size and memory (RAM). But in kernel code, the main thing to avoid is the dynamic_cast
operations or similar queries that involve walking up or down the run-time class hierarchy of an arbitrary object. But I don't see any problem (except the static memory overhead) to use the RTTI for simpler queries such as a simple typeid
call. Such simple queries of the RTTI are usually implemented as one virtual function call (at least, that's how my RTTI system works).
That said, I would never use the standard C++ RTTI for kernel code. But I would probably write my own replacement for it (which is quite easy), like the LLVM people did to reduce the overhead and limit scope to better reflect their needs.
It's important to remember that kernel-code C++ is not Embedded C++ (EC++). In EC++, there are things that are removed purely for size optimization reasons (templates, RTTI, namespaces) (and often with extreme prejudice), there are things that are removed for non-deterministic latencies (dynamic-cast, multiple/virtual inheritance), and others are just removed by fear of the unknown (exceptions) (and I don't mean that in the bad sense, fear of the unknown means not allowing a feature most people are not competent at using).
So, if you consider the case of a team of very competent C++ programmers setting out to write a completely new OS kernel from scratch (and not a "tiny" kernel), then the restrictions would be far less than EC++, IMHO. Sure, they would substitute the standard RTTI with a more limited and hand-rolled implementation (if any). Sure, they would naturally restrict the use of OOP style to where it's appropriate, probably fobidding the use of multiple/virtual inheritance. And they would be extremely careful in crafting their exception-using code and their exception-safety requirements. And, of course, apply the same caution with using new / delete as they do with using malloc / free. But I don't think there is need to drastically subset the C++ language (at least, not to the extent of Embedded-C++).
I think most of the cautionary tales about not using C++ for kernel code or other very low-level stuff is mostly a matter of tradition (very dominated by C programmers) and misunderstanding (dominated by programmers with little understanding of C++, and often with a prejudice against it.. cough... Linus Torvald ... cough..). In any case, kernel coding is hard enough as it is, so if those with the competence to write kernel code like the C language better, let them use it.
rubberman commented: Well put - and in some regards I stand corrected. You have waxed more expressive than I was willing to do... :-) +12
rubberman 1,355 Nearly a Posting Virtuoso Featured Poster
@mike2k "I think most of the cautionary tales about not using C++ for kernel code or other very low-level stuff is mostly a matter of tradition (very dominated by C programmers) and misunderstanding (dominated by programmers with little understanding of C++, and often with a prejudice against it.. cough... Linus Torvald ... cough..). In any case, kernel coding is hard enough as it is, so if those with the competence to write kernel code like the C language better, let them use it."
Agree entirely! :-)
rubberman 1,355 Nearly a Posting Virtuoso Featured Poster
And FWIW, I DO prefer C++ to C for my work, but I use C inside my C++ code when it makes sense.
MasterHacker110 -4 Newbie Poster
Well thanks for all that info.
Be a part of the DaniWeb community
We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.