Welcome to the Modern Embedded Systems Programming course. My name is Miro Samek and in this lesson I'll continue with OOP and polymorphism, but this time you will see how to implement polymorphism in portable, standard-compliant C code. This should reinforce what you've learned about virtual functions in C++ in the last lesson and expose some additional nuances of the VPTR-VTABLE implementation. This lesson will also provide some general principles and guidelines about using object-oriented programming in embedded software. By the way, this is a very round lesson number hex-20, which coincides with the even more round number of subscribers to the Quantum Leaps video channel, which is about to cross hex-8000 or 2- to-15-th. I'd like to thank you all for your help in reaching these fantastic milestones. As usual, let's get started by copying the previous lesson30--the C version without the underscore-CPP suffix, to lesson32. Get inside the new lesson32 directory and double-click on the project lesson to open it in the micro-Vision IDE. To remind you quickly what happened so far, in the last lesson, you learned about the OOP concept of polymorphism, which is the ability to provide different methods for the same inherited operation in the subclasses of a given class. Specifically, you saw how polymorphism is implemented in C++ by means of virtual functions and you have reverse-engineered the late-binding mechanism, where the specific method for a given operation is resolved at runtime based on the type of the object, not the type of the pointer used in the call. But before you go any further, from the last lesson is should be clear that unlike class encapsulation and single inheritance, which were essentially free in C, polymorphism in C will add coding complexity and overhead. Therefore, if you intend to use polymorphism extensively, you would be probably better off by switching to C++. However, if you build or use software libraries (such as the QP/C real-time framework), the complexities of polymorphism in C can be confined to the library and can be effectively hidden from the application developers. But either way, this lesson’s primary goal is to show you how things really work under the hood, which should help you to use polymorphism more efficiently and with greater confidence in any language. So, today you will implement the virtual functions manually in C following exactly the C++ VPTR-VTABLE design. For this, you need to explicitly add the VPTR to the attribute structure of the Shape base class. The VPTR will be the first attribute, and will be a pointer to the 'const' ShapeVtable structure. This 'const' will allow the VTABLE to reside in ROM. Please note that at this point you have not provided the declaration of the ShapeVtable struct just yet. But here you are using only a *pointer* to this struct, which the compiler accepts, because all it needs to know for processing the Shape attribute structure is the size of the pointer, which is known, not the size of the whole VTable. But now, you obviously need to declare the VTABLE, which even though it's called a "Table", is typically not implemented as an array but rather as a structure of pointers to all *virtual* functions, such as draw() and area() in the case of the Shape class. I used pointers to functions in this video course before, but today is the time to introduce them a bit more carefully. So, the C language allows you to provide a pointer to a function, just like you can provide a pointer to a variable. In both cases, a pointer contains the address (of the function or variable, respectively) and also the type information about the entity that is being pointed to. In case of pointers to functions, the type information consist of the full signature of the function. So, to declare a pointer to the draw() function, you first need to write the signature of this function. And then, you need to convert the actual name of the function into a pointer, which means using the asterisk operator. But because of the specific operator precedence rules in C, the asterisk would be bound to the return value instead of the function name, so you need to add explicit parentheses around the pointer, like this. You apply the exact same steps to declare a pointer to the area() function. OK, so the VPTR and VTABLE are both declared, but now the users of the Shape class as well as its subclasses must be able to call the virtual functions. You can provide this virtual call functionality, also known as late binding, in a few different ways. First, you can provide member functions for that, just like all the other operations of the Shape class. Specifically, you can take the draw() method signature and turn it into the Shape_draw_vcall() function. Similarly, you can take the area() method signature and turn it into the Shape_area_vcall() member function. The implementation of these virtual call functions goes to the shape.c file. Here, in the Shape_draw_vcall() function you first get from the "me" pointer to the vptr. Next, you get from the vptr to the specific pointer to function, such as draw. And finally, you need to provide the parameters of the call, which include the "me" pointer and potentially other parameters included in the signature of the function. You repeat the exact same steps for the Shape_area_vcall() function, except that this function returns a value, so you need to add the return statement in front. As you can see, the C compiler accepts this syntax without complaints, because the presence of the parameter list following the pointer to function tells the compiler that this is a function call based on this pointer-to-function. However, this syntax fails to show you that you are de-referencing a pointer and I prefer to be very explicit about it. Therefore I prefer the alternative syntax analogous to de-referencing any other pointer, that is, with the asterisk in front. Of course, again, just like in the declaration of a pointer-to-function, you need to use paretheses around the pointer. As you see, the compiler accepts this version just as well. But, either way, the most important point here is that the "me" pointer is used *twice*: once to find the VPTR within the object, so that the call is specific to the object, not to the type of the “me” pointer; and the second time the “me” pointer is used as the usual first parameter of a member function. So, this is the first clean and straightforward solution that will work. But it has a big drawback, and that is the additional function- call overhead to call the invented vcall() functions. But, the good news is that the newer C99 language standard allows you to avoid this overhead, by using the “inline” functions introduced exactly for this purpose. To make the functions inline, you move the whole definitions of the functions into the header file and you add the keywords inline and static in front. The discussion as to why it is a good idea to use both inline and static is a bit lengthy and I’m going to leave it for another day, as it is off topic for today. But now, when you try to compile this code it fails, because as it turns out, this compiler still works in the older C89 mode and does not recognize the ‘inline’ keyword. You can remedy this, by opening the Project Options dialog box, and going to the C/C++ tab, where you can tick the box next to C99 Mode. As you can see, this triggers the complete re-compilation of the whole project, but this time the code builds cleanly. While the inline implementation of the virtual call mechanism is the preferred way, for completeness I’d like to mention the third implementation option based on preprocessor macros, which will look as follows: You take the function definition and turn it into a macro by stripping away most of the type information. This is because macros provide only purely textual substitutions. Therefore, to avoid any surprises, when for instance the client code would use expressions for the macro parameters, it’s always a good idea to enclose the macro parameters, like the “me” pointer here in additional set of parentheses. For all these reasons, macros are not nearly as good as inline functions. But I still show you the macros, because they are the only low-overhead option for the older C89 compilers, which are still in extensive use. Alright, so you’ve seen how to define and how to de-reference pointers to functions, but you are not quite out of the woods yet with your virtual functions in C. You still need to somehow define the VTABLE in ROM and also you need to setup the VPTR in each instance of the Shape class and its subclasses. As you remember from the reverse-engineered C++ implementation from the last lesson, the VPTR and VTABLE setup occurs in the constructor, so that’s where you need to go next. Here, you need to define an instance of the struct ShapeVtable in ROM, meaning that it will be both ‘static’, that is not on the stack, and ‘const’. Now, a ‘const’ object cannot be changed, so you must immediately initialize at the point of creation. But before you can initialize the vtable, you need to provide the functions, with which to initialize the draw and area pointers-to- functions in your VTABLE. So, let’s provide the prototypes for the functions: Shape_draw() and Shape_area(). You can make these functions static, because they will be only used locally inside the shape.c module. So, now you can finally initialize your vtable instance, whereas you can choose between the two options of the syntax. First, you can directly use the function names. The compiler will accept this, because the absence of parentheses after the function names means that these are not function calls but rather addresses of the functions. However, I prefer the alternative syntax, in which you explicitly use the ampersands, that is address-of operators, to leave absolutely no doubt that here you mean addresses of the functions. So, now, you can setup the vptr in the newly constructed Shape object to point to the vtable for the Shape class. And finally, you need to define the Shape_draw() and Shape_area() functions. Interestingly, at the level of the Shape class, you cannot really provide meaningful implementations, because Shape is too abstract. So, for now you just leave the functions empty, but to avoid compiler warnings about unused parameters, you can use the idiom of casting the unused parameter on void. At this point, you are done with the general virtual function machinery in the Shape base class, and you can immediately put it to good use by writing the generic drawGraph() operation discussed in the last lesson. So, here is the C++ code from lesson 31, where an array graph[] holds pointers to Shape, but these pointers actually point to subclasses of Shape, such as Rectangle, Circle or Triangle. The central point here is that this C++ code applies polymorphism to call the specific draw() method for each Shape pointer in the graph array in a very intuitive and elegant way. So, now you your job is to do the same in C, using the virtual call mechanism you just implemented. Well, it’s actually very similar to the C++ version. The only difference now is that you use... the Shape_draw_vcall() inline function to apply polymorphism. So, this is all there is to it, except that you obviously still need to provide the prototype of the drawGraph() operation in the shape- dot-h header file. OK, the Shape base class is done, but some work is still needed in the subclasses of Shape, like Rectangle or Circle. They will all inherit the VPTR and the virtual call mechanism from Shape. But, each subclass, like Rectangle, still needs to provide its own VTABLE and its own specific implementation of the virtual functions draw() and area(). This was the main purpose of polymorphism. So, let’s do it now for the Rectangle subclass of Shape. You need to go to rectangle-dot-c and pretty much repeat the steps you did for the Shape base class. So first, you add the static and const virtual table inside the Rectangle constructor. At this point, I presume that Rectangle does not add any new virtual functions to the ones already specified in the Shape superclass. In that case, Rectangle can use the ShapeVtable structure directly. Otherwise, you would need to apply inheritance in C with respect to VTABLES, but let’s keep it simple here and not add any new virtual functions in Rectangle. So, back to the initialization of the VTABLE, instead of Shape’s methods, the Rectangle VTABLE will obviously contain the Rectangle’s implementations. But now, Rectangle already provides its own Rectangle_draw() and Rectangle_area() functions. The problem is that the “me” pointers in the signatures of the Rectangle implementations are of type “Renctangle”, while the pointers-to-functions in the Shape’s VTABLE have signatures that expect “me” pointers of type Shape. Because remember, that even though Rectangle and Shape are related by inheritance, the C compiler doesn’t know about it and will not preform upcasting automatically. So, to make the Rectangle signatures fit into the VTABLE originally defined for the Shape base class, you need to cast pointer-to- functions, by using the whole function signatures, like this. Only now, you can finally assign the inherited VPTR for the Rectangle class, but you need to be very careful to do it *after* calling the constructor of the Shape superclass. This is because, just like in C+ +, the Shape constructor sets the VPTR to point to the Shape’s VTABLE, but here you want to override this setting to the Rectangle’s VTABLE. And this is all, because rectangle-dot-c already contains the implementations of the Rectangle_draw() and Rectangle_area() functions. The very final touch, which will allow you to test all this in the debugger, is to call to the generic drawGraph() operation that makes use of the virtual call mechanism. For this, you can open the C++ code from the last lesson and copy the relevant snippet to your main-dot-c file. In the C version, you need to setup the graph array of pointers slightly differently, because you don’t have the Circle class in C, so let’s use the s1 Shape object instead. Also, instead of creating a Shape object dynamically, let’s create a Rectangle object. Then, of course you need to call the right constructor on it as well. So this is about it, but to make the C compiler happy, you still need to add an explicit cast in the Rectangle constructor and in the initialization of the graph[] array of pointers. Alright, so you got the code to build cleanly. Now I’m sure you are curious how all this will work in a real embedded board. So, let’s connect the TivaC LauchPad board and open the debugger. The first thing I’d like you to verify is the Rectangle constructor. When you step inside, the first code you see is the call to the Shape constructor of the superclass. Let’s step inside again and verify that the VPTR is initially not set, but gets initialized to the Shape VTABLE containing addresses of Shape_draw and Shape_area functions. Also, please note that the VTABLE is definitely in ROM, because its address starts with zeros. Now, when you keep stepping, you return back to the Rectangle constructor and there, the VPTR gets overridden to the Rectangle’s VTABLE with addresses of Rectangle_draw and Rectangle_area functions. So, your constructors in C work now exactly like the C++ constructors from the last lesson, except that in C you had to add the VTABLE and the code to set the VPTR manually, while C++ synthesized that code automatically. Now, the most interesting part is to see the virtual calls inside the generic drawGraph() function. But before you step there, just note that the first object to draw will be Shape s1, the second will be the Rectangle r1, and the third will be another Rectangle pointed to by the pointer ps3. Once inside drawGraph, step to the Shape_draw_vcall() virtual call, and take a look at the disassembly. When you compare it to the code generated by the C++ compiler, you can see that they are... identical. So, let’s step through this late-binding code a single instruction at the time. The first LDR fetches the VPTR from the me pointer in R6 and places it in R0, The second LDR fetches the first virtual function from the VTABLE now in R0, and places it in R1. The MOV instruction copies the “me” pointer in R6 to R0 to prepare it as the first parameter of the call. And finally, the BLX R1 instruction executes the call to the address in R1. And indeed, you end up in the Shape_draw() method, because as you remember the first pointer in the graph array was the s1 Shape class object. The second time through the loop in the drawGraph function the same late-binding code now invokes the Rectangle_draw function, however. So, as you just saw, the C implementation of virtual functions works exactly like the C++ original, down to the machine instructions for late binding. At this point, I’d like to note that the VPTR-VTABLE implementation of polymorphism in C that you learned here is not the only possibility. In the literature or online you can find plenty of other techniques. Perhaps the most frequently used alternative is to remove the VPTR level of indirection and embed the whole VTABLE inside every object. The advantage here is a little simpler virutal call as well as a nicer syntax more closely resembling C++, because the draw() method, for example, is invoked on on object using the dot operator, like this. But the drawback is the increased RAM usage, because VTABLE is now in RAM rather than ROM. Not only this, the VTABLE is repeated in every object, so if you have many objects you can easily double or triple your RAM usage. An example book that presents this implementation method is “Design Patterns for Embedded Systems in C” by Bruce Powel Douglass. You can find there the whole chapter on object-oriented programming and specifically its C implementation. The author discusses Classes, Objects, Polymorphism and Virtual Functions, Subclasses and other aspects. There is also specific C code where you can clearly recognize the VTABLE embedded directly in the attribute structure. Interestingly, please note how this book also uses the “me” pointer naming convention for the class member functions in C. The last subject I want to touch upon today is when to use polymorphism and perhaps even more importantly when not to. Let’s start with a simple “code-smell” indicating that polymorphism might be helpful. For that imagine how the generic drawGraph() function would be implemented in a very traditional C-code, without late-binding. Well, it would probably look something like that: Specifically, you would probably add an attribute “kind” into the Shape structure, and you would also provide an enumeration of all possible kinds of shapes, such as RECTANGLE, CIRCLE, TRIANGLE, etc. Then every time you would need behavior specific to the kind of shape, you would use a switch or if-then-else branching to invoke the right function for the kind of shape you happen to be dealing with. But such code is far less maintainable, because every time you add or remove a new kind of Shape, you would need to find and change all places throughout the code. In contrast, the virtual call mechanism is not only much more efficient. It is also automatically extensible, because you can keep adding and removing different shape subclasses, but you don’t need to change the code with the virtual call at all. You don’t even need to recompile the whole Shape base class, including drawGraph() and any other functions like it. So, here is your main guideline. Whenever you see or anticipate code like the switch statement scattered throughout your project, you should consider polymorphism. But please remember that the only valid reason for applying polymorphism is that the object-specific behavior needs to be selected at runtime. Which brings me to the guidelines when NOT to use polymorphism. Well, you don’t need polymorphism when the selection based on object type does not need to happen at runtime. The most frequent misuse of polymorphism I see in the industry is in attempts to manage product lines of related, but slightly different products. For example, a medical company making, say, infusion pumps for drugs might start with a single pump, but then keeps comming up with ever more versions for various drugs and market segments. The software for the new pumps is almost never created from scratch, but rather is continuously tweaked and adapted from the existing software. But actually, the software for a new pump is not copied and changed separately. Instead, a single, ever growing code base is extended to service all existing pumps. And herein lies madness. Developers litter the code with conditional logic, which quickly becomes unmanageable and untestable. This code makes decisions at runtime based on the product type, version numbers and similar variables, while really any given code set ends up in only one specific product. Therefore, the selection of the product does NOT need to happen at runtime. It can happen at compile-time and link-time. So, even if polymorphism could eliminate much of the convoluted IF- THEN-ELSE logic, a better way is to design a clean Board Support Package (BSP) interface and then provide different implementations, different BSPs, for different products. So, for product ABC, you would have bsp- underscore-ABC, and so on. This is the use of abstraction, as I explained back in lesson 29. Of course, there is much more to it than just a simple BSP abstraction. The effective management of product lines requires careful *physical design*, which is the way you partition your code into directories and files, such as header files and implementation files. That way, you can build the final software for any given product by combining various modules at *link-time*, rather than using techniques like polymorphism at runtime. The art of good physical design is very valuable especially in embedded systems programming. Unfortunately, the subject is not widely known or appreciated. While tons of books talk about logical design techniques, such as OOP, very few resources exist for physical design. One notable exception is the book “Large Scale C++ Software Design” by John Lakos. This book explains *physical design* really well. Highly recommended. And this concludes this group of lessons about object-oriented programming in embedded systems. In the next lesson, I will go back to my overarching theme, which is introducing the main trends that shaped the modern embedded systems programming. Specifically, I will start a new segment, in which you will learn about the last big trend that went mainstream during the 1980s, and that is event-driven programming. If you like this channel, please give this video a like and subscribe to stay tuned. You can also visit state-machine.com/quickstart for the class notes and project file downloads.