C++ ++ in embedded systems: Myth and reality

By Dominic Herity superior to C for embedded sys- Listing 1 Listing 2 Senior Software Engineer tems programming. Function name Silicon and Software Systems It aims to provide detailed Function name overloading. overloading in C. understanding of what C++ I was surprised a while ago by code does at the machine level, // C++ function name /* C substitute for */ overload example /* function name over- what I heard in a coffee break so that readers can evaluate for void foo(int i) load */ conversation during a C++ themselves the speed and size { void foo_int(int i) course. The attendees were spec- of C++ code as naturally as they //... { ulating on the relevance of C++ do for C code. } /*... */ void foo(char* s) } to embedded systems, and the To examine the nuts and { void foo_charstar(char* misconceptions that surfaced bolts of C++ code generation, //... s) drove me ultimately to write this I will discuss the major features } { article. of the language and how they void main() /*... */ { } Some perceive that C++ has are implemented in practice. foo(1); void main() overheads and costs that render Implementations will be illus- foo(“Hello world”); { it somehow unsuited to embed- trated by showing pieces of C++ } foo_int(1); ded systems programming, that code followed by the equivalent foo_charstar(“Hello world”); it lacks the control and brevity of (or near equivalent) C code. a C compiler and the machine } C, or that, while it may be suited I will then discuss some pitfalls code generated will be exactly to some niche applications, it specific to embedded systems what you would expect from a C Listing 3 will never displace C as the lan- and how to avoid them. I won’t compiler. A trivial class with guage of choice for embedded discuss the uses and subtleties This simple point invalidates member function. systems. of C++ or object-oriented (OO) any claims that a system can be / A trivial class These perceptions are wrong. design, as these topics have implemented in C, but not in class foo Where compilers and other tools been well covered elsewhere. C++. In practice, existing C code { are adequate, C++ is always can typically be re-compiled private: preferable to C as an implemen- The Myths as C++ with about the same int x; public: tation language for embedded Some of the perceptions that dis- amount of difficulty that adopt- void bar(); systems. While doing everything courage the use of C++ in embed- ing a new C compiler entails. }; that C does, it offers greater op- ded systems are: This also means that migrat- void foo::bar() portunities for expression, en- • C++ is slow ing to C++ can be done gradu- { x = 0; capsulation, re-use, and it even • C++ produces large binaries ally, starting with C and working } allows size and speed improve- • Abstraction leads to in new language features at your ments that are impractical in C. inefficiency own pace. Although this isn’t the Listing 4 Why, then, do these percep- • Objects are large best way to reap the benefits of tions persist? The main reason • Virtual functions are slow OO design, it minimises short C substitute for trivial class with member function. is that when people form their • C++ isn't ROMable term risk and it provides a basis opinions, they know a lot more • Class libraries make large for iterative changes to a work- /* C substitute for trivial class foo */ about C than about C++. They binaries ing system. struct foo have read some books, written { some code, and are competent Some of these perceptions are Front end features: a free int x; at using the features of C++, exaggerated concerns. Others are lunch }; void bar_foo(struct foo* but they lack the knowledge of just wrong. When the details of Many features of C++ are strictly this) what is happening under the C++ code generation are exam- front end issues and have no ef- { hood, the familiarity that allows ined in detail, it will be clear what fect on code generation. The ben- this->x = 0; one to visualise the disassembly the reality behind these myths is. efits conferred by these features } while typing source or even are therefore free of cost at run while formulating a design. It is Anything C can do, C++ can time. These features include use between const and non-const to these people that this article do better of the keywords const, private, data. These specifiers allow the is addressed. The most obvious property of protected, and public, which al- programmer to prevent misuse This article aims to replace ex- C++ is so obvious that it’s often low the programmer to prevent of data or interfaces through aggerated claims and misapplied overlooked: C++ is a superset of misuse of interfaces. No physical compiler-enforced restrictions. generalisations with informed C. If you write a code fragment difference exists between private, Default arguments to func- comment. It supports the view (or an entire source file) in the C protected, and public members. tions are another neat, free front that C++, used appropriately, is subset, the compiler will act like Neither is there a difference end feature. The compiler inserts

 eetindia.com | February 1998 | EE Times-India default arguments to a function generates a reference to a label Function names are altered to Behind the protection and call, where none are specified by such as ?foo@@YAHH@Z, while add argument types, so that the scoping, a class is almost the the source. a call to a function void foo(int) two functions have different same as a C struct. Indeed, in A less obvious front end fea- generates a label like ?foo@@ names. C++, a struct is defined to be a ture is function name overload- YAXH@Z and a call to a function In C++, name mangling is au- class whose members are public ing. Function name overloading void foo(bar*) generates a label tomatic, but in a C substitute, it by default. A member function is is made possible by a remarkably like ?foo@@YAXPAU bar@@@ would be the responsibility of the a function that takes a pointer to simple compile-time mecha- Z. Name mangling ensures that programmer. an object of its class as an implicit nism. The mechanism is com- functions aren’t called with the parameter. So a C++ class with a monly called name mangling, wrong argument types and it Classes, Member Functions, member function is equivalent, but has been called name deco- also allows the same name to and Objects in terms of code generation, to a ration by those who have noth- be used for different functions Classes and member functions are C struct and a function that takes ing better to do than sanitise provided their argument types the most important new concept that struct as an argument. perfectly good terms. Anyone are different. in C++. Unfortunately, they are Listing 3 shows a trivial class who has seen a linker protesting Listing 1 shows a C++ code usually introduced without ex- foo with one member variable x the absence of ?foo@@YAHH@ fragment with function name planation of how they are imple- and one member function bar(). Z knows which term is more overloading. There are two func- mented, which tends to disorient Listing 4 shows the C substi- appropriate. Name mangling tions called foo, one taking an int C programmers from the start. In tute for Listing 3. Struct foo has modifies the label generated for argument, the other taking a char* the subsequent struggle to come the same member variable as a function using the types of the argument. to terms with OO design, hope of class foo and the member func- function arguments, or function Listing 2 shows how this understanding code generation tion foo::bar() is replaced with signature. So a call to int foo(int) would be implemented in C. quickly recedes. a function bar_foo(struct foo*).

Listing 5 Listing 6 A simple string class featuring constructors, C substitute for simple string class. destructors, , new, and delete. /* C substitute // A simplified string class for simplified string class */ #include #include #include #include using namespace std; #include class String { struct String { private: char* data; char* data; unsigned len; unsigned len; }; public: #define length_String(s) ((s)->len) String(); void StringConstructor(String* this) { ~String(); this->len = 0; unsigned length() const; this->data = 0; String& operator=(const char* s); } }; void StringDestructor(String* this) { inline unsigned String::length() const if (this->data != 0) { free(this->data); return len; } }; String operatorEquals_String_const_char_star( String::String() { String* this, const char* s) { len = 0; this->len = strlen(s); data = 0; this->data = (char*) malloc( } (this->len+1) * sizeof(char)); String::~String() { /* if (data != 0) If char had a constructor, */ delete [] data; /* it would be have to be */ } /* called here. */ String& String::operator=(const char* s) { if (this->data == 0) len = strlen(s); this->len = 0; data = new char [ len+1 ]; else if (data == 0) strcpy(this->data, s); len = 0; return *this; else } strcpy(data, s); FILE* return *this; operatorShiftLeft_ostream_unsigned(FILE*, } unsigned); void main() { void main() { String s; String s; s = “Hello world”; StringConstructor(&s); cout operatorEquals_String_const_char_star( < &s, “Hello world”); < operatorShiftLeft_ostream_unsigned(stdout, s.length(); length_String(&s)); } StringDestructor(&s); }

 eetindia.com | February 1998 | EE Times-India Note the name of the argument Listing 7 Listing 8 of bar_foo(struct foo*) has been chosen as this, which is a key- Inheritance. C substitute for inheritance. word in C++, but not in C. The // Simple example of in- /* C Substitute for inheritance */ choice is made deliberately to heritance struct A { class A { int value; highlight the point that in C++, public: }; an object pointer named this is A(); void AConstructor(struct A* this) { implicitly passed to a member int f(); this->value = 1; function. private: } int value; int f_A(struct A* this) { An object in C++ is simply }; return this->value; a variable whose type is a C++ A::A() { } class. It corresponds to a vari- value = 1; struct B { able in C whose type is a struct. } struct A a; int A::f() { int secondValue; A class is little more than the return value; }; group of member functions that } void BConstructor(struct B* this) { operate on objects belonging to class B : public A { AConstructor(&this->a); the class. At the machine code private: this->secondValue = 2; int secondValue; } level, data is mostly made up of public: int g_B(struct B* this) { objects and code is mostly made B(); return this->secondValue; up of classes. int g(); } Clearly, arranging code into }; void main() { B::B() { B b; classes and data into objects secondValue = 2; BConstructor(&b); is a powerful organising prin- } f_A ((struct A*)&b); ciple. Dealing with classes and int B::g() { g_B (&b); objects is inherently just as ef- return secondValue; } } ficient as dealing with functions void main() { Listing 9 and data. B b; b.f(); Virtual functions. b.g(); Constructors and Destructors // Classes with virtual functions } In C++, a constructor is a member class A { function guaranteed to be called private: int value; when an object is instantiated or initialisation bugs and resource public: created. This typically means the leakage. A(); compiler generates a constructor virtual int f(); call at the point where the object Inline Functions }; A::A() { is declared. Similarly, a destructor Inline functions are a safer and value is guaranteed to be called when more powerful substitute for C = 0; an object goes out of scope. So a macros in many situations. It is } constructor typically contains any rare to see a macro that has local int A::f() { return 0; initialisation that an object needs variables, and rightly so, because } and a destructor does any tidying macros rapidly become illegible. class B : public A { up needed when an object is no Inline functions, by contrast, have public: longer needed. the legibility and safety of ordi- B(); virtual int f(); The insertion of construc- nary functions. Clearly, indiscrimi- }; tor and destructor calls by the nate use of inline functions can B::B() { compiler outside the control of lead to bloated code, and novice } the programmer is something C++ programmers are invariably int B::f() { return 1; that makes the C programmer cautioned on this point. } uneasy at first. Indeed, program- However, appropriate use void main() { ming practices to avoid exces- of inline functions can improve B b; sive creation and destruction of both size and speed. To estimate A* aPtr = &b; a->f(); so-called temporary objects are the code size impact of an inline } a preoccupation of C++ pro- function, estimate how many grammers in general. However, bytes of code it takes to imple- actual comparisons studying Operator Overloading the guarantee that constructors ment it and compare that to generated code with optimi- A C++ compiler substitutes a and destructors provide-that the number of bytes needed to sation turned on, you may be function call when it encounters objects are always initialised and do the corresponding function surprised by how complex an an overloaded operator in the are always tidied up-is generally call. Also consider that compiler inline function can profitably be. source. Operators can be over- worth the sacrifice. In C, where optimisation can tilt the balance The break-even point is often far loaded with member or global no such guarantees are provid- dramatically in favour of the beyond what can be expressed functions. So foo+bar is evaluated ed, the consequence is frequent inline function. If you conduct in a legible C macro. to be operator+(foo, bar) or foo.

 eetindia.com | February 1998 | EE Times-India operator+ (bar), which in terms of far, all confer substantial benefit at the class A constructor is called Inappropriate inheritance, code generation amounts to the no run-time cost. first and the reverse happens with however, can make objects larger same thing. Operator overload- destructors. than necessary. This is most likely ing is a front end issue and can be Inheritance Listing 7 shows an example of to arise in class hierarchies, where viewed as a function call for the In discussing how C++ imple- inheritance. Class B inherits from a typical class has several layers purposes of code generation. ments inheritance, we will limit class A and adds the member of base class, each with its own our discussion to the simple case function B::g() and the member member variables. New and Delete of single, non-virtual inheritance. variable B::secondValue. In C++, new and delete do the Multiple inheritance and virtual Listing 8 shows how this Virtual Functions same job as malloc() and free() in inheritance are more complex and would be achieved in C. Struct Virtual member functions allow us C, except that they guarantee con- their use is rare by comparison. B contains a struct A as its first to derive class B from class A and structor and destructor calls. They Let’s consider the case in which member, to which it adds a vari- override a virtual member func- also tend to be better-suited to class B inherits from class A. (We able secondValue. The function tion of A with one in B and have frequent use with small quantities can also say that B is derived from BConstructor(struct B*) calls the new function called by code of memory than older implemen- A or that A is a base class of B.) AConstructor to ensure initialisa- that knows only about class A. tations of malloc() and free(). We now know what the inter- tion of its “base class.” Where the Virtual member functions provide nal structure of an A is. What is the function main() calls b.f() in Listing polymorphism, which is a key fea- Simplified String Class internal structure of a B? An object 7, f_A(struct A*) is called in Listing ture of OO design. To illustrate the implementation of of class B is made up of an object 8 with a cast. Virtual functions have been a class with the features we’ve dis- of class A, with the member data It is startling to discover that controversial. It would seem that cussed, let’s consider an example of B tacked on at the end. In fact, the rather abstract concept of they exact a price for the benefit of a simplified C++ class and its C the result is the same as if the B inheritance corresponds to such of polymorphism. Let’s see, then, alternative. contains an A as its first member. a straightforward mechanism. how they work and what the Listing 5 shows a string class Therefore, any member functions The result is that appropriately de- price is. featuring a constructor and de- of class A called on an object of signed inheritance relationships Virtual functions are imple- structor, operator overloading, class B will work properly. When have no run-time cost in terms of mented using an array of func- new and delete, and an inline an object of class B is constructed, size or speed. tion pointers called a vtable for function. each class that has virtual func- Listing 6 is a C substitute for Listing 10 tions. Each object of such a class the string class shown in Listing 5. C substitute for virtual functions. contains a pointer to the class’s The inline function String::length() /* C substitute for virtual functions */ vtable. This pointer is put there is replaced by the macro length_ struct A { by the compiler and is used by String(). Operator overloads void **vTable; the generated code, but it isn’t int value; String::operator=(const char*) }; available to the programmer and operator < < (ostream&, int) int f_A(struct A* this); and it can’t be referred to in are replaced with function calls void* vTable_A[] = { the source code. Inspection of operatorEquals_String_const_ (void*) &f_A objects with a low-level debug- }; char_star(String*, const char*) and void AConstructor(struct A* this) { ger will reveal the vtable pointer, operator ShiftLeft_ostream_un- this->vTable = vTable_A; if the reader is interested. Of signed (ostream*, int) respectively. this->value = 1; The constructor and destructor } Listing 11 int f_A(struct A* this) { must then be called by the user of return 0; A C++ . the class, rather than being added } // Sample template class automatically by the compiler. struct B { template See how much harder to read A a; class A { }; private: the function main() is in Listing int f_B(struct B* this); T value; 6 than in Listing 5 and consider void* vTable_B[] = { public: how much more danger there is (void*) &f_B A(T); of a bug occurring. Consider how }; T f(); void }; much worse the problem would BConstructor(struct B* this) { template be for a more realistic string class. AConstructor((struct A*) this); A This is why C++ and the object this->a.vTable = vTable_B; ::A(T initial) { paradigm are so much superior to } value = initial; int f_B(struct B* this) { } C and the procedural paradigm for return 1; template partitioning complex problems. } T A Next, consider that the code void main() { ::f() { and data generated by Listing 5 struct B b; return value; struct A* aPtr; } is just as small and just as fast as BConstructor(&b); void main() { that generated by Listing 6. It is typedef void (*f_A_Type)(struct A*); A also safer, more coherent, more aPtr = (struct A*) &b; a(1); readable, and more maintainable. ((f_A_Type)aPtr->vTable[0]) (aPtr); a.f(); } } Of the C++ features discussed so

 eetindia.com | February 1998 | EE Times-India course, the vtable pointer is kept Listing 12 Listing 13 from the programmer for good reasons, and using it directly is A C “template.” A C++ exception example. an excellent way to prevent your /* C approximation of / C++ Exception example code being ported and to make template class */ #include #define A(T) using namespace std; its maintenance exciting! \ int factorial(int n) throw(const char*) When a virtual member func- struct A_##T { tion is called on an object, the \ if (n generated code can use the ob- { < \ 0) ject’s vtable pointer to access the T value; throw vtable for that class and extract \ “Negative Argument to factorial”; the correct function pointer. That }; if (n>0) pointer is then called. \ return n*factorial(n-1); \ return 1; Listing 9 shows an example void AConstructor_##T(A_ } using virtual member functions. ##T* this, void main() Class A has a virtual member func- \ { tion f(), which is overridden in class T initial) try \ { B. Class A has a constructor and a { int n = factorial(10); member variable, which are actu- \ cout ally redundant, but are included (this)->value = initial; < to show what happens to vtables \ < } “factorial(10)=” during object construction. \ < Listing 10 shows what a C \ < substitute would look like. The T A_f_##T(A_##T* this) n; result is both extremely ugly \ } { catch (const char* s) and hazardous. In the last line of \ { main(), we see the virtual function return (this)->value; cout call, which, after all the casting, \ < uses the object’s vtable pointer to } < A(int) /* Macro expands look up the vtable of its class for to ýclass’ A_int */ “factorial threw exception : “ the function pointer. Let’s quantify the costs of vir- void main() { < tual functions, in order of prior- A_int a; < AConstructor_int(&a, s ity. The first cost is that it makes 1); < objects bigger. Every object of a A_f_int(&a); < class with virtual member func- } “\n”; tions contains a vtable pointer. } } So each object is one pointer (to get the object’s vtable pointer) bigger than it would be oth- and a second memory read (to that it will be linked, even if it isn’t source file where it’s used. Newer erwise. If a class inherits from get the function pointer from the used in a particular system. compilers and linkers, however, find a class that already has virtual vtable). This cost has been the So the bottom line on virtual duplicates and produce at most functions, the objects already subject of heated debate and it functions is that they have little one expansion of a given template contain vtable pointers, so there is hard to believe that the cost is impact on speed, but be aware with a given parameter class. is no additional cost. But adding typically less than to that of adding of their effects on code size and Used appropriately, templates a virtual function can have a dis- an extra parameter to a function. data size. Virtual functions are not can save a lot of effort at little or proportionate effect on a small We hear no arguments about the mandatory in C++, unlike in other no cost. After all, it’s a lot easier object. An object can be as small performance impact of additional OO languages. and generally more efficient to as one byte and if a virtual func- function arguments because it use complex from the Standard tion is added and the compiler is generally unimportant, just as Templates C++ Library, rather than write enforces four-byte alignment, the cost of a virtual function call is C++ templates are powerful, as your own class. the size of the object becomes generally unimportant. shown by their use in the Standard Listing 11 shows a simple eight bytes. But for objects that A less discussed cost of virtual C++ Library. A class template is template class A. An object of contain a few member variables, functions is their impact on code rather like a macro which produces class A has a member variable of the cost in size of a vtable pointer size. Because each class with virtual an entire class as its expansion. class T, a constructor to initialise is marginal. functions has a vtable containing Because a class can be produced and a member function A::f() to The second cost of using vir- pointers to all its virtual functions, from a single statement of source retrieve it. tual functions is the one that gen- the pointers in this vtable must be code, misuse of templates can The macro A(T) in Listing erates the most controversy. That resolved by the linker. This means have a devastating effect on code 12 approximates a template is the cost of the vtable lookup for that all virtual functions of all size. Older compilers will expand class in C. It expands to a struct a function call, rather than a direct classes used in a system are linked. a templated class every time it is declaration and function defini- one. The cost is a memory read be- Therefore, if a virtual function is encountered, producing a differ- tions for functions correspond- fore every call to a virtual function added to a class, the chances are ent expansion of the class in each ing to the constructor and the

 eetindia.com | February 1998 | EE Times-India suggests an association with purer more member functions than a C Listing 14 OO languages like . This programmer would expect to use. A C “exception” example. association causes anxiety among This is because properly designed /* C approximation of */ the performance-conscious that classes are complete and contain #include efficiency has been compromised member functions to do anything #include jmp_buf ConstCharStarException; for purity. This is not so. Run-time with objects belonging to the const char* ConstCharStarExceptionValue; type information exploits the class that might legitimately be int factorial(int n) vtable pointer in an object that needed. For a well conceptualised { has one and provides sensible de- class, this number will be reason- if (n < faults for an object that does not. If ably small, but nevertheless larger 0) you don’t use run-time type infor- than what the C programmer is { mation, the only run-time cost is accustomed to. ConstCharStarExceptionValue = that classes are a little larger. If you When calculating code size, “Negative Argument to factorial”; longjmp(ConstCharStarException, 0); use a compiler option to disable bear in mind that modern linkers } run-time type information, that designed for C++ extract from if (n>0) cost is avoided. object files only those functions return n*factorial(n-1); that are actually called, not the return 1; } Memory Considerations entire object files. In essence, void main() Having discussed the implemen- they treat object files like librar- { tation of the major C++ language ies. This means that non-virtual if (setjmp(ConstCharStarException)==0) features, we can now evaluate class member functions that { int n = factorial(10); C++ in terms of the machine code are unused have no code size printf(“factorial(10)=%d”, it generates. Embedded systems penalty. So a class that seems to n); programmers are particularly have a lot of baggage in terms } concerned about code and data of unused member functions else { size, so we need to discuss C++ in may be quite economical in printf( these terms. practice. “factorial threw exception : %s\n”, In the case of virtual functions, ConstCharStarExceptionValue); How big is a class? it is reasonable to assume that all } } In C++, most code is in class mem- virtual functions of all classes used ber functions and most data is in in a system will be linked into the member function. We can see normal case is somewhat at the objects belonging to these class- binary. But class completeness in that although it’s possible to expense of performance in the es. C++ classes tend to have many itself does not lead to code bloat. approximate templates in C, it abnormal case. The second factor Listing 15 is impractical for any significant is the run time of destructor calls functionality. between an exception being A C ROMable dictionary. thrown and being caught. /* A C ROMable dictionary */ Exceptions Because of the performance #include Exceptions are to setjmp() and penalty in the no exceptions case, typedef struct { longjmp() what structured pro- many compilers have a “no excep- const char* englishWord; gramming is to goto. They im- tions” option, which eliminates ex- const char* foreignWord; pose strong typing, guarantee ception support and its associated } DictEntry; that destructors are called, and performance cost. const static DictEntry germanDict[] = { prevent jumping to a unused Listing 13 shows an example {“yes”, “ja”}, stack frame. of an exception and Listing 14 {“no”, “nein”}, Exceptions are intended to shows a C substitute that has sev- {NULL, NULL} handle conditions that don’t eral shortcomings. It uses global }; const static DictEntry frenchDict[] = normally occur, so implementa- variables. It allows longjmp(Cons { tions are tailored to optimise per- tCharStarException) to be called {“yes”, “oui”}, formance for the case when no either before it is initialised by se {“no”, “non”}, exceptions are thrown. Support tjmp(ConstCharStarException) or {NULL, NULL} }; for exceptions results in a small after main() has returned. In addi- const char* FromEnglish( performance penalty for each tion, substitutes for destructor calls const DictEntry* dict, function call. (This is to record must be done by the programmer const char* english); information to ensure destructor before a longjmp(). There is no const char* ToEnglish( const DictEntry* dict, calls are made when an excep- mechanism to ensure that these const char* foreign); tion is thrown.) The time taken calls are made. /*... */ to throw an exception is unpre- void main() dictable and may be long due to Run-time Type Information { puts(FromEnglish(frenchDict, “yes”)); two factors. The first is that the Run-time type information is a } emphasis on performance in the recent addition to C++. Its name

 eetindia.com | February 1998 | EE Times-India How big is an object? in ROM. For a system written in C, Listing 16 The size of an object can be cal- this means that all the non-vary- culated by examining its class ing data known at compile time A C++ ROMable dictionary NOT! (and all its base classes). Ignore can be specified by static initialis- // NOT a ROMable dictionary in C++ member functions and treat the ers, compiled to be stored in ROM #include using namespace std; data the same as for a struct. and left there. class Dict Then add the size of a pointer if In C++, we can do the same, { there are any virtual functions in but we tend not to. In well de- public: the class or base classes. You can signed C++ code, most data is Dict(); const char* fromEnglish( confirm your result by using the encapsulated in objects. Objects const char* english) const; sizeof operator. It will become ap- belong to classes and most class- const char* toEnglish( parent that the combined size of es have constructors. The natural const char* foreign) const; objects in a system need be no OO equivalent to const initialised private: enum { DictSize = 3 }; greater than the size of data in a data is a const object. A const struct C-based procedural model. This static object that has a construc- { is because the same amount of tor must be stored in RAM for its const char* english; state is needed to model a system constructor to initialise it. So while const char* foreign; } table[DictSize]; regardless of whether it is organ- in C a const static object occupies }; ised into objects. cheap and plentiful ROM, its natu- // *** Following won’t compile *** ral heir in C++ occupies expensive const static Dict germanDict = C++ and the Heap and scarce RAM. Initialisation is { { Heap usage is much more com- performed by start-up code that {“yes”, “ja”}, mon in C++ than in C. This is calls static constructors with pa- {“no”, “nein”}, because of encapsulation. In C, rameters specified in declarations. {NULL, NULL} where a function requires an This start-up code occupies more } }; unknown amount of memory, ROM than the static initialiser // *** Following won’t compile *** it is common to externalise the would have. const static Dict frenchDict = memory as an input parameter So if a system includes a lot { and leave the user with the prob- of data that can be kept in ROM, { {“yes”, “oui”}, lem. This is at least safer than mal- special attention to class design {“no”, “non”}, loc ing an area and relying on the is needed to ensure that the rel- {NULL, user to free it. But C++, with its evant objects are ROMable. For an NULL} encapsulation and destructors, object to be ROMable, it must be } }; gives class designers the option capable of initialisation by a static //... (and responsibility) of managing initialiser like a C struct. Although void main() the class heap usage. the easy way to do this is to make { This difference in philosophy is it a simple C struct (without cout < evident in the difference between member functions), it is possible < C strings and a C++ string class. to make such a class a bit more germanDict.fromEnglish(“yes”); In C, you get a char array. You object-oriented. } have to decide in advance how The criteria for a static initialiser long your string can be and you to be allowed for a class are: of the OO ideal of a class that is to none of the restrictions that the have to continuously make sure • The class must have no base easy to use correctly and difficult ROMable class is, so we can put in it doesn’t get any bigger. A C++ classes to use incorrectly. The unwary it a proper restricted interface to string class, however, uses new • It must have no constructor class user can, for example, our const static data. and delete to allow a string to be • It must have no virtual declare and use a non- const, To illustrate this discussion, any size and to grow if necessary. functions uninitialised instance of the class let's consider a simplified example It also makes sure that the heap • It must have no private or pro- that is beyond the control of the of a hand-held electronic multi- is restored when the string is no tected members class designer. language dictionary. To keep it longer needed. • Any classes it contains must To let us sleep securely in our simple, the translator handles The consequence of all this obey the same rules OO beds, something more is translation to German or French is that you can scrape by in an needed. That "something" is class and it has a vocabulary of two embedded system written in C In addition, we should also nesting. In C++, we can declare words, "yes" and "no." Obviously, without implementing malloc require that all member functions classes within classes. We can take these dictionaries must be held and free, but you won’t get far in of a ROMable class be const. A C our dubious class that is open to on ROM. A C solution would be C++ without implementing new struct meets these criteria, but misuse, and put it in the private something like Listing 15. and delete. so does a class that has member segment of another class. We can functions. Although this solves also make the const static instanc- A Dict is an array of DictEntry. ROMable Objects the ROMability problem and en- es of the dubious class private A DictEntry is a pair of const char* Linkers for embedded systems hances the C struct with mem- static members of the encapsulat- pointers, the first to the English allow const static data to be kept ber functions, it falls far short ing class. This outer class is subject word, the second to the foreign

 eetindia.com | February 1998 | EE Times-India Now, let’s do it right. In Listing 40; and “Embedded C++,” C/C++ Listing 17 18, the class Dict in Listing 17 be- User’s Journal, February 1997, p. A C++ ROMable corruptable dictionary. comes DictTable, which is nested 35). // A ROMable dictionary in C++, privately within the new class Dict. // but with poor encapsulation Class Dict also contains, as a static Class Libraries and Embedded #include using namespace std; member, an array of DictTables, Systems class Dict which we can initialise statically. A major benefit of using C++ is the { The function main() shows use of availability of class libraries and the public: this class Dict, which has a clean productivity gains they promise. const char* fromEnglish( const char* english) const; interface. So to make the best use OO design makes class libraries const char* toEnglish( of OO design for data on ROM, much easier to use (and harder to const char* foreign) const; special class design is needed, misuse) then their procedural pre- // PLEASE don’t access anything in the class which is quite unlike the casual decessors. But a suspicion exists // below this comment. // PLEASE don’t create your own instances approach typical of C. that class libraries are large and of // little use, and that while this may of this class. EC++ be tolerable on a desktop PC, class enum { DictSize = 3 }; As we’ve seen, some C++ features libraries are generally unsuited to struct { have costs that may be undesir- embedded systems. const char* english; able in embedded systems. Could This is a misplaced generalisa- const char* foreign; a subset of C++ be more cost-ef- tion. If a judgement is made on } table[DictSize]; fective in embedded systems? the basis of so-called applica- }; const static Dict germanDict = Which subset? Programmers who tion framework libraries, such { use ad hoc subsets are frustrated as Microsoft Foundation Classes { in importing libraries, an ironic (MFC), it is easy to see how this {“yes”, “ja”}, consequence of adopting a lan- opinion is formed. Application {“no”, “nein”}, {NULL, NULL} guage which offers such promise frameworks are designed to do } for libraries. Wouldn’t a widely as much as possible of an applica- }; recognised subset be better? tion, allowing the programmer to const static Dict frenchDict = Class library vendors could supply concentrate on the specifics of his { { products written in the subset. or her project. This is usually done {“yes”, “oui”}, Compiler vendors could enforce by inheriting from classes in the {“no”, “non”}, it and take advantage in code library to produce specialised be- {NULL, NULL} generation of the known absence haviour. Most of the functionality } }; of troublesome features. This was of the classes in the framework are //... the motivation behind Embedded not the concern of the program- void main() C++ (EC++), a de facto standard mer using them. { subset of C++. Embedded C++ While this type of class library cout < is the same as C++ except that is well suited to its intended use, < it prohibits multiple inheritance, small code size isn’t its best fea- germanDict.fromEnglish(“yes”); templates, exceptions, namespac- ture, and its narrow focus makes } es, and run-time type identifica- it essentially useless outside its tion. Because it’s a strict subset target application area. An em- word. The end of a Dict is marked a clean and simple interface. of C++ with no extensions, it can bedded system needs a more by a DictEntry containing a pair of Unfortunately, Listing 16 won’t be compiled with an existing C++ general purpose class library that NULL strings. compile. The static initialisers compiler. can help with a variety of pro- To complete the design, we for frenchDict and germanDict EC++ shouldn’t be an auto- gramming problems. (Indeed, the add a pair of functions which try to access private members of matic choice for embedded sys- same could be said of any system perform translation from and to the objects. tems programming. While most of whose implementation isn’t domi- English using a dictionary. This is a If we make these members its restrictions are easy to accept, nated by the needs addressed by simple design. The two dictionar- public and eliminate the construc- forswearing templates and the a framework.) The characteristics ies and the strings to which they tor as in Listing 17, the class will Standard C++ Library shouldn’t of a class library suitable for em- point reside in ROM. meet the criteria for static initialis- be done lightly. There is no reason bedded systems are as follows. Let’s now consider what ers and the code will compile, why the full language can’t be It should include classes such happens if we produce a naýve but we’ve broken encapsulation. used in embedded systems. But as strings and container classes OO design in C++. Looking at Users can see the internal imple- if you do decide to use a subset, like lists, vectors, hash tables, Listing 15 through OO glasses, mentation of the class and bypass EC++ would be better supported maps and sets, and more. Such we identify a class Dict with the intended access functions. than an ad hoc subset. a class library can dramatically two member functions const Even worse, they can create their For more information about shrink and improve code that in char* Dict::fromEnglish(const own (con-const) instances of Dict EC++, see P.J Plauger’s articles on C manipulates char* s and arrays. char*), and const char* Dict:: whose internal state is outside our the subject (“Embedded C++: An Classes in the library should toEnglish(const char*). We have control. Overview,” ESP, December 1997, p. be independent. This implies

 eetindia.com | February 1998 | EE Times-India limited use of inheritance. This libraries exist, though none has embedded systems are starting of modern embedded systems characteristic is in contrast with been used as much as MFC. They to emerge, most notably a revi- development. Unfortunately, we a framework in which most include the draft Standard C++ sion of the Standard C++ Library are not yet at the stage where a classes are in an inheritance hier- Library, gnu’s libg++, and Rogue for embedded systems. C++ cross compiler from an es- archy. Independence allows the Wave Tools.h++. None are tai- Compilers and Tools for tablished vendor can be assumed programmer to treat the library lored to embedded systems, but Embedded Systems to produce good code quality and like an a la carte menu, while a all are better models of a class Most C cross compiler vendors be supported by a good robust framework is more like a dietary library for embedded systems for 32-bit targets now offer C++ tool set, so it is necessary to be regime. than a framework library. Class compilers as well. The older com- cautious about tool investments. Several general purpose class libraries specifically designed for pilers among them are based on AT&T’s cfront. Cfront was the origi- Reality Check Listing 18 nal C++ compiler and it produces Having minutely examining the A clean C++ ROMable dictionary. C code output, which is then fed costs of C++ features, it is only #include to a C compiler. Naturally, cfront fair to see how C stands up to the using namespace std; was an easy option for estab- same degree of scrutiny. Consider class Dict lished C compiler vendors who the C code in Listing 19. One line { public: were looking for a C++ offering. contains a floating-point value, typedef enum But cfront has a poor reputation which will pull in parts of the { in terms of efficiency and optimi- floating-point library and have a german, sations, and it is understandable disproportionate effect on code french } Language; that a compiler whose mission it size, if floating point isn’t needed. Dict(Language lang); was to prototype the language The next line will have a similar const char* fromEnglish( should suffer from these limita- effect, if printf had been avoided const char* english) const; tions. Unfortunately for the ac- elsewhere. The next line calls const char* toEnglish( const char* foreign) const; ceptance of C++, opinions have strlen(s) strlen(s) times, rather private: been formed on the basis of than once, which has a serious class DictTable experience with this compiler, impact on run time. { which compared badly with ma- But who would argue that public: const char* fromEnglish( ture, optimising C compilers. these abuses are reasons not const char* english) const; to use C in embedded systems? const char* toEnglish( The Free Software Similarly, it is wrong to brand const char* foreign) const; Foundation’s g++, which is well C++ as unsuitable for embed- enum { DictSize = 3 }; struct established as a native compiler ded systems because it can be { that generates excellent machine misused. const char* english; code on several platforms, has const char* foreign; been used successfully as a cross Bigger than a bread box? } table[DictSize]; }; compiler. There is no need for a system const static DictTable Several compiler vendors now implemented in C++ to be larger DictTables[]; offer improved C++ compilers than a C implementation. Most Language myLanguage; in a package with source-level C++ features have no impact on }; const Dict::DictTable Dict::DictTables[]= debugging and other necessities code size or on speed. Some C++ { features have a minimal impact in { Listing 19 these areas, and they have been {“yes”, “ja”}, discussed out of proportion to {“no”, “nein”}, C reality check. {NULL, NULL} /* Reality check - */ their significance. Using C++- ef }, /* some things to avoid fectively in embedded systems re- { in C */ quires that you be aware of what {“yes”, “oui”}, #include is going on at the machine code {“no”, “non”}, #include {NULL, NULL} void main() level, just as in C. Armed with that } { knowledge, the embedded sys- }; char s[] = “Hello tems programmer can gain great //... world”; benefits from using C++, while void main() unsigned i; { int var =1.0; instinctively avoiding the pitfalls Dict germanDict (Dict::german); printf(s); that intimidate the novice. cout for (i=0; i < < < strlen(s); i++) germanDict.fromEnglish(“yes”); /*... */ Email Send inquiry } }

 eetindia.com | February 1998 | EE Times-India