Guidelines for using C++ as an alternative to C in embedded designs: Part 1 - Embedded.com

Guidelines for using C++ as an alternative to C in embedded designs: Part 1

Click here to see Part 2.

Embedded software applications are most commonly written in C. For manyyears, C++ has been seen as the natural successor and has found greateracceptance, but the increase in its usage has been much slower thananticipated.

There are a number of reasons for this. Firstly, embedded developersare quite conservative and prefer to use solutions that are provenrather than novel ” “if it ain'tbroke, don't fix it “.

There is also the lesson of experience. Many developers haveattempted to use C++ for embedded applications and failed. Suchfailures may sometimes be attributed to shortcomings in developmenttools, but most often it is the inappropriate use of the language “treating an embedded system like a desktop computer ” that is to blame.

In this two part tutorial, an attempt is made to address some ofthese issues, to give guidelines to the effective use of C++ forembedded applications and to show that the language has some truebenefits to the embedded developer.

One of the key reasons why C++ has gained popularity in thecomputing world in general is its history. Although it is a modernlanguage, with object oriented capabilities, it has backwardcompatibility that makes its adoption ” learning and application “straightforward.

Figure 1 below gives someinsight into the evolution of the language. Later in this series wewill look at C/C++ compatibility in more detail.

Figure1. The Genealogy of C++

Limitations of C
Although C is widely used, it has limitations, as it was not designedfor embedded applications or for projects of a scale that is nowcommonplace. Key limitations include:

1) C is extremely powerfuland flexible and can therefore be dangerous.(It has low level capabilities – which areuseful for embedded ” but also represent many pitfalls for the unwary .)

2) Programmers need to bevery methodical and disciplined

3) Programmers need tounderstand how the program behaves at low and high levels (largeprojects are thus hard to maintain)

4) Programmers need anexpert knowledge of the application

However, C++ has powerful object oriented capabilities which canhelp significantly with addressing the limitations of C:

1) it encapsulates and hidesareas of high expertise from non-experts into “objects;” (A test casewill demonstrate the encapsulation of expertise later in Part 2 in thisseries).

2) Objects can be usedintuitively by non-experts to implement conceptual designs at ahigh-level

Language Overview
Like ANSI C, C++ features many enhancements to the original C languagein addition to its object oriented capabilities. C++ is not simply asuper-set of C, as the two languages evolved in parallel.

However, the language may be learned and applied incrementally, aswill be illustrated later in this paper. For the moment, here is asummary of some of the useful language features:

1) Dynamicmemory allocation operators. The operators new and delete arealternatives to the library functions malloc() and free() and lead tomore readable and less error-prone code.

2) Functionprototypes. Iintroducedin C++ and adopted into ANSI C, their use is mandatory.

3) Functionparameter default values. A function may have default values fortrailing parameters to enhance code readability.

4) Reference parameters. Function parameters may be passed by reference instead of by value(copy). This gives the efficiency of using pointers without the greatpotential for errors that results from their use. Here is a simpleexample function:

void swap(int& a,int& b)
{
        int temp = a;

       a = b;
       b = temp;
}

4) Inlinefunctions. The code for a (small) function may be declared asbeing inline ” i.e. a copy of the actual code is included at each callsite. This improves the speed of execution, but may increase code size.This may be achieved using the inline keyword or by including the codein the body of a class definition. This is only advice to a compiler,which will probably take account of current optimization settings.

5) Functionoverloading. Multiple functions may be defined with the samename. The compiler would distinguish between them by their uniquenumber/type of parameters. This results in more readable code, withless contrived function naming.

6) Typesafelinkage. In C++, all function names are “mangled” ” their namesare modified to reflect parameter and returned data types. This enablesthe linker to perform additional cross-module checking without beingrequired to “know” about C++. This is also the mechanism wherebyfunction overloading is implemented.

Object Oriented Features
C++ is often described as an object oriented language, but this is notstrictly true. It is really a procedural language (like C) with someobject oriented capabilities.

The key language feature is the concept of a class. A class is verysimilar to a structure in C, but has some important differences andenhancements:

1) A class is defined usingthe keyword class.

2) A class may contain codeas well as data (not just pointers to functions)

3) Class members may bedeclared as private or public, enabling key functionality to be hidden(encapsulated)

4) A class is, in effect, anew data type with the name of the declared class; the data andpossible operations on that data are defined

5) Operators may be defined(overloaded) to manipulate the data in a class, which leads toreadable, intuitive code

6) Member functions may beincluded that are automatically called when an object is created anddestroyed (constructors and destructors)

7) One class may be derivedfrom another, inheriting all of its characteristics, which may beaugmented or replaced

Templates
A much-vaunted feature of C++ is templates. This concept may be appliedto function or class definitions. The idea is simple: the programmercan define the complete structure and logic of a class/function, butleave the specification of data types (for parameters and internalvariables) open.

This acts as a “recipe” for the compiler to generate code/data whenthe programmer instantiates a template (and indicates which data typesare required).

Here is a simple example of a function template:

       template void swap(X& x, X& y)         {                X temp;                temp = x;                x = y;                y = temp; }

This might be utilized like this:

       int i, j;
       float a, b;
       …
       swap(a, b); // instantiation #1
       wap(i, j); // instantiation #2

Templates provide all programmers ” desktop and embedded alike “with a very useful capability to create highly reusable code. Butembedded developers always want to know the “cost” ” what is theoverhead incurred by the use of templates?

In theory, no overhead would be anticipated, as a template does notitself generate code ” code is only created by the compiler whenrequired. But this is the cause of a problem: compilers only processone module at a time, so the template instantiations are on aper-module basis.

The result of this is the same code ” i.e. a template instantiatedwith an identical set of data types ” might be generated in numerousmodules of the application. This may increase the memory footprint verysignificantly. This may be of minimal concern to the desktopprogrammer, but is likely to be of great importance to the embeddedsystem developer.

A possible solution to this problem ” apart from simplyunderstanding what is happening ” is to have tools which are optimizedfor embedded work. One approach is to have a linker which can take”hints” from the compiler and eliminate redundant (copies of) code.

Exception Handling
In almost any kind of software there exists the possibility for errorconditions to occur, which may be detected by the software. Often, thisrequires some kind of “emergency exit” to some code to deal with theerror gracefully.

Unfortunately, structured programming languages (like C, C++ etc.)do not conveniently handle such a possibility. The C++ ExceptionHandling System (EHS) was created to address this problem.

The EHS is manifest in three additional C++ keywords: try, throw andcatch. A try block is used to control when exception handling isactive. A throw statement is used to instigate the processing of anerror (exception). And a catch block ” of which there may be many, eachidentified by an object type ” contains the code to process the error.

Here is a simplistic example which shows how the EHS syntax works:

       void scopy(char* str)
        {
               if (sizeof(store)+1 < strlen(str))
                       throw -1;
               strcpy(store, str);
        }

       void get_string()
        {
               char buff[100];

               cin >> buff;
               scopy(buff);
        }

       main()
        {
               try
        {
                   …
                   get_string();
                   …
               }
        }

       catch (int err)         {                cout << "String too long!";
        }

Although the EHS simplifies the coding required to deal withemergency situations, it does come with a cost: extra code is generatedto enable throw statements to work correctly. An embedded developer isalways wary of extra overheads, so the size of this extra code shouldbe monitored.

There are two additional points about the EHS that embeddeddevelopers need to appreciate:

First, since the compiler has no way to know whether a particular piece ofcode will be called from within a try block, the extra code will begenerated for all the application program modules. What is worse isthat some compilers have EHS enabled by default and it is theprogrammer's responsibility to turn it off. So, if the EHS is not beingused, it is essential to check the options on the compiler to ensurethat no overhead is being incurred.

Second, the EHS is designedto facilitate a selection of different responses to a wide variety oferror conditions. This makes complete sense for desktop applications.However, many embedded applications have quite simple error handlingneeds.

Often, a system reset is performed if a “difficult” error isdetected, as this is the best way to get back to a stable operatingstate. It may quite reasonably be concluded that the EHS would beoverkill under these circumstances. However, there are two ways to useit with lower overhead, which may tip the balance in its favor:

* If no matching catch statement is found when a throw is effected,a library function terminate() is called. This function could bereplaced by a routine to perform a system reset.

* Just a generic catch handler may be provided, using this notation:

           catch (…)

This will catch exceptions of any type and results in a measurablylower code overhead.

Language Extensions
One of the big advantages of using a standardized language, like C++,is that the code and the programmer's expertise are very portable. Onthis basis, it would seem foolish to add extensions to a standardizedlanguage, as this would lead to non-portability.

In an ideal world this would, indeed, be true. However, C++ (like C)was designed for desktop computing application, not embedded. Embeddeddevelopers are numerous, but still represent a small minority of C++users. As a result, a small number of language extensions may beconsidered useful and acceptable:

1) Many compilers, designedfor embedded use, include a feature to enable data to be packed, makingbest use of memory by eliminating alignment bytes in arrays andstructures.

For this facility to be really effective, the programmer needs thefacility to turn it off for specific data structures, to increase speedof access or provide format compatibility with some other system orsoftware. This override is usually implemented by the unpackedextension keyword. A corresponding packed keyword may be available topermit individual objects to be packed when the global option isswitched off.

2) Traditionally, interruptservice routines (ISRs) were coded in assembly language for tworeasons: execution speed efficiency and context saving. Moderncompilers generate code which is certainly fast enough for an ISR, buta regular function does not facilitate the full context save andalternate return mechanism that is required. An additional keyword,interrupt, is commonly provided to declare a function as being an ISR.

3) Sometimes the use ofassembly language is unavoidable. This is usually because a CPU featureis inaccessible from C++ ” enable/disable interrupts is a commonexample.

Although there is always the option of calling a function written inassembly language, the overhead may be reduced by being able to insertthe necessary lines of assembler code in line. Commonly the extensionkeyword asm is available to facilitate the declaration of assemblylanguage pseudo-functions. (#pragma asm may also be a possibility.)

4) To embedded softwaredevelopers using C, the keyword volatile is essential, as it is commonto have variables that are shared between threads or represent I/Odevices. This keyword was not always implemented in early C++compilers, but is obviously still required.

A Strategy for C to C++ Migration
The big advantage of the backwards compatibility between C++ and C isthat migration can be done gradually. A possible strategy is to apply athree stage process:

1) Exploit reusability ” write all new code in C++

2) Treat C as C++ ” clean up existing C codeto comply with C++ requirements

3) Start using C++ language features more widely

At some later point, the object oriented capabilities of C++ can beexploited more fully.

ApplyingReusability. A potential incompatibility between C and C++ isapparent at link time. A C++ compiler, as mentioned previously, willmangle function names to incorporate information about the number andtype of parameters and return data type. This facilitates functionoverloading and results in typesafe linkage. A C compiler will not dothis, so linking C and C++ modules would result in errors.

Fortunately, in C++ a mechanism is provided to work around thisproblem: the linkage of a function may be declared as being compatiblewith C using the extern “C” construct, thus:

           #ifdef _cplusplus            extern “C”
           {
           #endif
           extern void alpha(int a);            extern int beta(char *p, int b);            #ifdef _cplusplus            }
           #endif

Cleaning up C code for C++
The next step in migration to C++ is to review and modify existing Ccode so that it is truly C++ compatible and can be processed by a C++compiler without any problems. The resulting code may be called CleanC. Here are the key issues to consider:

1) Functionprototypes ” These are mandatory in C++.

2) Type casts ” Implicit casting is much stricter in C++; use explicit cast operatorslike (char).

3) Enumeratedtypes ” These are not taken very “seriously” by C compilers,being considered a means of making typed, symbolic constants; in C++enum declarations result in true data types.

4) String arraysize ” In C and C++ a char array is commonly used to contain aNULL terminated character string. This requires the array to havesufficient capacity for the characters and the terminator.

In C, it is not permitted to initialize an array with a stringlarger than the declared array size, but the NULL terminator may bediscarded. This leniency is not extended in C++. A simple strategy isto leave the array size unspecified and allow the compiler to allocatethe space, thus:

           char str[] = “xyz”;

5) Nestedstructures ” In C, if a struct is defined within anotherstruct, the inner struct may also be used declared by itself elsewherein the module. In C++, the scope of the inner struct is strictlyconfined to the outer one. Hence, the following code would not be CleanC:

           struct out
           {
                      struct in { int i; } m;
                      int j;
           };
           struct in inner;
           struct out outer;

6) Multipledeclarations ” In C, it is quite legal to declare a variable ina module, outside of a function and then declare it again later in themodule. This only causes problems if the redeclaration is inconsistent.In C++, multiple declarations are illegal.

7) Additionalkeywords ” C++ introduces a number of additional keywords ontop of those already reserved in C. This may be a problem if there is aclash with identifiers in the C code. The best strategy is to introducea naming convention and apply it to the code. For example, allfunctions, types and variables have an upper case initial letter andall constants are fully capitalized.

C+: A better C?
The last step in the migration process, prior to considering objectoriented techniques, is to incrementally apply C++ language features.This results in a hybrid language ” a “better C” ” that we might callC+. Possibilities include, but are not confined to, the following C++features:

1) New commentnotation ” C++ introduces a new, end of line comment notationusing the // operator. This is neater and addresses issues with nestingwhen using the traditional ” /* … */ ” notation (which is stillavailable, of course).

2) Referenceparameters ” In C++ by default, parameters are mostly passed byvalue (copy) the same as in C. However, there is also the option topass by reference to avoid complications of explicit pointer usage.

3) Variabledeclaration placement ” In C, automatic variables must bedeclared at the head of a block (commonly they are just placed at thehead of a function). This limitation is relaxed in C++, enablingvariables to be declared close to their point of first use. Forexample:

           for (int x=0; x<3; x++)
           …

4) Constructorsin structures ” In C++, a class definition may includeconstructor and destructor member functions, which are automaticallycalled when an object comes into and goes out of scope, respectively.Since a struct is, in most respects, identical to a class, it too mayinclude a constructor. This is useful to facilitate the automaticinitialization of the struct variable.

5) Memory leaks ” By use of paired constructors and destructors, memory leaksmay be avoided by ensuring that allocated resources (like dynamicmemory) are deallocated when no longer required.

6) Exceptionhandling ” The EHS, introduced earlier in this paper, is notstrictly an object oriented language feature and, hence, its use maylogically be considered when using C+.

Next in Part 2 : “ Encpsulation ofexpertise using C++ objects.”

Colin Walls has over twenty-five years experience in theelectronics industry, largely involved with embedded software. Afrequent presenter at conferences and seminars including the EmbeddedSystems Conference he is a member of the marketing team of the EmbeddedSystems Division of Mentor Graphics.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.