RSS Feed

Safe Coding in C

Coding has to implement the requirements and definitions of the architecture and module design to achieve the desired functionality of the system. However, it is not enough to achieve the functionality. The software also has to comply to other demands, like robustness, maintainability, stability under a wide range of operating conditions and most important of all, the number of faults in the software has to be reduced to a minimum. Many of these items are not only a property of the module design. Good coding of the software is a crucial piece in the puzzle. But how can good coding be achieved? Here are a few rules (which are not complete):

 

Templates and Pattern

If you are developing software and the software is bigger than just one module you better define templates for header files and source code modules. This is especially important if more than just one software developer is working on the project. Other developers, testers, etc. have to find their way through the code easily. The following features should be included in a template:

  1. A descriptive comment block at the top of each module and header file. The name of the file, the author, creation date, a description of the purpose of the file, a copyright text and an update history should be the minimum content of this block.

  2. Defined sections where you group certain coding elements like a section for includes, a section for data type definitions, a section for macros, a section for function prototypes, etc. Never allow e.g. includes or variable definitions to be scattered all over the module. There is absolutely no reason why they could not be all somewhere at the top of the file.

  3. A descriptive comment block before each C-function. This should contain a short description of the function, its purpose, things to observe, etc. It could contain the name of the autor, in case there are more authors working on the same module, some words about the interfaces of the function etc. etc. Besides the information in this block it consitutes an optical breakpoint to spot functions when scrolling through the file.

  4. Defined sections inside the functions, e.g. where local variables are defined, where variables are initialized, etc.

  5. Structure all of your modules in the same way. E.g. group all the functions in your module which are used from outside in one section, and have all "private" functions which are only used inside the module itself in another section. Further, have the init function which initialized all module related data always at the same place, e.g. at the end of the module.

The above list was alread a pattern of a module. But also your coding lines should use repeated pattern of approved quality, rather than doing the same things different at the various places in your code. Of course there are many places where pattern can be applied. Here are just a few examples to illustrate it:

 

Naming Conventions

You have to set up naming conventions for your variables, function names and other elements of a program. If applied consequently, this will help you to program, maintain, test and inspect your software. The following elements are recommended to be part of the naming:

  1. Define fixed elements in your names and their location in the names. Separate them with underscores.

  2. A short acronym for the modulename should be a prefix for every element of module wide and global validity. E.g. if your module has to do with aquiring inputs, all functions should have an acronym called IN_ in front of their name. Thus the origin of functions and variables are clear and unique. Anyway if you have for example an initialization function in every module you have to name them somehow uniquely. In this example the initialization function of the module could be called IN_Init.

  3. Make the data types of constants and variables, as well as the return value of functions part of the name. To do this you have to define acronyms for the used data types, e.g. us_ for unsigned short, uc_ for unsigned char etc. etc. In our example the initialization function would be then defined as: void IN_v_Init(void). As you can see the return data type of the function is void and this is represented as the second element of the name by the element v_. It is up to your taste how you arrange these elements. The data type could also be at the end of the name, or right at the beginning. It is also up to your taste if you use lower case or upper case acronyms. But in any case, this information makes the inspection and change of a program much easier. The actual definition of a variable may be at some distant spot in the code and by having the information at every code line you avoid searching for this information when you want to change or check the code.

  4. Have a descriptive element as part of your name. You can use upper and lower case characters e.g. to separate naming portions in the descriptive element. In our example the Init would be the descriptive element. To have a further example: KbdInput could be the descriptive element for a variable holding the inputs from a keyboard.

  5. Use suffixes for variables of the same kind, holding older values. E.g. _1 at the end of a variable called IN_uc_KeyInput_1 denotes that the variable holds the keyboard input of the previous execution cycle, whereas the variable IN_uc_KeyInput would hold the current input.

Make a naming convention for every element, from the module name down to the local variables. A good naming convention helps you a lot. Define it and apply it. It has to be subjec to code inspections that the convention is adhered to.

 

Coding Guidelines

Basically there are 5 areas which are very important and which a coding guideline should cover. Clear rules have to be set up to make sure that the pitfalls of these areas will be avoided. And finally you have to check in your inspections if these items in the guideline are adhered to. We can only give a small outline here and one or two examples for each of the items, although there is a vast number of pitfalls:

  1. Data types can generate a number of problems, if they are used mixed. There is the danger of truncation if a larger data type is assigned to a smaller one, there is the danger of sign mismatches if a signed value is assigned to an unsigned value and there is the danger of overflows if an unsigned value is assigned to a signed value of the same bit size of the element. Of course the problem does not only arise in assignment of values, but also if values are compared or used in calculations.

  2. The precedence of operators in the C-programming language is defined, but not always clear to most of the programmers and, at some places different than expected. An example can illustrate this: d = a / b * c; is a mathematical formula which can be put like this in any C-program. If you are using floating point variables there is no problem at all. However, according to the rules of precedence in C the compiler does the division before the multiplication. Using integer arithmetic and using some numbers instead of the variables the problem is clearly seen. d = 1 / 2 * 50; would result in d = 0; although you would expect the result to be 25. This is because 1 / 2 is done first and results in 0 in an integer calculation. Then the 0 is multiplied with 50 with results still in 0. You have to do the multiplication first and then the division to get correct results. This has to be addressed in a coding guideline!

  3. The order of evaluation in a C-program can be compiler dependent. Imagine a line like this: x[i] = a + i++; The sequence point in our example program line is the semicolon. Only after reaching the sequence point the variable contents are defined. This means that everything between two sequence points is undefined and entirely up to the implementation (compiler). In the named example let's imagine the value of i = 2; before the example line is executed. A valid result could be either x[2] = a + 3; if the i++ operation is done after the indexing of x, or it could be also x[3] = a + 3; if the indexing of x is done after the increment of i. Both would be o.k. from the C-standard point of view. But the first variant would be different to what most programmers would expect of this line. There are some other similar pitfalls. You have to make rules related to these pitfalls in a guideline, to point them out and give advice how to avoid them.

  4. Floating point operations can be a bit tricky if you do something like if (a == b) using floating point variables. They most likely will rarely ever match and fulfill the condition, although they would be close enough to a match in many more cases. In this case you have to check if the variables are close to each other within a certain range. However this and some other floating point pitfalls has to be addressed by a coding guideline.

  5. Name spaces can be also a problem. There are 5 name spaces in standard C as can be seen in the following example using the variable named x:

    unsigned char function1(int x); //namespace 1
    struct x {short x;} st1;        //namespace 2 and 3
    void function2(void)
    {
      unsigned int x;               //namespace 4

      goto x;                       //namespace 5
      x: x= st1.x;
    }

    This is a perfectly valid C-program, but you can imagine that if it has some more lines, nobody will be sure what x you are addressing when using it.

Our recommendation is that you set up a coding guideline where you address these pitfalls and items and define pattern to deal with these cases. Train your programmers to know the guideline and adhere to it. After that you have to do code inspections on the code by a separate instance (the 4 eyes principle) to check the compliance of your code with the guideline. We will gladly assist you in setting up your guidelines, training your programmers and doing code inspections.



  

Imprint