Destruction aids memory management in object-oriented C - Embedded.com

Destruction aids memory management in object-oriented C

Proper memory management is a key component of any successful object-oriented application. This article addresses the relevant issues, with a focus on the object-oriented C programming scheme previously demonstrated.

Destruction can come in many forms, most of it negative. The destruction I write of is class destruction , the opposite of class construction. It's a most useful tool to understand for memory management when working in object-oriented C (OOC).

In this article, we'll discuss how memory-management planning is important when developing an OOC application. We'll examine the ability to dynamically allocate memory for our application window class (AWC) template as well as the foundation classes. We'll also make some changes to the AWC to make it more suitable for data extensive applications.

In a two-part article on OOC, I discussed how to create foundation and AWC templates, respectively.[1,2] In the discussion of the foundation classes, we examined how to create simple classes in C, using structures for containment and functional pointers to create a member function linkage. We also discussed how to create aggregation relationships between classes, which enable classes to have “part-of” relationship with other classes. We also introduced the concept of class resources, which enables the assignment of initial properties to a class when the class is constructed. In this article, we'll take these ideas a step further by dynamically creating objects of child classes for use in the parent class.

In the discussion of the AWC classes, we created a simple template containing a small set of required member functions, namely Activate and Deactivate as well as Construct . Now we'll add a Destruct member function, which will enable us to dynamically remove memory allocated in the Construct member function. We'll also add a new twist to the AWC by adding a SendMessage functional pointer to a WndProc() function to the AWC template structure.

At this point, many of you may be asking, “What is OOC?” In simple terms, OOC is a collaboration of ideas to add the object-oriented features to “C” language. OOC techniques enable C to appear object-oriented with some additional overhead.

Managing memory in OOC
Memory management is the key to a successful object-oriented application. Failure to map out a memory strategy can put your object-oriented application in harms way.

Many embedded software engineers shy away from using any dynamic memory approaches because they're more likely to create hard-to-find memory leaks. If these leaks aren't fixed, they eventually result in catastrophic system failure, possibly in the hands of the customer. To make matters worse, the leaks may be elusive, which means detecting them is difficult unless the development team performs long-term testing prior to deployment. Because of the additional time and effort involved, correcting memory leaks is a daunting task that could delay the release of the product to market, not to mention strain relations between developer and management.

Now that I've cleared the room of any timid software engineers, the rest of us shall boldly proceed, with care. Using dynamic memory can have a great number of benefits, if handled in a well-disciplined, organized manner. Dynamic memory, of course, reduces the amount of memory necessary to run your embedded application, which shrinks your RAM size as well as lowering related hardware costs.

In managing your memory in OOC, you first need to size up the application you wish to develop. For a typical GUI application, you might want to start with what kind of information needs to be displayed under the most extreme circumstances. Let's say we want to create a simple OOC application that requires two option buttons that will enable the user to switch between two views, as shown in Figure 1.


Figure 1: The object-oriented application

In this application, we want to maximize the use of our memory resources; therefore, we'll only allocate memory at run-time and discard any memory that's currently not in use.

One of the first problems that comes to mind is how to segment the GUI display into manageable windows. In addition, how many windows would need to be displayed at any given time? After pondering the pros and cons of any one decision, let's say we decide to break the display into two main areas: the navigation and the view. In the view area, we'll have two views, one for option 1 and the other for option 2. Since, only one view needs to be displayed at any one time, our dynamic memory allocation techniques will be applied in this area. Now using our preliminary software design analysis, let's apply some class names, as summarized in Table 1.

Table 1: Summary of classes used in the example application

Class Description
CApp Our standard window application class that contains WinMain, WinTerm, and Run methods (refer to March 2001 Embedded Systems Programming for more details).2 It's recommended that you have WinMain, WinTerm, Run, and InitInstance as member functions of the CWndApp class instead of global functions. Remember, we're striving for 100% object-oriented design.
CWndNav This will be our container for the CWndView object. This container controls which view will display given the input from the user via the CApp.
CWndView There will be one instance of this class to handle both view types. This class will also contain a CButton object used to draw both styles of buttons.


Figure 2: UML class diagram

To map out how the class hierarchy will be organized, I've provided a UML class diagram in Figure 2. In this UML class diagram, you'll notice that I've used stereotypical names such as “<>” to denote what elements of the structure are functional pointers, which can be assigned statically. The “<>” denotes the pointer to object (in other words, m_pobjWndView ) that is dynamically allocated. I also used the stereotype labels in classes to clear up any ambiguity of the purpose of a class.

OOC class destruction
In the previous OOC articles, our classes only instituted a constructor member function and not a destructor member function, since the member functions were bound statically. Now in our OOC application classes, we'll allocate memory in the New() member function and establish a destructor member function, which will be used to free memory of the objects that we wish to create dynamically. As a reminder, the constructor member function used in OOC is primarily used to assign an object's initial attributes by means of a resource structures. A complete list of the resource structures used in an OOC application is stored in a resource file, as explain in my previous OOC articles.[1,2]

Starting at the top, the CApp class will contain a pointer (m_pWndNav ), which is an instance of the CWndNav class. In the Main Application section of the code, we first declare a pointer to the instance of the CApp class called pApp . As with all pointers dynamically declared at run-time, they'll need to have space allocated in memory. Since we're using C, we'll be calling malloc() to allocate the memory space for the pointer, as shown in Listing 1. (NOTE: all listings for this article can be found at ftp://ftp.embedded.com/pub/2003/10curreri_listings)

In further examination of the Listing 1, besides allocating memory for pApp and m_pWndNav pointer, we also need to bind the New() functional pointer to the mWndNavNew function, which is a member function of the CWndNav class. This is necessary since there's no way to invoke the member function through its functional pointer until its function has been assigned. We could have called the mWndNavNew function directly if it were either contained in the same file or made public via extern (in other words, mWndNavNew(pApp->m_pWndNav); ).

Our approach is more in the spirit of object-oriented methodologies and signifies a new change to the AWC. In the previous articles, the New() function wasn't a member of the class, but was global within the project. To improve encapsulation, the New() has been brought in as a member of the class, which enables us to apply more consistent object-oriented methodology when allocating memory for the dynamic objects of the class.

You may notice that the CWndNav class contains two instances of the CWndView class as well as any additional support for GUI foundational, as shown in Listing 1. Just below this, we created an endless loop for processing user choices for selecting the different views. Once a choice has been entered, the SendMessage function is invoked passing arguments to the mWndNavWndProc function for further processing.

SendMessage->WndProc()
In the previous articles, the structure of the AWC was Activate and Deactivate . This works well for some applications. I've found, however, that intense data services and display applications are more suited using a new template design in which the Activate and Deactivate functionality are clasped into one member function called WndProc() .

To have this new template (AWC 2.0) more widely accepted and used by other software engineers, a SendMessage functional pointer to the WndProc() provides a simple approach that many software engineers understand. In our simple OOC application, the SendMessage is just a functional pointer to the WndProc() member function. However, within large multi-task applications, a message queue could be inserted and be a server for messages from many different sources, including other windows.

In the CWndNav class, you'll find a constructor (mWndNavConstructor ), which performs memory allocations, and functional binding that collects member functions as well as assigns any object's resources, if applicable. A destructor also exists to free up memory used by our dynamic objects, for example: m_pobjWndView .

The mWndNavWndProc has four arguments, which have specialized purposes. The first object, of course, is the instance of the object _this . You may have noticed that the implicit this pointer used in OOC has now been changed to _this . This change is necessary if you want to avoid a multitude of C++ compiler problems; hence, it's better to use _this .

The remaining three arguments, which are somewhat new to OOC, are used extensively within the Windows environment where they play a similar role. The Msg argument can receive message label types such as WM_PAINT for asking the window to display something onto the screen. The third argument, wParam , specifies which object (for example, ID_OPTION1 ) is the recipient of the message found in the window. The last argument, lParam , is used to pass additional information, usually by its address. Typically, this action is handled by casting its address to the lParam as seen in both ID_OPTION cases, involving the string.

The mWndNavWndProc function dispatches messages to the m_pobjWndView dynamic object via the SendMessage functional pointer of the CWndView . Notice that in each case statement in the switch, the New and Construct functional pointers are called to allocate the necessary memory, bind functional pointers, and then call SendMessage . Once the screen has been displayed, the objects have their memory deallocated using the destructor.

As shown in Listing 3, the CButton and the CWndView classes are defined. Here the CWndView has a pointer to an object of CButton . The memory is again allocated in the mWndViewNew() member function of the CWndView class. The CWndView destructor deallocates the memory for its objects. When the CWndNav class invokes a SendMessage function, the mWndViewWndProc() receives the message and processes it by displaying specified objects onto the screen. The CButton object is also displayed in one of two possible formats defined by either the Paint or GotFocus member function.

What to avoid
Here are a few tips that can help you avoid larger problems down the road.

  • When working with pointers, especially functional pointers, make sure you allocate the pointer before trying to use it. This may seem a simple concept, but it's an important one. An unallocated member functional pointer doesn't create an error when you compile; however, a run-time error is reported when your code attempts to make a call to the member function pointer name (in other words, construct(…)) .
  • Also, make sure that whenever you're copying data from one location to another, especially with strcpy() , you don't overwrite the buffer space. Many times, the developer accidently overrides a string buffer attribute specified just before the functional pointer within a class. This situation creates run-time error that's very difficult to find, since it may be difficult to replicate.
  • Finally, make sure that all memory is destroyed. Not catching this problem early may lead to memory leaks that eat up valuable memory, which, if left unchecked, could create problems in the market place.

The output
All the code in this article can be assembled into a single file and compiled and executed using any ANSI C or C++ compiler. I used a common C++ compiler (Visual C++), so you can also test these concepts on a PC before porting the code to your target. To avoid getting caught up into the various graphical issues, I chose to use the MSDOS terminal via the printf() function to display simple character output. In figure 3, you can see the results of our OOC application. Each time you enter into either view one or two, memory is dynamically being created and destroyed.


Figure 3: Output

Looking back
In this article, we explored how to use memory in a more dynamic manner in OOC. We created an OOC application that had objects, which had their memory dynamically created and destroyed at run-time. In our OOC application, we only had a type of child class and one type of GUI class. This concept can be expanded to include many different types of window child classes of varying levels using a multitude of GUI classes, which in part could have their own degrees of dynamic memory allocation.

This dynamic use of memory can yield a more optimal design for developers, which will enable them to squeeze out more features and services within a small memory package. However, these same developers must proceed cautiously so as not to create memory leaks along the way. The ability to manage such design and development challenges will result in a more efficient application.

I hope this article helps you improve your embedded applications and provides another solution for developers working with a cutting edge embedded processor and only a C compiler at their disposal.

Matthew R. Curreri is a senior software engineer at Panasonic. His user interface and application-level embedded software helped power the first digital HDTV receivers for off-air and DirecTV satellite broadcasts. Matthew holds an MS in electrical engineering from Drexel. He may be reached at .

References

Leave a Reply

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