You probably recognize that the program in Listing 15.3.1 should have a subroutine to assign values to the fields in each struct. The general rules for passing arguments to functions are:
An input is passed by value.
An output is passed by reference.
While C++ supports pass by reference for output arguments, C does not. In C, a pass by reference is simulated by passing a pointer to the variable to the function. Then the function can change the variable, thus effecting an output. At the assembly language level, pass by reference is implemented in C++ by passing a pointer, so these two rules can be restated:
An input is a copy of the original value.
An output provides the address of the original value.
Some languages, e.g., ADA, also support passing an “update.” In this case the function replaces the original value with a new value that depends upon the original value. Passing an argument for update is also implemented by passing its address.
There is an important exception to the rule of passing a copy for inputs. When the amount of data is large, making a copy of it is inefficient. So we organize it into a single entity and pass the address of that entity.
The most common example of this is an array. In fact, it is so common that in C arrays are automatically passed by address. Thus, in C/C++
void f(int a, int b[]);
----
int x;
int y[100];
----
f(x, y);
----
will cause x to be passed by value and y to be passed by address.
Another common example of passing a possibly large amount of data as input to a function is a struct. Of course, not every struct is large. And it is possible to pass the value in a single struct field, but the main reason for organizing data into a struct is usually to treat several pieces of data as a more or less single unit.
Since a struct, unlike an array, is not automatically passed by address in C, we must use the address-of operator (&) on the name of the struct variable if we wish to avoid making a copy of the entire variable on the stack. The technique is exactly the same as passing an address of a simple variable.
Listing 15.4.1 shows a solution to the program in Listing 15.3.1 that uses a separate function to fill each struct.
The loadStruct function needs to specify that the first argument, aStruct, is a pointer to (address of) an entity that has the type struct theTag by using the dereferencing operator, ‘*’. Within the function, we need to dereference the pointer to the struct so we can access each field. This can be accomplished with:
The parentheses are required here because the field selector operator, ‘.’, has higher precedence than the dereference operator, ‘*’. This is a clumsy syntax, so C/C++ provides a much nicer syntax:
Both syntaxes are equivalent. They first dereference the pointer to the struct and then access the field by name.
When using a separate function to store values in the fields of the struct, the compiler generates code that uses a pointer to access the struct, as shown in Listing 15.4.4.
I have simplified the loadStruct function in my solution, as shown in Listing 15.4.6.
Notice that I have defined the struct field names in a separate file, theTagStruct.s. I use the .include assembler directive to include this file wherever I need to use the names.
It is tempting at this point to develop an expression to automate the computation of the value of locals in the main function. As pointed out in Section 10.5.2 this would probably be done in a production environment. But I found it to be a little tricky to do it in such a way that easily allowed for changes to this program, which is the only reason to automate the computation. I still had to draw a picture of the stack frame, and at that point I had the numbers I needed.
The prologue and epilogue in loadStruct are not really needed in this simple function. But it is good to get in the habit of coding them into all your functions. It certainly has a negligible effect on execution time, and they help establish a structure to the function if it is ever changed.