Best Practices for Writing Safer C Code
Thomas Honold wrote an article published on EETimes giving 17 steps to safer C code. Not only this article provides tips to write safer C code, but I believe those steps are simply best practices when writing C code for embedded systems as they shorten the software life cycle by making it easier for a software team to write, debug and maintain code and by improving the software QA procedure.
Here’s a summary of the 17 steps to achieve safer C code:
- Follow the rules you’ve read a hundred times:
- Initialize variables before use.
- Do not ignore compiler warnings.
- Check return values.
- Use enums as error types.
Define an ENUM_MAX value at the end, so that the code to check the range does not have to be modified each time you add a new error code.
- Expect to fail
Always assume there will be an error and set to default return value to error.
- Check input values: never trust a stranger
Check all input values for consistency at the outmost layer of your software architecture.
- Write once, read many times
Write code to make it readable by others. Declare meaningful variable names and add plenty of comments.
- When in doubt, leave it out
If you are not sure you need a specific function in your API do not add it. If somebody uses it and you later decide it’s not necessary to keep this function, it will break his code.
- Use the right tools
Use an IDE or editor that suits your need (e.g. Ultraedit), build tools (e..g GNU Tools), version control system (e.g. svn, git) and possibly a code style checker such as Artistic Style.
- Define the software requirements first
if you don’t define the requirements, you can’t test your final software properly and can not determine if you have finished your project.
- During boot phase, dump all available versions
If you have several chipset (e.g. FPGA) that each run its own firmware, so when you boot make sure you display all available firmware versions to facilitate debugging and (production) testing.
- Use a software version string for every release
Make you update your software version each time you release it for testing or production. If your version is stored in the version control repository commit it. The best is to have software version string generated automatically for each build.
- Design for reuse: use standards
Do not create types that are already defined in the standard libraries.
- Expose only what is needed
Do not declare variable or functions globally, if they do not need to. It may lead to naming conflicts, make your design non-modular or non-thread safe.
- Make sure you’ve used “volatile” correctly
Use the volatile type for a variable shared by an ISR and any other code, a global variable accessed by two or more RTOS tasks, a pointer to a memory-mapped peripheral register (or register set) or a delay loop counter.
- Don’t start with optimization as the goal
Focus on flexibility first to make it easier to add new features as they are requested and optimize the code for speed or size later if necessary.
- Don’t write complex code
Do not write code that is too complex for readability or maintenance. It’s quite difficult to assess whether a code is complex or not (it’s also subjective), but there are tools to help such as http://www.scitools.com.
- Use a static code checker
You most probably have code rules. make sure you have to script or tools to check those rules or you can be sure they’ll never be checked.
- Myths and sagas
Discard myths such as “you can’t use dynamic memory allocation”, this can be done safely at initialization phase, even though you might have to be careful with malloc during program execution as it can lead to memory fragmentation.
For code examples and more detailed explanations, please refer to the original EETimes article “Seventeen steps to safer C code“