NEXT UP previous
Next: Thread Scheduling

Calling C Functions

In order to understand the operation of the threads library, it is first necessary to look at how C programs implement function calls, and at how they deal with passing parameter values.

Every C program starts by executing the function main(). In fact, when you compile a C program and link it for execution, a system specific piece of code is bolted onto the front of your program which deals with any command line parameters and performs a call to the program's main() function.

Funcions are called in the same way as subroutines are called in machine code, that is, a return address is pushed onto the machine stack and then control is transferred to the start of the called function. When this function terminates, the return address on the top of the stack is used to get back to the calling function at the instruction following the subroutine call.

In fact, not only is the stack used to store function return addresses, but it is also used for passing function parameter values and for holding the values of automatic local variables declared within functions. This means that some care must be taken not to confuse any values stored on the stack, as any form of stack corruption can lead to some pretty tricky bugs.

When you call a function, any parameter expressions, whose values you will pass into the function, are first evaluated and their values pushed onto the stack. Next, a machine code call is made to the function which also results in a return address being stacked. At the top of the called function, the names of any parameter variables specified, are used to label the appropriate positions on the stack where the parameter values have already been pushed. In this way, any parameter values are automatically assigned to their respective parameter variables.

Once inside the function proper, the value of one of the processor registers (ebp in fact, which is the extended base pointer register) is also pushed onto the stack because that register is going to be used as a place holder to be able to find the return address on the stack when the function terminates. The value of ebp on entering the function, is pushed right next to the return address and then the current stack pointer value (pointing to the ebp value just pushed) is copied into the ebp register, overwriting its previous value, which is now safely stored on the stack.

After ebp is dealt with, the stack pointer is decremented to allow space on the stack for any automatic local variables declared within the function body. Decrementing the stack pointer is the correct operation to perform to make space on the stack because the stack grows from high memory addresses downwards. That is, the stack pointer is decremented when pushing values onto the stack and incremented when popping them off again.

When a function terminates, all the values on the stack related to the operation of that function need to be discarded in order to get the stack back to the state it was in before the function call occurred. This is done in four stages:

To illustrate the operation of the stack during the execution of a program, we will look at two snap-shots of the stack taken during the operation of the following very simple piece of code:

	main()
	{
		int x, y;

		x = 6;
		/* snap-shot one here */
		y = twice(x);
	}



	twice(int n)
	{
		int r;

		r = 2*n;
		/* snap-shot two here */ 
		return r;
	}

The first snap-shot (see Figure 1) is taken inside the main() function just before the call to the twice() function. It shows the return address that was pushed onto the stack when the main() function was called, the original value of ebp pushed next on the stack, with the ebp register set to point to this stack location, the stack locations set aside for the local variables x and y, the current position of the stack pointer, and the value of 6, assigned to the variable x on the stack.

The second snap-shot (see Figure 2) is taken at the end of the twice() function, just before the execution of the return statement. It shows that, after the stack frame for the main() function, the value 6 was pushed onto the stack (as the value of the parameter passed into the twice() function), which was later labeled with the name n. After this, the machine code call to the function twice() caused another return address to be pushed onto the stack. This was followed by stacking the value in ebp and updating the ebp register to point to the saved value. By this mechanism, the ebp register has effectively been set up as the head pointer to a linked list of function stack frames, all stored on the stack. Finally, the variable r is allocated space on the stack to which the calculation subsequently assigns the value 12.


NEXT UP previous
Next: Thread Scheduling