Home > Programming, Software management, Testing > Best Practices for Writing Safer C Code

Best Practices for Writing Safer C Code

September 13th, 2011 Leave a comment Go to comments

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:

  1. Follow the rules you’ve read a hundred times:
    • Initialize variables before use.
    • Do not ignore compiler warnings.
    • Check return values.
  2. 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.
  3. Expect to fail
    Always assume there will be an error and set to default return value to error.
  4. Check input values: never trust a stranger
    Check all input values for consistency at the outmost layer of your software architecture.
  5. Write once, read many times
    Write code to make it readable by others. Declare meaningful variable names and add plenty of comments.
  6. 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.
  7. 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.
  8. 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.
  9. 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.
  10. 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.
  11. Design for reuse: use standards
    Do not create types that are already defined in the standard libraries.
  12. 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.
  13. 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.
  14. 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.
  15. 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.
  16. 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.
  17. 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

Digg This
Reddit This
Stumble Now!
Buzz This
Vote on DZone
Share on Facebook
Bookmark this on Delicious
Kick It on DotNetKicks.com
Share on LinkedIn
Bookmark this on Technorati
Post on Twitter

  1. Bob
    December 9th, 2011 at 04:54 | #1

    Also never allocate memory with Malloc w/o immediately putting in your free() procedures. If you don’t know where to free your memory then you should allocate it in the first place. Another thing is to understand bounds checking to avoid buffer overwrites. Fat pointers are good and ‘guards’ may be even better because they are more portable.

  2. Bob
    December 9th, 2011 at 04:57 | #2

    Also strcpy, strcat, sprtinf, vsprintf, gets and scanf do not perform bound checking (and probably contribute to many exploits) so avoid these.

  3. December 9th, 2011 at 11:32 | #3

    @Bob
    Thanks. Yes, I should have mentioned to use strncpy, strncat, snprintf etc… instead.

  4. December 9th, 2011 at 11:46 | #4

    @Bob
    I had never heard of fat pointers before. It looks like it’s defined as a structure with 3 pointers: lower bound, pointer, higher bound. For those interested, there is a study about this: http://www.andrew.cmu.edu/user/miguels/safec/cpfinal.pdf that explains fat pointers and try to limit the memory overhead and performance hit that may occur with fat pointers.

  1. No trackbacks yet.