Back to TOC Features


Information Hiding in C via Modular Programming

Dwayne Phillips

Encapsulation is an important discipline even when the language doesn't fully support it.


This article discusses how to write programs that have both the information hiding advantages of C++ and the efficiency of C. The method that achieves this is modular programming in C. Modular programming requires a little extra work from the programmer, but pays for itself time and again during maintenance.

Information hiding has a number of benefits, which have influenced the science of programming in the last 20 years. The goal is to hide the implementation details of a data structure while keeping the abstract or logical interface visible. Later I give an example of a stack data structure. The abstract operations that can be applied to a stack are push and pop. There are many methods of implementing a stack (array, linked list, files, etc.). Information hiding teaches that users should be able to push and pop the stack's elements without knowing about the stack's implementation.

A benefit of this sort of information hiding is that users don't have to change their code even if the implementation details change. Consider the following scenario:

1) A software system is being used which has a stack.

2) a hundred routines use the stack.

3) Something happens that requires changing the implementation of the stack.

If the software system employs information hiding properly, the hundred user routines require zero changes. This saves large amounts of money and extends the useful life of the software system.

Saving time and money when changing software is significant. Software lasts longer than hardware. (It should, it costs more.) A software system may live ten or 15 years (look at Quicken, Word, Corel Draw, etc.). During such a system's lifetime, the maintenance changes will consume up to 80% of the total cost.

The Reason for C

The need for information hiding influenced the design of C++ and other object-oriented languages. In a typical C++ implementation, a stack's abstract operations (push and pop) are exposed as public member functions while the actual implementation (say, a linked list) is hidden as private data. Member functions whose signatures (parameter and return types) or existence depend on the implementation will also be made private. Users call the public member functions.

C++, however, is not always the best language for every kind of program. I've recently moved into the world of embedded software systems. In that world, C++ compilers are sometimes unavailable or inefficient. Plain old C is often a much better choice.

The question is how to keep the efficiency of C and still reap the money-saving benefits of information hiding. The answer is modular programming in C. Modular programming looks odd at first and requires a little more of a programmer's time. It is cost effective, however, as it saves money in maintenance.

The remainder of this article contrasts modular programming with the familiar structured programming. As an example, I show partial stack implementations in C, first using structured programming techniques, then using modular programming.

The Structured Programming Approach

Listing 1 shows code fragments that build and exercise a stack. (Full source code files are available from the CUJ ftp site. See p. 3 for downloading instructions.) This is a traditional implementation using pointers and a linked list of structures. The function a_user_function calls the push and pop functions.

Notice how the specific implementation of the data structure is strewn throughout a_user_function. a_user_function must see the definition of the stack_struct to use the push, pop, and other stack functions. Thus the implementation is not hidden, but is mixed with the abstract operations.

The structured programming approach works today, but breaks down tomorrow during maintenance. If the implementation changed from a linked list to an array, a_user_function would need to be changed. This change doesn't cost much when there's just one user function, but is prohibitively expensive if there are dozens of them.

The Modular Programming Approach

Modular programming is based on modules, where "module" can be described as a "poor-man's object." The module provides encapsulation via information hiding. Encapsulation keeps implementation details hidden, and separate, from the abstract operations. Note that this poor-man's object lacks the object-oriented attributes of inheritance or polymorphism.

The key to using modules in C is arranging definitions in multiple include files (see [1]). Figure 1 shows how to do this for the stack example. The user function(s) will include public definitions — which define the abstract operations. The implementation functions will include public as well as private definitions. The private definitions contain the implementation details. Since the definition of the implementation is not included in the user source code files, they are hidden.

Listings 2 through 7 demonstrate the inclusion of files. Listing 2 (spublic.h) shows the public definitions of the abstract data type element and abstract operations push, pop, etc. As shown in Listing 3 (user.c), a_user_function sees these definitions, but knows nothing of the actual implementation.

Listing 4 (sprivate.h) has the private definitions. It declares the implementation-specific data structure stack_struct, and the implementation functions priv_push, priv_pop, etc. Listing 5 shows the implementation source code (stack.c). This source file #includes both the public and private declarations in spublic.h and sprivate.h. This listing also shows how the private or hidden functions perform the abstract operations. These functions use the linked list of structures in a manner similar to the structured example of Listing 1. One difference is that the modular code's pointer to top of stack is a global static variable (defined in Listing 4) . The priv_push, priv_pop, and other private functions access the stack through this variable. Using a global makes it easier to call the private functions.

The modular code involves an extra level of function calls. For example, a_user_function calls push which calls priv_push. The intermediate function call (push) shields the user (a_user_function) from the implementation (priv_push).

The modular code is much easier and cheaper to maintain. Suppose the stack implementation needed to change from a linked list to file storage. Listing 6 shows the new definitions for the data and prototypes for the implementation functions. Listing 7 shows the new implementation code. The file spublic.h and the user's a_user_function do not change. If there were dozens of user functions, none of them would change.

A shortcoming of the modular program in C is its lack of instantiation. In C++, a programmer can define a class and instantiate objects by declaring them. Thus, the C++ program can have as many stacks as desired. The modular C program shown in the listings can have only one stack. A programmer can work around this by making copies of the source code and naming the subroutines pusha, popa, pushb, popb, etc.

Conclusions

Modular programming in C provides the benefits of information hiding. It is not as good as pure object-oriented programming in this regard. There are cases, however, when object-oriented programming is inappropriate. The C++ compilers needed to do object-oriented programming are not always available in embedded systems, and the ones that are available are sometimes too inefficient. Besides, even the strongest proponenets of object-orientation will admit, the object-oriented approach is not best for every problem.

Modular programming involves some redundancy in its extra level of subroutine calls. The arrangement of include files may look like a cute trick, but it works. This use of include files shows the flexibility and utility of the C language.

In the end, modular programming in C works. It provides a method for saving time and money during software maintenance. o

Reference

[1]Steve McConnell. Code Complete (Microsoft Press, 1993).

Dwayne Phillips works as a software, systems, and computer engineer with the United States Government. He has a Ph.D. in Electrical and Computer Engineering from Louisiana State University. His interests include computer vision, artificial intelligence, software engineering, and programming languages.