What we have for you in this section.............
WHAT IS AN IDENTIFIER?
Before you can do anything in any language, you must know how to name an identifier. An identifier is used for any variable, function, data definition, etc. In the C programming language, an identifier is a combination of alphanumeric characters, the first being a letter of the alphabet or an underline, and the remaining being any letter of the alphabet, any numeric digit, or the underline.
Two rules must be kept in mind when naming identifiers.
The case of alphabetic characters is significant. Using INDEX for a variable name is not the same as using index and neither of them is the same as using InDeX for a variable name. All three refer to different variables.
According to the ANSI-C standard, at least 31 significant characters can be used and will be considered significant by a conforming ANSI-C compiler. If more than 31 are used, all characters beyond the 31st may be ignored by any given compiler.
WHAT ABOUT THE UNDERLINE?
The underline can be used as part of a variable name, and adds greatly to the readability of the resulting code. It is used by some, but not all, experienced C programmers. A few underlines are used for illustration in this tutorial. Since most compiler writers use the underline as the first character for variable names internal to the system, you should refrain from using the underline to begin an identifier to avoid the possibility of a name clash. To get specific, identifiers with two leading underscores are reserved for the compiler as well as identifiers beginning with a single underscore and using an upper case alphabetic character for the second. If you make it a point of style to never use an identifier with a leading underline, you will not have a naming clash with the system.
It adds greatly to the readability of a program to use descriptive names for variables and it would be to your advantage to do so. Pascal and Ada programmers tend to use long descriptive names, but most C programmers tend to use short cryptic names. Most of the example programs in this tutorial use very short names for that reason, but a few longer names are used for illustrative purposes.
There are 32 words defined as keywords in C. These have predefined uses and cannot be used for any other purpose in a C program. They are used by the compiler as an aid to compiling the program. They are always written in lower case. A complete list follows;
auto double int struct
break else long switch
case enum register typedef
char extern return union
const float short unsigned
continue for signed void
default goto sizeof volatile
do if static while
In addition to this list of keywords, your compiler may define a few more. If it does, they will be listed in the documentation that came with your compiler. Each of the above keywords will be defined, illustrated, and used in this tutorial.
WE NEED DATA AND A PROGRAM
Any computer program has two entities to consider, the data, and the program. They are highly dependent on one another and careful planning of both will lead to a well planned and well written program. Unfortunately, it is not possible to study either completely without a good working knowledge of the other. For that reason, this tutorial will jump back and forth between teaching methods of program writing and methods of data definition. Simply follow along and you will have a good understanding of both. Keep in mind that, even though it seems expedient to sometimes jump right into coding the program, time spent planning the data structures will be well spent and the quality of the final program will reflect the original planning.
HOW THIS TUTORIAL IS WRITTEN
As you go through the example programs, you will find that every program is complete. There are no program fragments that could be confusing. This allows you to see every requirement that is needed to use any of the features of C as they are presented. Some tutorials I have seen give very few, and very complex examples. They really serve more to confuse the student. This tutorial is the complete opposite because it strives to cover each new aspect of programming in as simple a context as possible.
Throughout this tutorial, keywords, variable names, and function names will be given in boldface as an aid to clarity. These terms will be completely defined throughout the tutorial.
RESULT OF EXECUTION
The result of executing each program will be given in comments at the end of the program listing after the comment is defined in about the fourth program of chapter 2. If you feel confident that you completely understand the program, you can simply refer to the result of execution to see if you understand the result. In this case, it will not be necessary for you to compile and execute every program. It would be a good exercise for you to compile and execute some of them however, because all C compilers will not generate exactly the same results and you need to get familiar with your own compiler.
Example program ------> FIRSTEX.C
At this point, you should compile and execute FIRSTEX.C if you have not yet done so, to see that your C compiler is properly loaded and operating. Don't worry about what the program does yet. In due time you will understand it completely.
Note that this program will compile and execute properly with any good compiler.
A WORD ABOUT COMPILERS
All of the example programs in this tutorial will compile and execute correctly with any good ANSI compatible C compiler. Some compilers have gotten extremely complex and hard to use for a beginning C programmer, and some only compile and build Microsoft Windows programs. Fortunately, most of the C compilers available have a means of compiling a standard C program which is written for the DOS environment and includes none of the Windows extensions. You should check your documentation for the capabilities and limitations of your compiler. If you have not yet purchased a C compiler, you should find one that is ANSI-C compliant, and that also has the ability to generate a DOS executable if you are planning to use the DOS operating system.
ANSWERS TO PROGRAMMING EXERCISES
There are programming exercises at the end of most of the chapters. You should attempt to do original work on each of the exercises before referring to the answers in order to gain your own programming experience. These answers are given for your information in case you are completely stuck on how to solve a particular problem. These answers are not meant to be the only answer, since there are many ways to program anything, but they are meant to illustrate one way to solve the suggested programming problem.
The answers are all in source files named in the format CHnn_m.C where nn is the chapter number, and m is the exercise number. If more than one answer is required, an A, B, or C is included following the exercise number.
YOUR FIRST C PROGRAM
Example program ------> TRIVIAL.C
The best way to get started with C is to actually study a program, so load the file named TRIVIAL.C and display it on the monitor. You are looking at the simplest possible C program. There is no way to simplify this program or to leave anything out. Unfortunately, the program doesn't do anything.
The word main is very important, and must appear once, and only once in every C program. This is the point where execution is begun when the program is run. We will see later that this does not have to be the first statement in the program but it must exist as the entry point. Following the main program name is a pair of parentheses which are an indication to the compiler that this is a function. We will cover exactly what a function is in due time. For now, I suggest that you simply include the pair of parentheses.
The two curly brackets in lines 2 and 3, properly called braces, are used to define the limits of the program itself. The actual program statements go between the two braces and in this case, there are no statements because the program does absolutely nothing. You can compile and run this program, but since it has no executable statements, it does nothing. Keep in mind, however, that it is a valid C program. When you compile this program, you may get a warning. You can ignore the warning and we will discuss it later in this tutorial, or you can modify the program so that it appears as follows;
This modified program must compile on any good C compiler since it conforms to the ANSI-C standard. We will explain the difference in these two programs later in this tutorial.
A PROGRAM THAT DOES SOMETHING
Example program ------> WRTSOME.C
For a much more interesting program, load the program named WRTSOME.C and display it on your monitor. It is the same as the previous program except that it has one executable statement between the braces plus the obligatory return statement.
The executable statement is a call to a function supplied as a part of your C library. Once again, we will not worry about what a function is, but only how to use this one named printf(). In order to output text to the monitor, the desired text is put within the function parentheses and bounded by quotation marks. The end result is that whatever text is included between the quotation marks will be displayed on the monitor when the program is run.
Notice the semi-colon at the end of line 5. C uses a semi-colon as a statement terminator, so the semi-colon is required as a signal to the compiler that this line is complete. This program is also executable, so you can compile and run it to see if it does what you think it should. It should cause the text between the quotation marks to appear on the monitor when you execute it.
You can ignore the statements in lines 1 and 7 in this program and similar statements in each of the remaining programs in this chapter. These will be fully described later in this tutorial. We will also define why the word int is used at the begining of line 3. We have a few preliminary topics to cover before we get to these items.
ANOTHER PROGRAM WITH MORE OUTPUT
Example program ------> WRTMORE.C
Load the program WRTMORE.C and display it on your monitor for an example with more output and another small but important concept. You will see that there are four executable statements in this program, each one being a call to the function printf(). The top line will be executed first, then the next, and so on, until the fourth line is complete. The statements are executed sequentially from top to bottom.
Notice the funny character near the end of the first line, namely the backslash. The backslash is used in the printf() statement to indicate that a special control character is following. In this case, the "n" indicates that a newline is requested. This is an indication to return the cursor to the left side of the monitor and move down one line. Any place within printed text that you desire, you can put a newline character to start a new line. You could even put it in the middle of a word and split the word between two lines.
A complete description of this program is now possible. The first printf() outputs a line of text and returns the carriage. (Of course, there is no carriage, but the cursor is moved to the next line on the monitor. The terminology carries over from the days of teletypes.) The second printf() outputs a line of text but does not return the carriage so that the third line is appended to the end of the second, then followed by two carriage returns, resulting in a blank line. Finally the fourth printf() outputs a line followed by a carriage return and the program is complete.
After compiling and executing WRTMORE.C, the following text should be displayed on your monitor;
This is a line of text to output.
And this is another line of text.
This is a third line.
Compile and execute this program to see if it gives you this output. It would be a good idea at this time for you to experiment by adding additional lines of printout to see if you understand how the statements really work. Add a few carriage returns in the middle of a line to prove to yourself that it works as stated, then compile and execute the modified program. The more you modify and compile the example programs included with this tutorial, the more you will learn as you work your way through it.
LET'S PRINT SOME NUMBERS
Example program ------> ONEINT.C
Load the file named ONEINT.C and display it on the monitor for our first example of how to work with data in a C program. The entry point main() should be clear to you by now as well as the beginning brace. The first new thing we encounter is line 5 containing
int index; which is used to define an integer variable named index. The word int is a keyword in C, and can not be used for anything else. It defines a variable that can store a whole number within a predefined range of values. We will define an actual range later. The variable name, index, can be any name that follows the rules for an identifier and is not one of the keywords for C. The final character on the line, the semi-colon, is the statement terminator as discussed earlier.
Note that, even though we have defined a variable, we have not yet assigned a value to it, so it contains an undefined value. We will see in a later chapter that additional integers could also be defined on the same line, but we will not complicate the present situation.
Observing the main body of the program, you will notice that there are three statements that assign a value to the variable index, but only one at a time. The statement in line 7 assigns the value of 13 to index, and its value is printed out by line 8. (We will see how shortly. Trust me for the time being.) Later, the value of 27 is assigned to index, and finally 10 is assigned to it, each value being printed out. It should be intuitively clear that index is indeed a variable and can store many different values but only one value at a time of course.
Please note that many times the words "printed out" are used to mean "displayed on the monitor". You will find that in many cases experienced programmers take this liberty, probably due to the printf() function being used for monitor display.
HOW DO WE PRINT NUMBERS?
To keep our promise, let's return to the printf() statements for a definition of how they work. Notice that they are all identical and that they all begin just like the printf() statements we have seen before. The first difference occurs when we come to the % character. This is a special character that signals the output routine to stop copying characters to the output and do something different, usually to output the value of a variable. The % sign is used to signal the output of many different types of variables, but we will restrict ourselves to only one for this example. The character following the % sign is a d, which signals the output routine to get a decimal value and output it. Where the decimal value comes from will be covered shortly. After the d, we find the familiar \n, which is a signal to return the video "carriage", and the closing quotation mark.
All of the characters between the quotation marks define the pattern of data to be output by this statement. Following the output pattern, there is a comma followed by the variable name index. This is where the printf() statement gets the decimal value which it will output because of the %d we saw earlier. The system substitutes the current value of the variable named index for the %d and copies it to the monitor. We could add more %d output field descriptors anywhere within the brackets and more variables following the description to cause more data to be printed with one statement. Keep in mind however, that the number of field descriptors and the number of variable definitions must be the same or the runtime system will generate something we are not expecting.
Much more will be covered at a later time on all aspects of input and output formatting. A reasonably good grasp of these fundamentals are necessary in order to understand the following lessons. It is not necessary to understand everything about output formatting at this time, only a fair understanding of the basics.
Compile and run ONEINT.C and observe the output. Two programming exercises at the end of this chapter are based on this program.
HOW DO WE ADD COMMENTS IN C?
Example program ------> COMMENTS.C
Load the file named COMMENTS.C and observe it on your monitor for an example of how comments can be added to a C program. Comments are added to make a program more readable to you but represent nonsense to the compiler, so we must tell the compiler to ignore the comments completely by bracketing them with special characters. The slash star combination is used in C for comment delimiters, and are illustrated in the program at hand. Please note that the program does not illustrate good commenting practice, but is intended to illustrate where comments can go in a program. It is a very sloppy looking program.
The slash star combination in line 3 introduces the first comment and the star slash at the end of that line terminates this comment. Note that this comment is prior to the beginning of the program illustrating that a comment can precede the program itself. Good programming practice would include a comment prior to the program with a short introductory description of the program. The comment in line 5 is after the main program entry point and prior to the opening brace for the program code itself.
The third comment starts after the first executable statement in line 7 and continues for four lines. This is perfectly legal because a comment can continue for as many lines as desired until it is terminated. Note carefully that if anything were included in the blank spaces to the left of the three continuation lines of the comment, it would be part of the comment and would not be compiled, but totally ignored by the compiler. The last comment, in line 15, is located following the completion of the program, illustrating that comments can go nearly anywhere in a C program.
Experiment with this program by adding comments in other places to see what will happen. Comment out one of the printf() statements by putting comment delimiters both before and after it and see that it does not get executed and therefore does not produce a line of printout.
Comments are very important in any programming language because you will soon forget what you did and why you did it. It will be much easier to modify or fix a well commented program a year from now than one with few or no comments. You will very quickly develop your own personal style of commenting.
Some C compilers will allow you to "nest" comments which can be very handy if you need to "comment out" a section of code during debugging. Since nested comments are not a part of the ANSI-C standard, none will be used in this tutorial. Check the documentation for your compiler to see if they are permitted with your implementation of C. Even though they may be allowed, it is a good idea to refrain from their use, since they are rarely used by experienced C programmers, and using them may make it difficult to port your code to another compiler if the need should arise.
GOOD FORMATTING STYLE
Example program ------> GOODFORM.C
Load the file GOODFORM.C and observe it on your monitor. It is an example of a well formatted program. Even though it is very short and therefore does very little, it is very easy to see at a glance what it does. With the experience you have already gained in this tutorial, you should be able to very quickly grasp the meaning of the program in it's entirety. Your C compiler ignores all extra spaces and all carriage returns giving you considerable freedom in formatting your program. Indenting and adding spaces is entirely up to you and is a matter of personal taste. Compile and run the program to see if it does what you expect it to do.
Example program ------> UGLYFORM.C
Now load and display the program UGLYFORM.C and observe it. How long will it take you to figure out what this program will do? It doesn't matter to the compiler which format style you use, but it will matter to you when you try to debug your program. Compile this program and run it. You may be surprised to find that it is the same program as the last one, except for the formatting. Don't get too worried about formatting style yet. You will have plenty of time to develop a style of your own as you learn the C language. Be observant of styles as you see C programs in magazines and books.
This covers some of the basic concepts of programming in C, but as there are many other things to learn, we will forge ahead to additional program structure. It will definitely be to your advantage to do the programming exercises at the end of each chapter. They are designed to augment your studies and teach you to use your compiler.
Write a program to display your name on the monitor.
Modify the program to display your address and phone number on separate lines by adding two additional printf() statements.
Remove line 7 from ONEINT.C by commenting it out, then compile and execute the resulting program to see the value of an uninitialized variable. This can be any value within the allowable range for that variable. If it happens to have the value of zero, that is only a coincidence, but then zero is the most probable value to be in an uninitialized variable because there are lots of zero values floating around in a computer's memory. It is actually legal for the program to abort if you refer to a variable that you failed to initialize, but few compilers, if any, will actually do so.
Add the following two lines just after the last printf() of ONEINT.C to see what it does. Study it long enough to completely understand the result.
printf("Index is %d\n it still is %d\n it is %d",
THE WHILE LOOP
The C programming language has several structures for looping and conditional branching. We will cover them all in this chapter and we will begin with the while loop.
The while loop continues to loop while some condition is true. When the condition becomes false, the looping is discontinued. It therefore does just what it says it does, the name of the loop being very descriptive.
Example program ------> WHILE.C
Load the program WHILE.C and display it for an example of a while loop. We begin with a comment and the program entry point main(), then go on to define an integer variable named count within the body of the program. The variable is set to zero and we come to the while loop itself. The syntax of a while loop is just as shown here. The keyword while is followed by an expression of something in parentheses, followed by a compound statement bracketed by braces. As long as the expression in the parenthesis is true, all statements within the braces will be repeatedly executed. In this case, since the variable count is incremented by one every time the statements are executed, it will eventually reach 6. At that time the statement will not be executed because count is not less than 6, and the loop will be terminated. The program control will resume at the statement following the statements in braces.
We will cover the compare expression, the one in parentheses, in the next chapter. Until then, simply accept the expressions for what you think they should do and you will be correct for these simple cases.
Several things must be pointed out regarding the while loop. First, if the variable count were initially set to any number greater than 5, the statements within the loop would not be executed at all, so it is possible to have a while loop that never is executed. Secondly, if the variable were not incremented in the loop, then in this case, the loop would never terminate, and the program would never complete. Finally, if there is only one statement to be executed within the loop, it does not need delimiting braces but can stand alone.
Compile and run this program after you have studied it enough to assure yourself that you understand its operation completely. Note that the result of execution is given for this program, (and will be given for all of the remaining example programs in this tutorial) so you do not need to compile and execute every program to see the results. Be sure to compile and execute some of the programs however, to gain experience with your compiler.
You should make some modifications to any programs that are not completely clear to you and compile them until you understand them completely. The best way to learn is to try various modifications yourself.
We will continue to ignore the #include statement and the return statement in the example programs in this chapter. We will define them completely later in this tutorial.
THE DO-WHILE LOOP
Example program ------> DOWHILE.C
A variation of the while loop is illustrated in the program DOWHILE.C, which you should load and display. This program is nearly identical to the last one except that the loop begins with the keyword do, followed by a compound statement in braces, then the keyword while, and finally an expression in parentheses. The statements in the braces are executed repeatedly as long as the expression in the parentheses is true. When the expression in parentheses becomes false, execution is terminated, and control passes to the statements following this statement.
Several things must be pointed out regarding the do-while loop. Since the test is done at the end of the loop, the statements in the braces will always be executed at least once. Secondly, if the variable i were not changed within the loop, the loop would never terminate, and hence the program would never terminate.
It should come as no surprise to you that these loops can be nested. That is, one loop can be included within the compound statement of another loop, and the nesting level has no limit. This will be illustrated later.
Compile and run this program to see if it does what you think it should do.
THE FOR LOOP
Example program ------> FORLOOP.C
Load and display the file named FORLOOP.C on your monitor for an example of a program with a for loop. The for loop consists of the keyword for followed by a rather large expression in parentheses. This expression is really composed of three fields separated by semi-colons. The first field contains the expression
"index = 0" and is an initializing field. Any expressions in this field are executed prior to the first pass through the loop. There is essentially no limit as to what can go here, but good programming practice would require it to be kept simple. Several initializing statements can be placed in this field, separated by commas.
The second field, in this case containing
"index < 6", is the test which is done at the beginning of each pass through the loop. It can be any expression which will evaluate to a true or false. (More will be said about the actual value of true and false in the next chapter.)
The expression contained in the third field is executed each time the loop is exercised but it is not executed until after those statements in the main body of the loop are executed. This field, like the first, can also be composed of several operations separated by commas.
Following the for() expression is any single or compound statement which will be executed as the body of the loop. A compound statement is any group of valid C statements enclosed in braces. In nearly any context in C, a simple statement can be replaced by a compound statement that will be treated as if it were a single statement as far as program control goes. Compile and run this program.
The while is convenient to use for a loop when you don't have any idea how many times the loop will be executed, and the for loop is usually used in those cases when you are doing a fixed number of iterations. The for loop is also convenient because it moves all of the control information for a loop into one place, between the parentheses, rather than at both ends of the code. It is your choice as to which you would rather use. Depending on how they are used, it is possible with each of these two loops to never execute the code within the loop at all. This is because the test is done at the beginning of the loop, and the test may fail during the first iteration. The do-while loop however, due to the fact that the code within the loop is executed prior to the test, will always execute the code at least once.
THE IF STATEMENT
Example program ------> IFELSE.C
Load and display the file IFELSE.C for an example of our first conditional branching statement, the if. Notice first, that there is a for loop with a compound statement as its executable part containing two if statements. This is an example of how statements can be nested. It should be clear to you that each of the if statements will be executed 10 times.
Consider the first if statement. It starts with the keyword if followed by an expression in parentheses. If the expression is evaluated and found to be true, the single statement following the if is executed, and if false, the following statement is skipped. Here too, the single statement can be replaced by a compound statement composed of several statements bounded by braces. The expression
"data == 2" is simply asking if the value of data is equal to 2. This will be explained in detail in the next chapter. (Simply suffice for now that if
"data = 2" were used in this context, it would mean a completely different thing. You must use the double equal sign for comparing values.)
NOW FOR THE IF-ELSE
The second if is similar to the first with the addition of a new keyword, the else in line 17. This simply says that if the expression in the parentheses evaluates as true, the first expression is executed, otherwise the expression following the else is executed. Thus, one of the two expressions will always be executed, whereas in the first example the single expression was either executed or skipped. Both will find many uses in your C programming efforts. Compile and run this program to see if it does what you expect.
THE BREAK AND CONTINUE
Example program ------> BREAKCON.C
Load the file named BREAKCON.C for an example of two new statements. Notice that in the first for loop, there is an if statement that calls a break if xx equals 8. The break will jump out of the loop you are in and begin executing statements immediately following the loop, effectively terminating the loop. This is a valuable statement when you need to jump out of a loop depending on the value of some results calculated in the loop. In this case, when xx reaches the value of 8, the loop is terminated and the last value printed will be the previous value, namely 7. The break always jumps out of the loop just past the terminating brace.
The next for loop starting in line 15, contains a continue statement which does not cause termination of the loop but jumps out of the present iteration. When the value of xx reaches 8 in this case, the program will jump to the end of the loop and continue executing the loop, effectively eliminating the printf() statement during the pass through the loop when xx is eight. The continue statement always jumps to the end of the loop just prior to the terminating brace. At that time the loop is terminated or continues based on the result of the loop test.
Be sure to compile and execute this program.
THE SWITCH STATEMENT
Example program ------> SWITCH.C
Load and display the file SWITCH.C for an example of the biggest construct yet in the C language, the switch. The switch is not difficult, so don't let it intimidate you. It begins with the keyword switch followed by a variable in parentheses which is the switching variable, in this case truck. As many cases as needed are then enclosed within a pair of braces. The reserved word case is used to begin each case, followed by the value of the variable for that case, then a colon, and the statements to be executed.
In this example, if the variable named truck contains the value 3 during this pass of the switch statement, the printf() in line 13 will cause
"The value is three\n" to be displayed, and the break statement will cause us to jump out of the switch. The break statement here works in much the same manner as the loop, it jumps out just past the closing brace.
Once an entry point is found, statements will be executed until a break is found or until the program drops through the bottom of the switch braces. If the variable truck has the value 5, the statements will begin executing at line 17 where
"case 5 :" is found, but the first statements found are where the case 8 statements are. These are executed and the break statement in line 21 will direct the execution out of the bottom of the switch just past the closing brace. The various case values can be in any order and if a value is not found, the default portion of the switch will be executed.
It should be clear that any of the above constructs can be nested within each other or placed in succession, depending on the needs of the particular programming project at hand. Note that the switch is not used as frequently as the loop and the if statements. In fact, the switch is used infrequently but should be completely understood by the serious C programmer. Be sure to compile and run SWITCH.C and examine the results.
THE EVIL GOTO STATEMENT
Example program ------> GOTOEX.C
Load and display the file GOTOEX.C for an example of a file with some goto statements in it. To use a goto statement, you simply use the reserved word goto followed by the symbolic name to which you wish to jump. The name is then placed anywhere in the program followed by a colon. You can jump nearly anywhere within a function, but you are not permitted to jump into a loop, although you are allowed to jump out of a loop.
This particular program is really a mess but it is a good example of why software writers are trying to eliminate the use of the goto statement as much as possible. The only place in this program where it is reasonable to use the goto is the one in line 23 where the program jumps out of the three nested loops in one jump. In this case it would be rather messy to set up a variable and jump successively out of each of the three nested loops but one goto statement gets you out of all three in a very concise manner.
Some persons say the goto statement should never be used under any circumstances, but this is narrow minded thinking. If there is a place where a goto will clearly do a neater control flow than some other construct, feel free to use
After teaching C programming for several years, I found that there are certain concepts that are difficult to understand and are often misunderstood by new programmers. This is even true of some programmers that have been using C for several years. This document will define how parameters are passed to functions. A detailed knowledge of how this is done in C and C++ will allow you to write more efficient and more robust programs.
HOW IS DATA PASSED TO A FUNCTION?
Consider the very simple program given in the listing immediately below. This program makes a single call to the function named product(), and it passes two parameters to that function. We can also see that the function returns a single value which is of type int.
Because this is a trivial program, and we wish to keep it simple, a prototype for the function is not included. A real program should also do something with the returned result named area which we simply ignore in this example.
int length = 12;
int width = 5;
area = product(length, width); /* The call to the function */
int product(int side1, int side2) /* The function header */
return side1 * side2;
This is a trivial program, but it will be the starting point for our study of how parameters are passed to a function. If we consider the two lines that are commented on in the example code, we can extract the following two statements, which are executed each time the function named product() is called.
int side1 = length;
int side2 = width;
Even though these two statements are not explicitly given in the code, they are nevertheless executed with each call to product() because of the way C is defined. The first statement is saying, "create a variable for use within the function that is of type int, named side1, and initialize it to the value of length from the calling program". Anytime we refer to the variable named side1 within the function, we are referring to the local copy of the variable, and if we change the value stored in side1, we do not affect the value of length back in the calling program because we are working only with side1. The same is true of the second parameter. We are telling the system to create a variable named side2 which is of type int, and initialize it to the value of width from the main program.
When we return from the function, the two new variables are no longer needed so they are deleted. When we say they are deleted, they are not set equal to zero, or any other value, they actually cease to exist. They are deallocated by the C runtime system.
A GRAPHICAL REPRESENTATION OF THIS OPERATION
If you have not already done so, read the Memory Diagram document which defines some of the terms given below including the graphical diagramming conventions used by this author.
A picture is said to be worth a thousand words, so let's draw a picture. Figure 1 is a diagram of the memory space of the computer just prior to making the call to the function. (This is actually a greatly simplified diagram, but it will help us get started in understanding just what is happening. We will fill in more details later.) The function main() and the function product() are stored somewhere in the global memory space of the computer, and the stack is allocated by the C runtime system for use by our program. The two boxes represent storage for each of the two global variables named length and width. The storage for each of these two variables will be created just large enough to store an int type variable on our system. The stack is currently empty. You will notice that each of the variables has a value as a result of the initialization in the code. (I am purposely ignoring the variable named area for the time being. We will explain where it is stored later in this document.)
When the call to the function is made, the system creates a variable named side1 of type int and copies the value of length from the main program into it. This variable is created on the stack and can be used within the function. Likewise, another variable is created on the stack named side2 of type int and the value of width is copied into it. Neither of these two variables is available for use within the calling program, but are only available to the function named product(). The memory space now looks like figure 2, and the code in the function named product() is executed with full access to the two parameters on the stack. Because the variables named length and width are global, they are also available for use or modification within the function named product().
When execution of the function code is complete, the value of the product of the two variables is returned to the calling program, which can do whatever it wishes to do with it. The two variables are removed from the stack and discarded or, to be a little more proper, they are deallocated. We will say more about the returned value later in this document. When we return to the calling program, the memory space can be diagrammed as shown in figure 3. You will notice that figure 3 is identical to figure 1.
Based on what we have said so far, the following facts should be clear to you;
A copy of the input parameters is made for use within the function. This is often referred to as pass by value.
Since the function is working with a copy of the data, it cannot change the value of the variable passed from the calling program.
We can define a generalized assignment for each parameter that is passed to a function,
formal_type formal_parameter = actual_parameter;
We are still not worried about the return value or the variable named area. We will define them completely later, but I am more interested in defining the parameter passing scheme at this time.
LET'S REFINE THE MODEL SOME
We will repeat the first program with a slight modification and we will be a little more correct since we will not ignore any of the variables. We will move the two global variables into the main program so that they are local variables, and if you studied C diligently, you will recall that they are also called automatic variables. They are called automatic variables because they are automatically allocated when they are needed. If you guess that they are allocated on the stack, you are right. The new code is given as follows;
int length = 12;
int width = 5;
area = product(length, width); /* The call to the function */
int product(int side1, int side2) /* The function header */
return side1 * side2;
Now we have a slightly different memory layout. The automatic variables are all allocated on the stack as they are encountered in the main program, so that when we arrive at the function call in the main program, the memory layout is as depicted in figure 4. The three variables on the stack can be accessed by the main() program. I promised earlier that I would show you where the variable area is stored, and you can see that it is on the stack because it is an automatic variable. It is a little different from the other two variables however, because it has not been initialized. Local variables are not automatically initialized by the system, but contain whatever bit pattern happened to be in that memory location. At this time therefore, the variable named area contains any value imaginable that is legal for an int type variable. The variables named width and length are explicitly initialized in the code.
During the call to the function, the system allocates the two parameters on the stack as it did in the last program, initializes them to the required values as illustrated above, and we have the resulting memory layout given in figure 5 just prior to executing the return statement.
The stack now contains 5 variables. Three are available exclusively to the main() function, and two are available exclusively to the product() function. The word exclusively is essential in the preceding statement. The variables cannot be seen by any address space within the memory other than where they are defined because of the way C is defined. Since we moved the two global variables into the main() function, they are no longer visible when we are executing code within the product() function. Execution of the return statement in the function, causes the two variables for the function to be deallocated from the stack, and control returns to the main() function. At this time, the memory space will look very much like figure 4 again but with a meaningful value stored in area.
We can make up two more rules of argument passing and stack operations;
When a function is entered, all formal parameters are created on the stack and they are initialized to the values of their corresponding actual parameters,
then all local variables within the function are created on the stack and they are initialized only if initialization values are explicitly given in the code.
WHAT ABOUT THE ORDER OF CREATION
In the case of function parameters, the default for C is to create the variables on the stack in the reverse order from their listed order so that side2 would go on the stack prior to side1. We will not be concerned about this because we are only trying to understand the method of passing variables. If we were writing a compiler, we would be very interested in this issue, and for some kinds of advanced programming we also would need to pay attention to the creation order.
THE WRONG NUMBER OF PARAMETERS
Let's repeat the code from above but with another little change. This time we will make two calls to the function, one with too few actual parameters, and one with too many actual parameters. The new code is given as follows;
int length = 12;
int width = 5;
int height = 6;
area = product(length); /* Too few */
area = product(length, width, height); /* Too many */
int product(int side1, int side2) /* The function header */
return side1 * side2;
In the case of using too few parameters, we will be asking the compiler to execute the following two instructions which it cannot do because the second line is incomplete.
int side1 = length;
int side2 = ; /* error, uninitialized parameter */
If we are using prototypes, which we should be doing, the compiler will report that there are not enough parameters during compilation, and we can very quickly repair the error.
When providing too many parameters, there is an extra value which the system does not know what to do with, so it probably just ignores it. If you pass an extra value to the function, you are probably expecting the function to use the value for something, so there is some sort of an error. Detecting and reporting these kinds of errors is what prototyping is all about. You should be very diligent in the use of prototyping for all of your C programming.
HOW IS THE DATA RETURNED?
Returning data is not as easy to define since the compiler writer has a good deal of leeway, but he must follow one rule;
The value returned, in this case the product of the two values passed in, may be returned on the stack or it may be returned in a register, or any other way that the compiler writer desires. It is up to the compiler writer to provide a means of storing the returned value in the calling program for you in a manner that corresponds with his means of returning it. Simply keep in mind that the system must somehow maintain a copy of the returned value long enough for you to store a local copy of it. It must keep the value available until execution reaches a "sequence point", which in this case means the end of the statement. When you tell the system to assign the value to area, you are saving a copy of it. Following that, the compiler writer may delete the memory he used to return the value. If he returned it on the stack, it is imperative that he remove it from the stack in preparation for the next operation. But that is fine because you have the value stored in area and you can use it in whatever way you desire.
HOW DO WE PASS A POINTER?
Since we are starting a whole new subject, it would be profitable to write a completely new program with a couple of pointers in it to use as our illustration. So, consider the following program fragment;
int length = 16;
int width = 10;
int *p_width = &width;
area = mod_product(&length, p_width)
int mod_product(int *sideA, int *sideB)
*sideA = *sideA / 2;
*sideB = *sideB / 2;
return *sideA * *sideB;
This is a very simple program that does nothing worthwhile except to act as a very good example of how pointers are passed as parameters in a function call. We define a global variable named length and within the main() function we define another variable named width to give us some data to work with. Then we define a pointer named p_width and cause it to point to the variable named width by assigning it the address of width. Finally, we define a variable named area so we will have a place to store the result of the function call. The length and width are defined in different places in this program only to illustrate two different storage means. In a meaningful program, such similar variables should be defined together and in a similar fashion.
Just prior to making the call to the function, we have the memory layout as depicted in figure 6. If you understand all of this article up to this point, you should be able to understand every detail of this diagram. Keep in mind that a pointer is actually another storage element, sort of like an integer, but one that stores the address of an entity rather than some actual data.
CALLING THE FUNCTION WITH POINTERS
When we call the function we are executing the following two statements in the manner which we defined earlier in this document. These statements create two new pointers on the stack and initializes them to point to two variables, one in the main program named width, and one in global memory named length;
int *sideA = &length;
int *sideB = p_width;
Once again, two different methods of passing a pointer to a function are used for illustration. In the first case we take the address of the actual parameter right in the function call and pass the resulting pointer to the function. In the second case we pass a pointer's value. A well planned program should pass both parameters in the same manner in order to make the program easier to understand. You should be able to clearly see that the reason these two pointers are pointing to variables in the other parts of the program is because we passed pointers to those other variables. Following execution of the function call, we are in the function and the memory layout is depicted in figure 7.
Following execution of the three statements within the function but prior to the return to the main program, we have the memory layout as depicted in figure 8. You will notice that the system has modified the two variables named length and width which are elements of the calling program. It has calculated the return value but we have no way to depict where it is stored since each compiler can use any method it desires. Suffice it to say that somewhere the system has the value of 40 stored away for return.
Following return, the value of 40 is stored in the variable named area and the program terminates.
This program should have clearly depicted how the pointers are used to provide access to the actual values in the calling program. You should notice also that copies of the pointers were passed to the function in this program which says that the first rule, given at the beginning of this document, is not violated.
PASSING AN ARRAY TO A FUNCTION
The name of an array is actually a pointer which is pointing to the first element in the array, with the added caveat that it cannot be changed. It is actually a constant pointer. Therefore when we pass the name of the array to a function we are passing a pointer to the first element and we make a copy of that pointer for use within the function. For more details on arrays, see the document entitled Arrays and Pointers in this group of documents.
Study the following program fragment for a few minutes and we will make a few comments on it.
#define ELEMENTS 10
for (index = 0 ; index < ELEMENTS ; index++)
my_array[index] = (double)(index * 2);
sum = sum_array(ELEMENTS, my_array);
double sum_array(int size, double in_array)
double total = 0.0;
for (index = 0 ; index < size ; index++)
total = total + in_array[index];
A graphical representation of the address space just prior to executing the for loop in the function is given in Figure 9. You will notice that we did not pass the entire array to the function, we only passed the address of the first element which we stored in the variable in_array. This gives us access to the entire array for either reading or modification.
Note that the pointer in_array is not a constant but is a variable and can be changed because of the way we defined it.
A VERY IMPORTANT DEFINITION
The diligent student will notice that we did not say, "an array is a pointer", because that is definitely not true. We can say, "the name of an array is a constant pointer to the first element of the array." Be sure you understand that statement completely because it could save you a lot of grief some time in your programming future. This is carefully defined in the document entitled Arrays and Pointers in this group of documents.
PASSING A STRUCTURE TO A FUNCTION
We finally come to the big one, passing a structure to a function. We will begin as usual, by defining a very simple program containing the construct of current interest. In this case we declare a trivial structure that can store each of the elements of a date, and write a program that passes a date variable to a function and passes a pointer to the date structure to another function.
struct date birthday;
birthday.month = 7;
birthday.day = 4;
birthday.year = 1776;
use_struct(birthday); /* Pass the full date */
use_point(&birthday); /* Pass a pointer to the date */
void use_struct(struct date in_date)
/* Use the date passed in */
void use_point(struct date *in_date)
/* Use the date pointer passed in */
The first thing you should notice is that the entire date structure named birthday is stored on the stack because it is defined locally. A lot of large structures defined in this way will use up the stack very quickly. This should encourage you to use dynamically allocated memory as often as possible for large entities. The heap is much larger than the stack on most systems.
When we execute the use_struct() function, the entire date structure is passed into the function. A structure is allocated on the stack with storage space for each of the three variables, and the three values from birthday are copied into their respective spaces. The function then has access to each of the three variables which it can read or modify, and the structure is removed from the stack when control returns to the calling program. It should be apparent that this could be very inefficient for a large structure since it could take a relatively long time to build the structure and copy the data from the actual parameter. Figure 10 gives a graphical representation of the memory space of the computer when we have just entered the function use_struct(), but before we have changed any of the values of the date structure on the stack.
Calling use_point() with a pointer to the structure is very similar to the previous example program, so very little needs to be said about it except to examine figure 11 for a graphical representation of the memory space just after entering the function. Note that the pointer named in_date is actually pointing to the entire structure that was already on the stack so a new structure does not have to be built and initialized. It looks like it is pointing to only the month variable, but it is actually pointing to all three components of the date structure. This is a weakness with this particular graphical notation.
You must keep in mind that the representation of the stack in each of these examples has been greatly simplified. We only showed those elements on the stack that were needed to accomplish the task of passing parameters to a function. In addition to what we depicted, the return address may be placed on the stack that tells the system where to return to following execution of the function. There may also be some frame pointers used internally by the function, and there may be some wasted bytes that are used only as fillers to provide some particular byte alignment. Careful byte alignment can make the resulting program much more efficient in execution time by wasting a few bytes here and there.
All of this is taken care of by the compiler transparently to you, but you should be aware that some other operations are taking place that you have no control over.
I trust that you found this information useful.