The concept of functions in C language is the same as for any other programming language. Few languages have methods but, since there are no classes in C, methods are not used.
Functions are best used for reusable code, or code that you know works and do want make use of it throughout development. If you find yourself tempted to use copy and paste on a particular block of code, even once, then you should move the code into a function.
Lets talk about the different types of variables before diving into functions as it is a bit of a prerequisite.
Automatic Variables
You can go forever without ever declaring a variable automatic, the reason being that all variables and functions are implicitly automatic. The technical meaning of automatic variable or function means that memory will automatically allocated for it by the compiler and will be destroyed automatically after it is no longer within scope.
auto int var = 0;
int var = 0;
Are exactly the same.
When programming in C, you should initialize the value of any variable before using it as early on as possible. It does not do this for you as many of the newer languages do.
External Variables
As the name implies, external variables are variables declared someplace that would cause a compiler error when used if not for the extern keyword. You probably wonder why this is important, it will become apparent when we get to header files.
For now, know that there will be times when a variable needs to have a program-wide (global) scope and in order to allow other parts of the program to access it, it must be declared with the extern keyword.
extern int GLOBAL_VARIABLE;
This lets the compiler know that it is okay for other parts of the program to access this variable’s value. You now just use it as you would any local variable.
This type of variable comes in very handy for embedded system coding where you want to reuse variables as much as possible for memory efficiency.
Static Variables
Here is a variable that no embedded application or operating system level application can live without. The static keyword in a variable’s declaration means that the variable will be available throughout the lifetime of the entire application execution. So you can write interrupt driven code (common for system and embedded applications) without worrying that the variable will lose its current value or be unavailable if the program receives an interrupt and executes a different procedure that the one currently executing. When the interrupt procedure returns, your variable will still be available and have the value it had before the interrupt was called.
Imagine you have the following declaration in a program.
static int count = 0; </p>
<p>int interrupt_func() {
return count++;
}
The first line declares a static integer for counting something. The interrupt function could now be called at any time throughout the execution of the program to increment the value of count. You can feel confident when calling the interrupt function again that it will have the same value as it did after the last time it was called. Of course this is assuming the only part of the program that ever changes the value is in that interrupt function.
There is another use for the static keyword in C. It keeps the variable local to the current source file. What this means for you is that you can not declare a variable as both extern and static. Large C programs are usually made up of many different source files and that is where this use for the static keyword comes in handy.
So this would not compile:
extern static int count = 0;
You must choose one or the other.
Register Variables
You could use the register keyword on variables that you think may be used often throughout your program. However, you are most likely wasting your time doing this because the compiler will ignore it if it thinks it can do a better job with optimization. It is probably right in thinking this too.
Register is a seldom used declaration, in fact I have never seen it in practice. I do not recommend trying to use it since compilers are pretty efficient these days. Knowing it exists is more than sufficient.
Declaring Functions
Functions are declared similar to regular variables.
<data type> function_name();
As you can see, the only difference is the parenthesis after the name of the function. However, a declaration is not enough. A function must also have an implementation. To completely declare a function you have to have the following:
<data type> function_name();
<data type> function_name() {
//some code here
}
You can combine them by just having the implementation, but it is not good programming practice and would never be used in any professional code. But you should be aware because tutorials do it sometimes for brevity.
Scope and Lifetime of Variables in Functions
A variable’s scope can be either global or local. Global scope means that a variable may be referenced anyplace in the entire program and local means it can only be referenced in the current block of code being executed. A global variable will also be available as long as the program is executing.
We have already introduced global variables and told you to avoid them if at all possible, and now we shall explain why. When you use a global variable, it can be changed anywhere in a program at any time and by anyone. When working on teams this can be very troublesome if somebody names a variable the same name in a part of the program that they are writing.
Take a look what happens when we take our first printxy example and add a local variable x inside of main while still keeping the global x variable. Notice the changes we made in bold.
printxy4.c:
#include <stdio.h>
//global variables. int x = 2; int y;
//Function for printing variable's values.
void printxy();
void main() {
int x;
x = 10;
y = 5;
printxy();
x += y;
printxy();
y++;
printxy();
}
void printxy() { printf("value of x: %d, ", x); printf("value of y: %d", y); printf("n");
}
This is the output:
Notice x now equals 2 across the board instead of the 10, 15, 15 we expected it to have. This is all caused by simply adding another int x declaration inside of main. Even though we clearly set the value of x to 10 to start. Why is it using the global variable x for each of the print statements instead of our local one?
The reason is that global variables get precedence over local ones. If you declare a global variable and try to create a local variable someplace that has the same name as the global one, you will drive yourself crazy trying to debug the problem. It will never be the value you expect it to be. Keep your variables as small in scope as possible and you should be fine.
I take it you have deduced that local scope means that a variable can only be referenced by the current block of code. You are correct if that is the case. All you need to remember is: if you declare a variable inside a function, it may only be used within the confines of that function. One reason for having functions that can return data, is so it can share the data from its local variables with the caller.
Unless a local variable is declared as static, it will no longer be available once the function stops executing. This is another reason why functions need to be able to return data.
Functions and Arrays
We know how to pass regular variables to and from functions, but what about arrays of data? It is actually no harder than passing a singular variable.
To allow passing of an array to a function as an argument, you must add [] to the parameter list in the function’s prototype. For example,
void function(int array[]);
Declares a function that takes an integer array as an argument.
Let us change our printxy example to allow x and y to be arrays, to show just how little needed to be changed.
printxy5.c:
#include <stdio.h>
#include <stdlib.h>
//Function for printing variable's values. 2 arguments, no return value.
void printxy(int x[], int y[]);
int main(int argc, char *argv[]) {
int x[3], y[3]; int i;
for (i = 0; i < 3; i++) { x[i] = i; y[i] = 10;
}
printxy(x, y);
for (i = 0; i < 3; i++) { x[i] += y[i];
}
printxy(x, y);
for (i = 0; i < 3; i++) { y[i]++;
} printxy(x, y);
return 0;
}
void printxy(int x[], int y[]) {
int i = 0;
for (i = 0; i < 3; i++) { printf("value of x: %d, ", x[i]); printf("value of y: %d", y[i]); printf("n");
}
}
Notice how the call to the printxy function did not even have to be changed. All we did was add brackets to all x and y references and for loops to loop through each value in the arrays. All the operations are the same as before they just use 3 element arrays rather than regular integers now.
The output of printxy5 when executed should look like the following screenshot.
Functions cannot technically return arrays. They can, but they do so using pointers. Explanation of arrays relation to pointers is covered in the pointers tutorial. If you need to learn about this right away, you should see the pointers tutorial.
Recursion
Recursion is a powerful tool that can really simplify your code if you find that you have a problem that can be solved by using it. A recursive function is one that calls itself one or more times. One example of recursive functions is operations on binary trees. Binary trees are an advanced data structure, but all operations on them are considered recursive in nature. Traversing linked lists is another recursive problem, but linked lists too are an advanced topic.
Recursion is a powerful tool, but it takes a lot of careful planning so it can be difficult to implement, and many programmers will simply pass it up because of this. In order to successfully implement a recursive function, you must identify one or more exit conditions for stopping the recursive calls. If you do this wrong, your code will enter an endless loop and cause a stack overflow because of all the function calls.
Recursion is never necessary, and many never use it in practice because of the time it takes to design the problem is often longer than just coding it iteratively. In these days where agile development is king, time is everything. It is still good to know in case you do come across a problem recursive in nature, or see it in somebody else’s code.
Most examples of recursion are rather contrived, and to be honest this programmer has never used it in C (Python yes, C no) since learning it in college. Calculating factorials, tower of Hanoi, and the Sieve of Eratosthenes are common ones for explaining recursion without going into too advanced of concepts. Calculating factorials is simple enough to explain the concept so we are going to write a very quick sample to do just that.
You may remember the definition of factorials from math courses, you may not. The point to take home is that the recursive function calc_factorial in calc_factorial.c calls itself until the base case is resolved and it returns 1 instead of n – 1.
calc_factorial.c:
#include <stdio.h>
int calc_factorial(int n);
int main() {
int i; int n_values[5] = {1, 2, 5, 3, 9}; int factorials[5];
for (i = 0; i < 5; i++) { factorials[i] = calc_factorial(n_values[i]);
}
for (i = 0; i < 5; i++) { printf("Factorial of %d is %d", n_values[i], factorials[i]); printf("n");
}
}
int calc_factorial(int n) {
int n_minus_one; int next_n;
//Base case for exiting the recursion is a value of 1.
if (n <= 1) {
return 1;
} else {
//Otherwise return the next iteration's n value. n_minus_one = n - 1; next_n = n * calc_factorial(n_minus_one);
return next_n;
}
}
Here is the output of calc_factorial:
Multi-File Programs
All but the most simple programs written in C will have multiple source files. There are two types of source files in C, header files and implementation files.
Header files are where all your function prototypes, global variables, constants, and the like goes. You have been using header files all along; stdio.h, for example. Header files use the .h extension and are accessed by your implementation files by #include statements. At compile time, #include essentially sticks the header file where you use it in your program so you can access anything that was declared in the header file, right in your program as though you had typed it there.
Implementation files are where you put the function definitions, and actually “do stuff.” Basically all the stuff from our examples so far from the end of the main function to the end of the file is what goes into an implementation file, and what is above main goes into a header file. Usually main will go into it is own source file called main.c.
Lets turn our printxy example into a simple multi-file C program.
printxy.h:
We move just our function prototypes into printxy.h. If we were still using x and y globally we would move them here as well. The #ifndef, #define, and #endif are macros that make sure we do not have problems with redeclaring functions or variables when multiple source files try to use the same header files.
#ifndef checks if the token has been declared someplace else. The token is whatever you want it to be. Conventionally it uses the underscore and capital naming style you see above. Naming it after your header file name is the most common way to decide on the token.
So if #ifndef does not find that token anyplace else, it will define it using the #define statement. Use #define to actually set your token.
#endif simply goes at the end of what code you want to be protected under the umbrella of your token. It does not necessarily have to go at the end of the file.
printxy.c:
Our implementation file is where we move our definition of the printxy function to. We must not forget to include our new header file. We used the ” ” instead of < > because it is not a built-in standard header, it is defined right here in the program. That was not so hard, was it?
main.c:
#include "printxy.h"
int main(int argc, char *argv[]) {
int x[3], y[3]; int i;
for (i = 0; i < 3; i++) { x[i] = i; y[i] = 10;
}
printxy(x, y);
for (i = 0; i < 3; i++) { x[i] += y[i];
}
printxy(x, y);
for (i = 0; i < 3; i++) { y[i]++;
} printxy(x, y);
return 0;
}
Finally our main function is alone in its own file. Easy to find by maintainers and yourself if your program grows to many source files. Now all our main has to import is the printxy.h header file. That is really the only difference from before, besides the deletion of the printxy prototype and definition.
You might be thinking this seems like more trouble than it is worth. You are right, for this program. Try to imagine a program with 10 or more header and source files. Probably scares you, but that is because I have not told you the best part yet. You only put code that relates to eachother into header files together.
You come up with some sort of grouping for them before breaking them up. Print functions in one header, math functions in another, input-getting functions in another, or both input and output like stdio. Whatever you want, those are just some ideas. It really does make things easier to maintain, especially if you use descriptive names. Just make sure your header and corresponding implementation files have matching names like we did here.
When compiling these multi-file programs from the command-line, you just list all the files instead of the one file like before. I use gcc, I do not know how to use any others so I will use that as an example. I will show you how I compile printxy1.c and how I compile this multi-file version using gcc.
1 file:
gcc -o printxy1 printxy1.c
3 files:
gcc -o printxy printxy.h printxy.c main.c