UNIT 3: FUNCTIONS AND STORAGE
3.1 FUNCTIONS
A function is a block of code that performs a specific task.
Suppose, a program related to graphics needs to create a circle and color it depending upon the radius and color from the user. You can create two functions to solve this problem:
- create a circle function
- color function
Dividing complex problem into small components makes program easy to understand and use.
Types of functions in C programming
Depending on whether a function is defined by the user or already included in C compilers, there are two types of functions in C programming
There are two types of functions in C programming:
Standard library functions
The standard library functions are built-in functions in C programming to handle tasks such as mathematical computations, I/O processing, string handling etc.
These functions are defined in the header file. When you include the header file, these functions are available for use. For example:
The printf() is a standard library function to send formatted output to the screen (display output on the screen). This function is defined in “stdio.h” header file.
There are other numerous library functions defined under “stdio.h”, such as scanf(), fprintf(), getchar() etc. Once you include “stdio.h” in your program, all these functions are available for use.
Visit this page to learn more about standard library functions in C programming.
User-defined functions
As mentioned earlier, C allow programmers to define functions. Such functions created by the user are called user-defined functions.
Depending upon the complexity and requirement of the program, you can create as many user-defined functions as you want.
How user-defined function works?
#include <stdio.h>
void functionName()
{
… .. …
… .. …
}
int main()
{
… .. …
… .. …
functionName();
… .. …
… .. …
}
The execution of a C program begins from the main() function.
When the compiler encounters functionName(); inside the main function, control of the program jumps to
void functionName()
And, the compiler starts executing the codes inside the user-defined function.
The control of the program jumps to statement next to functionName(); once all the codes inside the function definition are executed.
3.2 DEFINITION
A function definition associates the function body (a sequence of declarations and statements) with the function name and parameter list. Unlike function declaration, function definitions are allowed at file scope only (there are no nested functions).
C supports two different forms of function definitions:
- specifiers-and-qualifiers parameter-list-declarator function-body
- specifiers-and-qualifiers identifier-list-declarator declaration-list function-body
where
specifiers-and-qualifiers | a combination of
|
parameter-list-declarator | a declarator for a function type which uses a parameter list to designate function parameters |
identifier-list-declarator | a declarator for a function type which uses a identifier list to designate function parameters |
declaration-list | sequence of declarations that declare every identifier in identifier-list-declarator. These declarations cannot use initializers and the only storage-class specifier allowed is register. |
function-body | a compound statement, that is a brace-enclosed sequence of declarations and statements, that is executed whenever this function is called |
New-style (C89) function definition.
This definition both introduces the function itself and serves as a function prototype for any future function call expressions, forcing conversions from argument expressions to the declared parameter types.
int max(int a, int b)
{
return a>b?a:b;
}
double g(void)
{
return 0.1;
}
Old-style (K&R) function definition.
This definition does not behave as a prototype and any future function call expressions will perform default argument promotions.
int max(a, b)
int a, b;
{
return a>b?a:b;
}
double g()
{
return 0.1;
}
3.3 PROTOTYPES
It is now considered good form to use function prototypes for all functions in your program. A prototype declares the function name, its parameters, and its return type to the rest of the program prior to the function’s actual declaration. To understand why function prototypes are useful, enter the following code and run it:
#include <stdio.h>
void main()
{
printf(“%d\n”,add(3));
}
int add(int i, int j)
{
return i+j;
}
This code compiles on many compilers without giving you a warning, even though add expects two parameters but receives only one. It works because many C compilers do not check for parameter matching either in type or count. You can waste an enormous amount of time debugging code in which you are simply passing one too many or too few parameters by mistake. The above code compiles properly, but it produces the wrong answer.
To solve this problem, C lets you place function prototypes at the beginning of (actually, anywhere in) a program. If you do so, C checks the types and counts of all parameter lists. Try compiling the following:
#include <stdio.h>
int add (int,int); /* function prototype for add */
void main()
{
printf(“%d\n”,add(3));
}
int add(int i, int j)
{
return i+j;
}
The prototype causes the compiler to flag an error on the printf statement.
Place one prototype for each function at the beginning of your program. They can save you a great deal of debugging time, and they also solve the problem you get when you compile with functions that you use before they are declared. For example, the following code will not compile:
#include <stdio.h>
void main()
{
printf(“%d\n”,add(3));
}
float add(int i, int j)
{
return i+j;
}
3.4 PASSING ARGUMENTS
You can pass data to functions so they can work on that data. For example, you can create a function named adder() that you want to add two integers and display the results.
To indicate which arguments a function takes, you include an argument list in the parentheses following the function name when you define the function. For example, the adder()function takes two arguments: the two integers to add, which we’ll name x and y:
void adder(int x, int y)
Now in the body of the function, you can refer to the first argument as x and the second argument as y.
When you create a function prototype, on the other hand (when you call the function before defining it in your code), you omit the names of the arguments, instead including just the type:
void adder(int, int);
Now you can write the body of the adder() function to add the two integers, which you can refer to by name, x and y:
void adder(int x, int y)
{
printf(“%i + %i = %i”, x, y, x + y);
}
Program
#include <stdio.h>
void adder(int x, int y)
{
printf(“%i + %i = %i”, x, y, x + y);
}
Program
#include <stdio.h>
void adder(int x, int y)
{
printf(“%i + %i = %i”, x, y, x + y);
}
int main()
{
int value1 = 5, value2 = 10;
adder(value1, value2);
return 0;
}
3.5 RECURSIONS
A function that calls itself is known as a recursive function. And, this technique is known as recursion.
How recursion works?
void recurse()
{
… .. …
recurse();
… .. …
}
int main()
{
… .. …
recurse();
… .. …
}
The recursion continues until some condition is met to prevent it.
To prevent infinite recursion, if…else statement (or similar approach) can be used where one branch makes the recursive call and other doesn’t.
Example: Sum of Natural Numbers Using Recursion
#include <stdio.h>
int sum(int n);
int main()
{
int number, result;
printf(“Enter a positive integer: “);
scanf(“%d”, &number);
result = sum(number);
printf(“sum = %d”, result);
return 0;
}
int sum(int num)
{
if (num!=0)
return num + sum(num-1); // sum() function calls itself
else
return num;
}
Output
Enter a positive integer:3
sum = 6
Initially, the sum() is called from the main() function with number passed as an argument.
Suppose, the value of num is 3 initially. During next function call, 2 is passed to the sum() function. This process continues until num is equal to 0.
When num is equal to 0, the if condition fails and the else part is executed returning the sum of integers to the main() function.
Advantages and Disadvantages of Recursion
Recursion makes program elegant and cleaner. All algorithms can be defined recursively which makes it easier to visualize and prove.
If the speed of the program is vital then, you should avoid using recursion. Recursions use more memory and are generally slow. Instead, you can use loop.
3.6 STORAGE CLASSES
A storage class defines the scope (visibility) and life-time of variables and/or functions within a C Program. They precede the type that they modify. We have four different storage classes in a C program −
- auto
- register
- static
- extern
3.7 AUTOMATIC
The auto Storage Class
The auto storage class is the default storage class for all local variables.
{
int mount;
auto int month;
}
The example above defines two variables with in the same storage class. ‘auto’ can only be used within functions, i.e., local variables.
3.8 EXTERNAL
The extern Storage Class
The extern storage class is used to give a reference of a global variable that is visible to ALL the program files. When you use ‘extern’, the variable cannot be initialized however, it points the variable name at a storage location that has been previously defined.
When you have multiple files and you define a global variable or function, which will also be used in other files, then extern will be used in another file to provide the reference of defined variable or function. Just for understanding, extern is used to declare a global variable or function in another file.
The extern modifier is most commonly used when there are two or more files sharing the same global variables or functions as explained below.
First File: main.c
#include <stdio.h>
int count ;
extern void write_extern();
main() {
count = 5;
write_extern();
}
Second File: support.c
#include <stdio.h>
extern int count;
void write_extern(void) {
printf(“count is %d\n”, count);
}
Here, extern is being used to declare count in the second file, where as it has its definition in the first file, main.c. Now, compile these two files as follows –
$gcc main.c support.c
It will produce the executable program a.out. When this program is executed, it produces the following result –
count is 5
3.9 STATIC
The static Storage Class
The static storage class instructs the compiler to keep a local variable in existence during the life-time of the program instead of creating and destroying it each time it comes into and goes out of scope. Therefore, making local variables static allows them to maintain their values between function calls.
The static modifier may also be applied to global variables. When this is done, it causes that variable’s scope to be restricted to the file in which it is declared.
In C programming, when static is used on a global variable, it causes only one copy of that member to be shared by all the objects of its class.
#include <stdio.h>
/* function declaration */
void func(void);
static int count = 5; /* global variable */
main() {
while(count–) {
func();
}
return 0;
}
/* function definition */
void func( void ) {
static int i = 5; /* local static variable */
i++;
printf(“i is %d and count is %d\n”, i, count);
}
When the above code is compiled and executed, it produces the following result –
i is 6 and count is 4
i is 7 and count is 3
i is 8 and count is 2
i is 9 and count is 1
i is 10 and count is 0
3.10 REGISTER VARIABLES
The register Storage Class
The register storage class is used to define local variables that should be stored in a register instead of RAM. This means that the variable has a maximum size equal to the register size (usually one word) and can’t have the unary ‘&’ operator applied to it (as it does not have a memory location).
{
register int miles;
}
The register should only be used for variables that require quick access such as counters. It should also be noted that defining ‘register’ does not mean that the variable will be stored in a register. It means that it MIGHT be stored in a register depending on hardware and implementation restrictions.
Register variables are a special case of automatic variables. Automatic variables are allocated storage in the memory of the computer; however, for most computers, accessing data in memory is considerably slower than processing in the CPU. These computers often have small amounts of storage within the CPU itself where data can be stored and accessed quickly. These storage cells are called registers.
Normally, the compiler determines what data is to be stored in the registers of the CPU at what times. However, the C language provides the storage class register so that the programmer can “suggest” to the compiler that particular automatic variables should be allocated to CPU registers, if possible. Thus, register variables provide a certain control over efficiency of program execution. Variables which are used repeatedly or whose access times are critical, may be declared to be of storage class register.
3.11 MULTI-FILE PROGRAMS
In a program consisting of many different functions, it is often convenient to place each function in an individual file, and then use the make utility to compile each file separately and link them together to produce an executable.
There are a few common-sense rules associated with multi-file programs. Since a given file is initially compiled separately from the rest of the program, all symbolic constants which appear in that file must be defined at its start. Likewise, all referenced library functions must be accompanied by the appropriate references to header files. Also, any referenced user-defined functions must have their prototypes at the start of the file. Finally, all global variables used in the file must be declared at its start. This usually means that definitions for common symbolic constants, header files for common library functions, prototypes for common user-defined functions, and declarations for common global variables will appear in multiple files. Note that a given global variable can only be initialized in one of its declaration statements, which is regarded as the true declaration of that variable [conventionally, the true declaration appears in the file containing the function main()]. Indeed, the other declarations, which we shall term definitions, must be preceded by the keyword extern to distinguish them from the true declaration.
As an example, let us take the program printfact4.c, listed previously, and break it up into multiple files, each containing a single function. The files in question are called main.c and factorial.c. The listings of the two files which make up the program are as follows:
/* main.c */
/*
Program to print factorials of all integers
between 0 and 20
*/
#include <stdio.h>
/* Prototype for fucntion factorial() */
void factorial();
/* Global variable declarations */
int j;
double fact;
int main()
{
/* Print factorials of all integers between 0 and 20 */
for (j = 0; j <= 20; ++j)
{
factorial();
printf(“j = %3d factorial(j) = %12.3e\n”, j, fact);
}
return 0;
}
and
/* factorial.c */
/*
Function to evaluate factorial (in floating point form)
of non-negative integer j. Result stored in variable fact.
*/
#include <stdio.h>
#include <stdlib.h>
/* Global variable definitions */
extern int j;
extern double fact;
void factorial()
{
int count;
/* Abort if j is negative integer */
if (j < 0)
{
printf(“\nError: factorial of negative integer not defined\n”);
exit(1);
}
/* Calculate factorial */
for (count = j, fact = 1.; count > 0; –count) fact *= (double) count;
return;
}