Monday, September 28, 2015

Writing Robust Embedded-C Code

Writing Robust Embedded-C Code
(Author: Rohit Sivakumar)


1.    Always use snprintf instead of *sprintf. Using sprintf causes buffer overruns if the destination size is less than the source size resulting in segmentation faults due to buffer overflows and missed ‘\0’  (NULL) character termination.
           
            char arr[3];
sprintf(arr, "%s:%s", "Test", b);  //causes segmentation fault

*Same point holds true for some string function and hence use strncpy and strncat

2.    When using snprintf, ensure to force-terminate the end of buffer by a NULL termination.

char arr[4];
//causes arr to hold smaller string without NULL termination causing faults
snprintf(arr, sizeof(arr), "%s:%s", "Test", “1” );

INSTEAD use it as:

char arr[4] ;
snprintf(arr, sizeof(arr), "%s:%s", "Test", “1” );
arr[sizeof(arr)-1] = ‘\0’; //Force-terminate the last char with a NULL.


3.    Never leave dangling pointers in your code. After you finish using a dynamic pointer, always free it and assign it to NULL to avoid accidental freeing up again.
            Remember: You never free up a pointer on stack; you free it up only on Heap.
char * p;
p= (char*)malloc(sizeof(char));
....          //some use of arr dynamic pointer
free(p) ;     //usage of dynamic pointer is over so freeing up
p= NULL;      //Always assign the freed pointer to NULL.

//else-where in the code trying to again accidentally do free(p)
free(p);      //Ensures freeing up p again won’t crash the system.

Accessing a pointer to a deleted object results in an access violation exception and hence assigning it to NULL ensures full safety to such a situation.




4.    Never forget the NULL character when assuming string array sizes or when computing the length of the string.
char arr[]= “Rohit”;
n = strlen(arr); //returns only 5 by excluding the terminating NULL char.

5.    Never use non-reentrant functions (generally string lib functions like strtok(...) in a recursive looping constructs like ISRs.  A non-reentrant function remembers a local copy of its action. Hence when non-reentrant functions are again called in ISRs, it will lose the correctness of its operation.

6.    Always use correct data-types when dealing with signed and unsigned specifiers . A signed number range is different from an unsigned range.
            eg: signed char range is -127 to +128
                  unsigned char range is: 0 to 255.
                  Remember: 0 to 255 with 0 inclusive in count is 256. 0xFF is not 256.
                 Unsigned integers produce faster C code.

7.    Correct way to initialize all the elements of structure to zero (0) is:
            Correct method:
struct strSchool = {{0}};

       Incorrect method:
struct strSchool;
memset(&strSchool, 0, sizeof(strSchool));

8.    Ensure there are no left-overs in your code. This means:
a.    Your code must be clean of warnings
b.    Your code must be well-commented
c.    Your code must be free-of  commented/non-used variables/c-code.
d.    If you need code that is to included for debugging then don’t leave it commented in the release mode instead use #define DEBUG preprocessor.

9.    If your variables are global and shared between threads or ISRs or main and ISRs, ensure to use the specifier “volatile”.

10.  Always initialize a local variable to avoid program crashes. It is optional for a global variable to remain uninitialized as it is automatically equated to zero.
int m;  //if m was global then it is automatically equated to 0. So m=0;
int m;  //if m was local then it has garbage value unless initialized.

11.  When working with dynamic pointers, always perform a NULL check.
int * pInt;
pInt = (int*) malloc(sizeof(int)*10);
if(pInt != NULL)
{
  . . .
  //do what your business logic needs.
}
else
{
  //we have an error terminate from here.
}

12. It is wrong to assume that a return value of a bool function(...) is always true! It is a common mistake the programmers do to assume that the function always returns true and never check the return value before proceeding to the next step whose outcome is dependent on the return value of the called bool function.

13.  Use assert(...) very carefully. assert(...) is used in debug mode for checking pre-conditions and post-conditions. When a program is compiled in release mode, asserts are removed in the pre-processing stage. 
So it is incorrect to do this:
                                                int * pInt;
                           pInt = (int*) malloc(sizeof(int));
                           assert(pInt != NULL); //Available in debug mode only.
                           pInt = 10;
                           ....
       Correct way to do this:
                                                int * pInt;
                           pInt = (int*) malloc(sizeof(int));
                           assert(pInt != NULL); //This code gets removed in release.
                           if(pInt == NULL)
                             return ERROR
                           else
                                  pInt = 10;
                           ....

14. If your variables and functions are only to be used in one-specific file then ensure you add the specifier “static” to limit its scope to that file. 
a.    You don’t want to leave things as global. They may seem very good to be like that, accessible from any-where but are the most common cause of errors in a robust code.
15.  Don’t use magic numbers as far as possible. 
a.    Instead use const <data-type> specifier if you want to fix the value as a constant and also take advantage of compiler’s type-checking feature.
const int nLicenses = 50; //Fixed magic number with a name

b.    If you want to only use a meaningful name and not the advantage specified in a above then use preprocessor directive : #define. If you are using #defines then ensure to name in CAPITAL for easy readability.
                        #define TOTAL_LICENSES (50) //Fixed magic number with a name


16.  Compare constants in if loop constructs by keeping the constants on  left-side to ensure robustness and to free up from = vs == errors!
Eg: if(x=10) or if (x==10)  //both compile to be correct.
                            //Did you mean to compare or equate?
Suggested Method:  if(10 == x) 

//You can never equate a variable to a number so compiler knows and lets you only compare equality.