Modular structure of a C program

December 12th, 2009 by Kevin | No Comments | Filed in C Programming
A C program is generally divided into the structure as shown below. This is not a compulsion as such but following this produces a modular structure of the program which is good for:
1) Compilation of the source code. As in this case, we can compile each of the modules separately. In effect, the compiler then compiles only those modules which have changed, effectively reducing the overall compilation time of the program.
2) This follows a modular design. It is much easier to find out problems in the source code when it is structured cleanly. So it helps in the maintainability of the source code. Also it is easier to add functionality to your program if it follows a modular design.

In the above example, we have a program with two source files: foo.c and main.c and one header file: foo.h. We will see what is a header file in details later. But for the basics, a header file contains all the definitions, constants, variables, function declarations, etc which are to be shared between the modules of a C program. As in the above example, the header file foo.h contains a definition MAX and a declaration of a function foo(). The definition of the function foo() is in the source file foo.c.

The main.c and foo.c are compiled separately to create their own .obj(object files). So if we make any change in say just foo.c, then the compiler compiles only foo.c to create foo.obj. But it does not recompile main.c. The compiler has to be told how to do this (Most IDE do this on their own. Else you may have to create makefiles to tell the compiler to do it). If the foo.h file changes, then both the main.c and foo.c have to be recompiled.

The main.c is the main source file of the program. It calls the function foo() from foo.c. However, when we compile the program, the main() is unable to find foo(). To do this, we include the header file foo.h in main.c. Now the compiler understands that the declaration of foo() is present in foo.h. Note that there is still no understanding of the definition of foo(). This is the task of the linker which during the linking process, finds the definition of foo() in the object file created by the compilation of foo.c.

 

Related Posts:

Tags: ,

A C program is a collection of functions

December 11th, 2009 by Kevin | No Comments | Filed in C Programming

A C program generally is a collection of functions. Functions are based on the concept that if we have a large problem, it is wise to divide that problem into smaller units and then solve each unit separately which is supposed to be much easier.

Before a function can be used in a C program, it has to be declared and defined just as a variable (declaring a function is not compulsory but I recommend it as a good programming practice. Because you will have the declarations of all your functions in one place and it will be easier to maintain them).

A function is declared as

 function_name (type , type , ...);

And it is defined as

 function_name (type , type , ...)
{
   return; /* function body */
}

Note that C is not a nested language. So there can be no nesting of one function inside another. The return_value and arguments are optional. Also the return statement in the function body is optional. It is important that the declaration and the definition have the same signature else the compiler will complain.

The example of a C program with function is as below where we convert temperature from celcius to fahrenheit:

int CelciusToFahrenheit(int celcius); /* Declaration of the function */

int CelciusToFahrenheit(int celcius) /* Definition of the function */
{
   int fahrenheit = 0;
   fahrenheit = (9.0/5) * celcius + 32;       /* formula to convert celcius to fahrenheit.
                                                 Note the 9.0 is used for int to float implicit type
                                                 conversion for better precision */
   return fahrenheit;
}

main()
{
   int celcius = 35;
   printf("Temperature in Celcius = %d\n", celcius);
   printf("Temperature in Fahrenheit = %d\n", CelciusToFahrenheit(celcius));     /* Calling the function */
}

The CelciusToFahrenheit() function is shown simplified. A better way to write this function is

int CelciusToFahrenheit(int celcius) /* Definition of the function */
{
  return (9.0/5) * celcius + 32;  /* this shows that the return value can be any valid expression */
}

The return value and the function arguments can be any type i.e int, float, char, etc. The function arguments are call by value and not call by reference. In call by reference, if we modify the function arguments inside the function, the changes are reflected in the actual arguments in the calling code. But in call by value, the function arguments are local to the function and any changes are not reflected back to the actual arguments. This is a good thing because the code is less error prone when the variables are localized. In the above celcius to fahrenheit example, the celcius variable in the main program is different from the celcius argument of the function which is local to the function.

 

Related Posts:

Tags: , ,

Control Flow in C programming

December 10th, 2009 by Kevin | No Comments | Filed in C Programming

By control flow, we mean the ability to make decisions through programming.

If else
The if else statement is used to branch for either of the conditions. If the expression is true then that particular branch will be followed and the other branch will be ignored.

if (expression)
{
   statement 1; /* one direction of the branch */
}else
{
   statement 2; /* other direction of the branch */
}

The else condition is entirely optional. I recommend that if else always be written with the braces {} even for simple statements as a good programming practice even though they can be written without it as

if (expression)
   statement 1;
else
   statement 2;

But what happens when

if (a > b)
   if(c < d)
      printf("Error");
else
   printf("OK");

This is not OK as representation wise the else was supposed to belong to if(a >b) but it ends up paired with if (c < d). So always use braces for control flow statements as it will make your life that much simpler in avoiding unnecessary bugs.

Conditional expression

if (a > b){
   z = a;
}else{
   z = b;
}

can be replaced by the conditional expression

z = (a > b)? a : b;

Here the '?' is the conditional operator. Above statement means the same as the if else block. Many times using a conditional expression leads to more efficient and cleaner code.

The correct way for the above example is

if (a > b){
   if(c < d){
      printf("Error");
   }
}
else{
   printf("OK");
}

If else if
The if else if is an extension of the if else statement.

if (expr1){
   statement1;
   statement2;
   ...
}else if(expr2){
   statement3;
   ...
}else if(expr3){
   statement4;
   ...
}else{
   statement5;
}

It is useful when multiple conditions have to be checked and branching has to be done accordingly. The last else statement acts as a default case when all the previous conditions have failed.

if (a == 1){
   printf("Add");
}else if (a == 2){
   printf("Delete");
}else if (a == 3){
   printf("Update");
}else{
   printf("Exit");
}

Switch
The switch control flow can be used to replace the above if else if statement. It leads to much cleaner code. The only condition is that it can act upon checking integral values only in the conditions.

switch (expr){
case result1:
   statement1;
   ...
case result2:
   statement2;
   ...
case result3:
   statement3;
   ...
default:
   statement4;
}

The expression should result in an integral type. The result will branch into any of the cases specified. It should be noted that the flow will drop through cases by default. Hence a break statement is required to get out of the case. This is better shown by the example below:

switch (a)
{
   case 1:               /* fall through */
   case 2:               /* fall through */
   case 3:               /* fall through */
   case 4:
      printf("Number less than 5");
      break;
   case 5:
      printf("Number is 5");
      break;
   default:
      printf("Number greater than 5");
      break;
}

Care should be taken to mention fall through explicitly to avoid unwanted bugs. Also a default case should be placed to handle "the never will happen" situations which unfortunately in most software quite often "do happen".

While and do while loop

while (expr){
   statement1;
   statement2;
   ...
}

The while loop will continue execution of it's control block till the expression evalutes to false. The do while will also continue till the expression evaluates to false. But the difference is that do while will execute the body of the loop at least once.

do{
   statement1;              /* execution of the loop at least once */
   statement2;
   ...
}while (expr);

The while can also be used for an endless loop as

while (1){
   statement;
   ...
}

The for loop
The for loop is quite useful when the need is to iterate a particular sequence of numbers that we know.

for(initialization; condition; increment)
{
   statement;
   ...
}

for(i=0; i<100; i++)
{
   printf("Number %d\n", i);
}

The for loop above is similar to the while below

i = 0;
while (i < 100){
   printf("Number %d\n", i);
   i++;
}

Break, continue and goto
We have already seen the break statement used in the switch statement. Break can be used to exit from the do, while and for loops as well as from switch.

while (a > 100){
   i++;
   if(i > THRESHOLD){
      break;           /* this will cause the execution to come out of the loop */
   }
}

Continue statement can be used to continue the next sequence of the loop by ignoring the next few statements as shown.

while (a > 100){
   i++;
   if(b == 0){
      continue;        /* after this, execution will return to test the while expression. b-- will not be executed.*/
   }
   b--;
}

Finally we have the dreaded goto statement which can be used to branch anywhere in the function where the label is specified.

while (a > 100){
   i++;
   if (a > i){
      goto err;       /* causes execution to jump out of the loop to the statement following the err label */
   }
}
err:
   printf("error");

Goto is to be avoided being used frequently as it can cause nasty jumps in the code reducing code readability and creating confusion. A particular case where goto is useful is if we have a lot of nesting of the loops and we need to come out of it in case of an error. Break will not work in this case.

for(i=0; i<100; i++){
   for(j=5; j>200; j--){
      if(error condition){
         goto err;
      }
   }
}
err:
   printf("Unable to continue due to error");

 

Related Posts:

Tags: ,