So you're an embedded developer. You know that C is the right languagefor the job, although sometimes those maintenance cycles can be, wellrepetitive. You sometimes get that nagging feeling that you are codinglike an automaton, repeatedly creating basic iterations over structuresthat are remarkably similar to ones from last week or last month.
You've heard the sales pitch for C++ as a powerful language but youalso hear of horror stories about its large footprint, which makes it ano-go for embedded applications. Besides, it's so complicated: it mustbe really difficult to work with.
Does this have a familiar ring to it? The embedded developmentmarket covers a large range of application areas, including automotive,medical, defense and telecommunications. While C undoubtedly enjoys agood reputation as a strong and powerful language for embeddeddevelopment, C++ does not have quite such a broad appeal. With today'scapable and sophisticated C++ environments this is often an incorrectbelief.
Why is C++ a viable alternative to C? As a language, it did ofcourse grow from roots in C. Simply re-compiling a C project using aC++ compiler will yield more rigorous code type checking (there aresome declaration incompatibilities that you may have to overcomefirst).
Once you embrace its core features, C++ offers a greater abstractionof data, which is an important objective for larger, more complexsoftware systems. Object Orientation (OO) takes this abstraction a stepfurther, where you can replace global “worker” functions with classfunctionality.
Templates are perhaps the biggest fear-factor against C++ usage, andmost often quoted as the reason or presumption for large code-bloatexperiences. But, in fact, well-designed template code offers elegantmeans of handling a variety of data-types consistently. C++'s StandardLibrary is an advertisement for such generic programming styles.
Exception handling is another area that suffers from FUD (Fear,Uncertainty, and Doubt). Exceptions in many C software systems willtypically be a manual, programmatic task. This exception handlingmachinery can be replaced, albeit at a cost, with a more elegant C++exception-based solution.
As always, the elegant solution only gets its due recognition whenrequirement changes are thrust on a development team. The speed withwhich well-crafted object-based designs can adapt in such circumstancesis due, in part, to better abstraction and hiding of implementation.
Comparing C and C++ compiler implementations can be odious, to put itmildly. It is difficult to obtain scientific results from such applesversus oranges comparisons.
Anecdotal newsgroup discussions report space and performanceefficiency losses of between 8 percent and 30 percent for C++implementations. Of course, for the exact same code using the same Clibrary, we expect identical binary code.
Over the course of its genesis, there have undoubtedly been verypoor experiences of C++ performance in the areas of speed andparticularly of executable and run time space needs. Early in theadoption stages of C++, these concerns led to an interesting industryformation.
A decade ago, a collaboration of mainly Japanese embedded systemsproviders proposed a strictly limited language environment for the C++language to cater for embedded requirements. The concept was to bansections of the C++ language, including Exception Handling, Run-TimeType Information, Namespaces, Templates, Multiple Inheritance, andVirtual Functions.
There were two main planks to its justification. First was removalof language constructs that caused the dreaded 'code bloat'. Second wasan objective to simplify the C++ language from a learning perspective,perhaps with the lower OO experience levels of embedded engineers inmind. fundamentally flawed
But, with all due respect to its authors, the approach to EmbeddedC++ seems fundamentally flawed. The hard-working members of the C++language committee must have paled on hearing of the proposal todiscard whole chunks of their well-engineered standard C++ language.
Their collective response to this proposal was to begin anexamination of the actual performance issues in the language and itsmost popular implementations (compiler and machine environments).
The result is a comprehensive report released by the C++ StandardsCommittee on the performance expectations of each of the major featuresof the complete C++ language. Within the report there is a sectiondealing with the performance and space efficiency (both static andrun-time) for each feature. This article summarises findings for themost interesting features.
a) Namespaces: There is no space or time overhead associated with the use ofnamespaces. They only affect name lookup rules at compilation time. Theprinciple advantage of namespaces is in providing a mechanism forpartitioning names in large projects in order to avoid name clashes. Asan aside, the using directive avoids the additional typing effort inusing explicit namespace qualification by moving all unqualifiedidentifiers into the current namespace.
b) TypeConversions: C++ brings with it the C-style cast notation, butsupports more secure and explicit conversions, through four newoperators, which apply to different conversion situations. For three ofthese new-style cast operators (const_cast, static_cast, andreinterpret_cast) there is no performance implication.
In fact, it is typical for a compiler to transform cast notationinto one of these new type conversion operators when generating objectcode. Only dynamic_cast may involve additional overhead, if therequired conversion requires Run-Time Type Information (RTTI)mechanisms such as cross casting in a class hierarchy, which we'll seelater.
c) ClassRepresentation: The basic class feature, somewhat surprisingly,does not suffer any space or speed overhead compared with C's structand global function equivalents. This is because non-virtual functionsand static data members are stored with the class definition, ratherthan in objects of the class.
Member function calls have one additional implicit pointer argument,required to point to the class object (* this). On the other hand,freestanding function calls need operational data passed to themexplicitly, typically through an equivalent explicit pointer argument.
d) VirtualFunctions: Virtual functions incur a well-defined cost, which isbased on the underlying operation: indexing into an array of pointersto function. This is an implementation technique that is common in Ccode, but more elegantly expressed in the virtual function paradigm.
There are situations where use of virtual functions can result in'code bloat'. If a class template that contains virtual functions isspecialised on a variety of types, then each of these specializationsholds duplicated member functions and their associated supportstructures, including the virtual table.
This will generally result in excessive object code, since currentlinker/optimizer technology is not sufficiently sophisticated toidentify this circumstance. To avoid this problem, you can move commoncode (not dependent on the instantiated type) out of the class templateand into non-template helper functions, or alternatively movefunctionality from the template class into a non-template base class.
e) FunctionInlining: In terms of achieving performance efficiency, functioncalls are to be avoided. C++'s inline feature provides the compilerwith a hint for functions that could be inlined into their callinglocation. The compiler is not obliged to take this hint. Advancedoptimization techniques can identify and remove small and less complexfunction calls automatically without the code explicitly providing theinline hint. However, experience to date suggests that implicitinlining yields no consistent benefit, and the explicit inline keywordshould be used.
f) Virtual BaseClasses (VBC): In non-virtual inheritance, member function calls perform a simpleconstant adjustment. The essential difference with VBCs is that memberfunctions need to perform lookup at run-time to discover which classfunction in the inheritance tree should be activated. This involves anadditional overhead of approximately 15 percent over the non-virtualcase.
However, simulating the feature of virtual calls through anotherfeature carries costs as well. The alternative technique ofimplementing an interface class that is passed around the constituentclasses does itself require an indirection in its access, with theattendant costs and overhead.
g) Run-Time Type Information:
RTTIis used to interrogate the type of an object and is also part of theinfrastructure of the dynamic_cast feature. As an indication of itsexpense in performance terms, consider the application of dynamic_cast:find the vtable of the object, find the most derived object of whichthis is part, use that object's type_info to perform the requiredadjustments to the this pointer.
h) ExceptionHandling: C++'s exception handling feature requires typeinformation at run time, and partly overlaps with and extends the RTTIstructure. However, any manual coding alternative must consider codingstyle, complete coverage of error handling routines, thread-safety,run-time system overheads, and overheads from handling errors.Considering the associated cost, run-time overhead, and codemaintenance overhead makes exception-handling a reasonable alternative.
i) Templates: Templates are one of the most denounced features of C++ in terms ofspace costs. Code bloat arises when class and function templatesgenerate a new set of code and data for each instantiation withdifferent parameters.
Tests conducted on multiple instantiations of the samespecialization versus many different specializations indicate quitevaried performance results, suggesting that compilers have some way togo to meet optimization goals in this area. implement optimizations
By enabling certain features, in particular partial specializations,compilers can allow library vendors to implement optimizations toovercome this problem. The developer can deploy a technique to avoidmultiple specializations by routing all instantiation requests througha common class template, for instance by using a common class-templatefor a single specialization based on void* .
While this analysis might make you more comfortable with C++ as adevelopment language, it does not by itself help you to create good,reliable, and above all high-performance, C++ code.
There is much advice on optimization matters in the C++ language,including preference for initialization over assignment, avoidance ofunwanted conversions, avoidance of temporary object creation (inparameter passing, function return, and expressions), and judicious useof inline. Beyond such language-based optimizations are recommendationon correct usage of C++'s excellent library.
The use of Coding Standard Enforcement (CSE) tools provides a usefulsafety net which allows you to freely create C++ code but provides atimely warning when suspect code is written. PRQA's QAC++ advancedanalyzer tool, for example, flags up code which is non-portable,difficult to maintain, overly complex, non-ISO compliant, or written inany way that is likely to cause problems.
The message browser displays warning messages on source code for thecomplete project. These are categorized and grouped across all sourcefiles. The browser links to helpful additional information and adviceon the coding violations identified by the analyzer.
Source code can be analyzed on a file-by-file or project basis toidentify potentially dangerous language usage to prevent bugs creepinginto your system. The analyzer is able to locate obscure bugs in code,thereby reducing debugging timescales dramatically.
However, let's get back to our C developer mindset. Often, thepresumption is made that developing in C++ takes a vastly differentapproach than traditional C development. If we examine two typical Cimplementation techniques, we may come to a different conclusion.
Polymorphism in C
With a record that needs to store data of different types, thedefinition might look like this:
Then any code that operates on this record must check what kind ofdata it represents:
But this looks very much like the polymorphism concept. The memberkind corresponds to a class vtable pointer, and code sections in theswitch-statement correspond to a set of virtual functions.
The polymorphism alternative is more elegant andmaintenance-friendly, considering the proliferation of such switchstatements when using this record. There is even the benefit of no datapadding to the largest type, as has to happen here.
Code bloat in C
In a typical C project, you will often see iteration code such as:
The C++ library targets this type of repetitive code by putting richiteration functionality into its set of container classes. It offers amore maintainable, compact, and most probably faster implementationthan can usually be achieved with a hand-coded alternative.
So maybe this evidence of performance capabilities doesn't cause youto immediately transition your embedded development to the C++ language- after all it is not a choice to make lightly. However, as projectsizes grow larger they increasingly need to be well-engineered andwell-managed.
In this context, C++ is an entirely valid alternative to C. Based onPRQA's own experience, C++ offers a high return in terms ofmaintainability and responsiveness to changed and expandedrequirements.
In the more exacting requirements of embedded development, theseresearch results show that, with a little care and attention, and withthe additional safety-net of CSE tools, C++ has much to offer.
Fergus Bolger is chief technical officer at PRQA.
Acknowledgements: Thispresentation references and is partly based on ISO/IEC TR 18015:2006(E)”TechnicalReport on C++ Performance“, from the C++ standards committee (WG21).