Parameter Passing: Pass-by-Value
C, much like Java, uses the pass-by-value mechanism. When a function is called, the function's parameters are copies of the arguments that were passed in -- they are not the same variables. The mechanism is called "pass by value", because the values of the arguments are passed in -- not the arguments themselves. The behavior that results from this should not be a surprise to you -- it is the same mechanism as was used in Java.The classic example of this is the doesn't-really swap() function:
int swap (int x, int y) { int temp = x; x = y; y = temp; printf ("%d %d\n", x, y); /* 7 5 */ } ... int main () { int a = 5; int b = 7; printf ("%d %d\n", a, b); /* 5 7 */ swap (a, b); printf ("%d %d\n", a, b); /* 5 7 */ }Notice that, in the example above, the swap() function does swap the values of "x" and "y", its parameters. But, it does not affect, "a" and "b", main's local variables that are passed in. Please also note that renaming "a" and "b" to "x" and "y" wouldn't change this. Although main()'s two local variables and swap()'s two arguments would happen to have the same names -- they would remain different variables.
The Runtime Stack
Let's talk a bit more about function calls and variables. Each time a function is called, it's parameters and local variables are created. When the function returns, they go away. For this reason, once upon a time, local variables were known as automatic variables. This nomencalture isn't presently favored -- but remains valid and correct.Each time a function is called, execution jumps to a completely different part of the program, and entirely new variables exist. These variables might have the same names as other variables in the program, but they are not the same -- they can have different values. And, once the function returns, these local variables "go away" and execution picks right up where it left off. Additionally, arguments need to be communicated to the function when called, and a return value needs to be communicated to the caller, upon return.
The compiler does this using a stack, often called the runtime stack. When a function is called, the compiler pushes the parameters onto the stack, followed by the return address. It then pushes empty space (allocates space) on the stack for all of the local variables. This state, which is stored on the runtime stack, and is associated with a single activation of a function, is known as a stack frame.
When the function returns, the compiler pops its frame from from the stack, revealing the return address, and stores the return value on the stack. Upon return, the caller then reads the return value from ahead of the stack.
Additionally, there is plenty of other state information associated with the activation of each function that is stored within the stack frame. For example, if there are only a few parameters, they are often passed in registers, fast memory within the CPU. These registers need to be saved to and restored from the stack when a function is called and upon its return. There are also some pieces of metadata about the stack, itself, such as pointers to the beginning of each stack frame, &c. And, to be honest, technically speaking, the paramters are part of the caller's stack frame, not the callee's stack frame.
But, for this class, we're not going to get bogged down in the details -- those are covered in 15-213. But, it is critical that you realize that each instance of a function has its own parameters and local variables. It is also very important that you realize that the compiler uses a stack to manage function invocation.
Global Variables
The C Language has global variables proper. They are declared outside of any function. As a result, they are in "global space", not within a function's scope. Because the compiler processes each file as if from the top donw in a single pass, they are usually declared at the very top. The variable will appear undeclared if it is used at a location within the file before it is declared.
Before continuing, let me offer a warning. Global variables should only be used to model global state. And, global state is somewhat rare. Global variables shold never be used in place of local variables. And, global variables shold never be used instead of parameter passing. These are two common misuses that generate aweful code. Before using a global variable, ask yourself two questions: 1) Can this be a local? and 2) Can I pass it as an argument, where needed, rather than storing it globally?
The toy example below shows a global variable and its use. Notice all three functions can access the global variable:
#include <stdio.h> int global = -1; void set(int value) { global = value; } int get() { return global; } int main() { printf ("Initial: %d\n", global); set (5); printf ("After set(): %d\n", global); printf ("Result from get(): %d\n", get()); printf ("Another direct access: %d\n", global); global = 42; printf ("After main() changes the value: %d\n", global); return 0; }I've got one last note. And, I know that this will be clear to many of you, especially after reading it, but it has caused some confusion over time. Variables declared within main() are not globals -- they are local to main(). main() happens to be the entry point into a C program -- but it is also a function like any other.
Function Prototypes
Let's consider a quick little program that contains add() and subt() functions.addsub.c
#include <stdio.h> float add (float x, float y) { return x + y; } float subt (float x, float y) { return x-y; } int main() { float a, b, c; a = 5.5; b = 7.5; c = add (a,b); printf ("Sum is %f\n", c); c = subt (c,b); printf ("Difference is %f, which, incidentally, is the same as \"a\"\n", c); return 0; }As shown above, the program compiles just fine. But, if we move the add() and subt() functions below main(), the compiler barks at us:
addsub.c
#include <stdio.h> int main() { float a, b, c; a = 5.5; b = 7.5; c = add (a,b); printf ("Sum is %f\n", c); c = subt (c,b); printf ("Difference is %f, which, incidentally, is the same as \"a\"\n", c); return 0; } float add (float x, float y) { return x + y; } float subt (float x, float y) { return x-y; }Specifically, here's what it says:
addsubt.c: In function 'main': addsubt.c:10: warning: implicit declaration of function 'add' addsubt.c:13: warning: implicit declaration of function 'subt' addsubt.c: At top level: addsubt.c:19: error: conflicting types for 'add' addsubt.c:10: error: previous implicit declaration of 'add' was here addsubt.c:23: error: conflicting types for 'subt' addsubt.c:13: error: previous implicit declaration of 'subt' was hereWhat's going on? Well, at the time that the compiler encounters each of the uses of add() and subt(), it hasn't encountered the functions, themselves -- it works through the file form top to bottom. So, it doesn't know their arguments or return types. What does it do? It assumes that the types of their arguments match those that are passed in. And, it assumes that the return type is "int". It has to assume this "default" type, because it can use an assignment to determine the type -- it is after-the-fact and might not even exist.
Then, when the compiler gets down to the bottom of the file, it sees the actual function definitions -- and observes the violation between the new defintion and the "implied" definition that it earlier assumed.
Rather than constraining ourselves to define functions before their first use, which is using the tail to wag the dog, we directly address the only problem -- that the compiler doesn't know the types associated with the function. We do this by placing a type of forward reference, known as a function prototype, ahead of its first use. By convention, we put it at the top of the code. This makes the compiler happy.
Notice the prototypes in the version below. They do not happen to contain the names of the arguments. This is unnecessary -- at this stage of the game, the compiler need only know the types.
addsub.c
#include <stdio.h> /* Notice these prototypes */ float add(float, float); float subt(float, float); /* End of prototypes */ int main() { float a, b, c; a = 5.5; b = 7.5; c = add (a,b); printf ("Sum is %f\n", c); c = subt (c,b); printf ("Difference is %f, which, incidentally, is the same as \"a\"\n", c); return 0; } float add (float x, float y) { return x + y; } float subt (float x, float y) { return x-y; }