C Language

To share this page click on the buttons below;

More about C source files

What we saw with our first program is a very common situation: usually in your program you will use functions contained in libraries written by someone else (the C standard library is just the first example, another one is some graphical library providing widgets to develop graphical applications). Those libraries usually come in a binary form. The binary form was the common choice some years ago when the concept of open source was not so common in the software development: this was a way to protect the intellectual property of the underlying code (you can use the library but you cannot see how the library is implemented, nor modifying it). In any case the binary form is handy: many libraries are huge, they can be difficult to compile in the right way and the compilation may take a long time. Besides when you have a library compiled and well tested that is just what you need to develop your programs that use that library.

When we write a code that uses library functions it is necessary to have a mechanism to allow our code be aware of the functions contained into the library. We already had a little taste of that with our first program: this mechanism is provided by header files. So now we can understand why C uses 2 different kind of source files and why variables and functions have declaration and definition.

Header files (.h) contains the declaration of functions and variables. Do you remember? The declaration is just a promise: somewhere else those variables will be defined, in other words header files contain the description of something that is implemented somewhere else. Where? Well in the source .c files that have been translated in the binary form of the library.

Why am I spending many words about this mechanism? Because, in my opinion, is the correct way to think about the architecture of a C program. A good way to start thinking about the architecture of a well designed C program is to think it as a collection of modules, each one providing some related functions to other modules (each one is a sort of little library). As it happens for libraries the actual implementation of a module should be unimportant for other ones as far as the functions provided by the module are working correctly (same functions can implemented in many different ways).

Modules

Very often a C module is designed as a pair of a source C file and a header C (at least that is the simplest implementation of a C module). Although this is not strictly required (it is in any case highly recommended) this 2 files have the same name (and obviously different extensions). I am not sure about the fact that module is a commonly accepted terminology but here we will use that word to refer to a set of related functions, grouped togheter, that provide some services to other parts of a program.

For example if you write a program that communicate over a serial line you can decide to wrap all the functions and the variables which are related to this task in the files serial.c and serial.h. A little advise: please give your modules a meaningful name which may help the reader to predict what the module does. Utils is a perfectly valid name but it is completely unsatisfactory to determine which kind of utility the module functions might provide.

The are are no rules about how to separate functions and variables between different modules and the granularity of your division, only experience will teach you that some choices are better than others and will help you to define the proper architecture of your program.

A good starting point in defining your architecture is to think about a module as something that handles functions related to a common task and that provides some service to other modules. Each module, in other words, implements a set of functions, related to a common purpose, that other modules can use.

Let's continue with the serial communication example: your serial module will be perhaps responsible to pack together the information you want to transmit and maybe it will receive a confirmation that the data sent have been correctly received and it will probably implement a mechanism that checks the correctness of the data (transmitted and received) and it will deal with errors that might occur and so on. The serial module, in other words, will deal with all the details related to the common purpose of handling serial communication. Nevertheless if you think about serial communication as a service that the module will provide to others you may think that all those details are unimportant, other modules will probably only want to transmit something and receive something and perhaps to know if the result of the communication is good or not: so your module will have to expose to others modules just these three functions.

The source file will contain the real implementation (the definition) of all the functions and the variable the module uses, the header file will export only the information that other modules need to know about how to proper use the functions that module is providing (the declaration).

The header file will contain all the declarations of the functions that the module will supply to other modules. In the source file you will write the definition of all the functions your module implements, both the ones it has to export (and that will be used by other modules) and the ones it will use internally to perform all the task it is designed for. Here is an interesting point: the module will (can) contain much more functions respect to the ones declared in the corrisponding header. Those functions are private to the module, the other modules does not know about their existence. So that each module manages 2 set of functions: public ones can be called by other modules to use the services provided by the module, private ones are hidded inside the module and usually makes all the actual work the module is designed to handle.

That is a very powerful way to think about a program architecture because it separates the code in different, independent, reusable modules:

  • it forces to group related functions into a single container and so it forces to make a logical separation of the different functions the program is made of
  • it forces to think a module as set of public functions the module makes available to other one and a set of private ones the other module cannot see
  • it suggests to design modules you can re-use in different projects.

In particular that separation between public and private functions is important because it draws a line between what you can easily change in the module (the private functions) and what is very hard to change (the public functions). Private functions are internally used just by the module itself, so that the changes are restricted to the module itself, public functions are used by other modules so that a change in those functions leads to a change in every module that uses that functions.

The declaration of the private functions is usually placed into the .c file itself at the very beginning of the file: this helps the reader to understand what is inside the module itself. It is a good programming practice to do.

To share this page click on the buttons below;