The beauty of any code lies not only in finding the solution to a given problem but is in its simplicity, effectiveness, compactness and efficiency( memory ). Designing the code is harder than actually implementing it. Hence every programmer should keep a couple of basic things in mind while programming in C. Here we introduce you to such 10 ways of standardizing your C code.
1. Avoid unwarranted function calls
Consider the following two functions:
1 |
void str_print( char *str ) |
7 |
for ( i = 0; i < strlen ( str ); i++ ) { |
9 |
printf( "%c" ,str[ i ] ); |
1 |
void str_print1 ( char *str ) |
9 |
for ( i = 0; i < len; i++ ) { |
11 |
printf( "%c" ,str[ i ] ); |
Notice the similarity in function of the two functions. However the first function calls the strlen() multiple times whereas the second function only calls
the function strlen() a single time. Thus performance of the first function is obviously better than the second one.
2. Avoid unnecessary memory references
Again lets take a couple more examples to explain this.
1 |
int multiply ( int *num1 , int *num2 ) |
1 |
int multiply1 ( int *num1 , int *num2 ) |
Again these two functions have similar functionality. The difference is there are 5 memory references in the first function ( 1 for reading *num1 , 2 for reading *num2 and 2 for writing to *num1 )whereas in the second function there is only 2 memory references ( one for reading *num2 and one for writing to *num1 ).
Now which one do you think is better of the two?
3. Saving memory used( concept of Memory Alignment and Padding )
Assume that a char takes 1 byte , short takes 2 bytes and int takes 4 bytes of memory. At first we would think that both the structures defined above are the same and hence occupy the same amount of memory. However whereas str_1 occupies 12 bytes the second structure takes only 8 bytes? How is that possible?
Notice in the first structure that 3 different 4 bytes need to be assigned to accomodate the three data types( as we have int declaration between char and short). Whereas in the second structure in the first 4 bytes both char and short can be accomodated hence int can be accomodated in the second 4 bytes boundary( thus a total of 8 bytes ).
4. Use unsigned ints instead of ints if you know the value will never be negative. Some processors can handle unsigned integer arithmetic considerably faster than signed ( this is also good practise, and helps make for self-documenting code).
5. In a logical conditional statement always keep the constant item of the comparison on the left hand side
Using the “=†assignment operator instead of the “==†equality comparison operator is a common typing error we can’t make out until execution. Puttin the constant term on the left hand side will generate a compile-time error, letting you catch your error easily.
Note : ‘=’ is the assignment operator. b = 1 will set the variable b equal to the value 1.
‘==’ is the equality operator. it returns true if the left side is equal to the right side, and returns false otherwise.
6. Whenever possible use typedef instead of macro. Sometimes you just cannot avoid macros but using typedef is better.
Here in the macro definition a is a pointer to an integer whereas b is declared as only an integer. Using typedef both a and b are integer pointers.
In addition, debugging with typedef is more intuitive compared to macros.
7. Always declare and define functions as static unless you expect the function to be called from different files.
Functions that are visible only to other functions in the same file are known as static functions.
It restrict others from accessing the internal functions which we want to hide from outside world. Now we don’t need to create private header files for internal functions.Others don’t see the function and so theyh don’t use them therefore don’t cast those function definition in concrete.
Advantages of declaring a function static include:
a) Two or more static functions with the same name can be used in different files.
b) Compilation overhead is reduced as there is no external symbol processing.
Let’s understand this better with the examples below:
3 |
static int foo ( int a ) |
8. Use memoization to avoid repititious calculation in Recursion
Consider the Fibonacci problem;
The Fibonacci problem can be solved by simple recursive approach:
5 |
if ( n == 0 || n == 1 ) { |
13 |
return fib( n - 2 ) + fib ( n - 1 ); |
Note : Here we are considering the fibonacci series from 1. Thus the series looks like : 1 , 1 , 2 , 3 , 5 , 8 , …
Notice from the recursive tree that we have calculated the fib( 3 ) function 2 times and fib ( 2 ) function 3 times. This is repeated calculation for the same function. If n is extremely large the calculation of the fib( i ) function increases for i < n. A faster way of solving this problem would be to compute the value of the function once , store it in some place and retrieve it whenever needed rather than recomputing it all over again. This simple technique is called memoization which can be used with recursion to enhance the speed of computation.
The memoized code for the above fibonacci function would look something like this:
7 |
for ( i = 0; i <=n; i++ ) { |
17 |
return fib( n , val ); |
21 |
int fib( int n , int* value ) |
25 |
if ( value[ n ] != -1 ) { |
33 |
value[ n ] = fib( n - 2 , value ) + fib ( n - 1 , value ); |
Here the calc_fib( n ) function is called from the main().
9. Avoid dangling pointers and wild pointers
A pointer whose pointing object has been deleted is known as a dangling pointer.
On the other hand, wild pointers are those pointers which are not initialized. Note that wild pointers do not point any specific memory location.
1 |
void dangling_example() |
5 |
int *dp = malloc ( sizeof ( int )); |
The program usually shows weird behaviour when these pointers are encountered.
10. Always remember to free whatever memory you have allocated in your program. Notice in the example above how we freed the dp pointer which we allocated using the malloc() function call.
Source : http://www.fortystones.com/tips-to-make-c-program-effective/