Passing Data to Functions

When function calls have to be made, it becomes important to find out how this can be done as efficiently as possible. When you look at how functions receive their parameter data, there are basically three options to choose from:

  • Passing parameters by value

  • Passing parameters by reference

  • Using global data

This section examines in detail how to get data to functions via these three methods.

Pass by Reference Versus Pass by Value

As was shown in the previous section, parameters which are passed to a function are placed onto the stack before the function call is made. If a parameter is an integer, the integer is placed on the stack. If a parameter is a pointer to an integer, a pointer to an integer is placed on the stack. So the distinction between passing a parameter by reference or passing it by value lies in what happens to the stack when the function is called. A parameter which is passed by value is copied onto the stack in its entirety. A parameter which is passed by reference is not copied at all but a pointer containing its address is placed on the stack. Listing 8.12 shows the different ways of passing function arguments by value and reference.

Code Listing 8.12. Declaration and Use of Reference and Pass-by-Value Function Arguments
// Passing by value:

void FunctValue(int input1, int input2)
{  input1 += input2;}

// Passing by reference:

void FunctRef(int &input1, int &input2)            // using references.
{  input1 += input2; }

void FunctPoint(int *input1, int *input2)          // using pointers.
{  *input1 += *input2; }

void main(void)
{
   int a = 6, b = 7;

   FunctValue(a, 5);    // values 6 and 5 placed on stack.
   FunctRef(a, b);  // addresses of 'a'and 'b'placed on stack.
   FunctPoint(&a, &b);  //
							
							
							 addresses of 'a'and 'b'placed on stack.
}

Note that passing an object by reference does in fact exactly the same thing as passing an object by a pointer; the only difference is in the notation of the function call and the way the implementer can refer to the object in the body of the function: input1 versus *input1.

There are two reasons why you want to be able to pass a parameter by reference:

  • When an object is relatively large it is a much better idea to place its address on stack than the entire object itself. Placing a large object on stack not only costs runtime footprint, it also costs time.

  • When a function receives a reference to an object, it can permanently change the value of that object. In Listing 8.12 the functions FunctRef() and FunctPoint() will change the value of variable a in the scope of the calling function, in this case main(). After FunctRef(a,b) the variable a equals 13, then. After FunctPoint(&a, &b) the variable a equals 20 (13 + 7). The first call—FunctValue()—however, does not change the value of variable a. In fact, FunctValue() does nothing—that is, it changes the value of input1 which is a variable on the stack and which disappears after FunctValue() ends.

There is another implication to consider, namely the access to parameters. When a parameter is passed by value, it is part of the local function stack. This means its value can be accessed directly. A parameter which is passed by reference has to be dereferenced before its value can be accessed. This means the pointer of the variable is taken and used to find the value of the variable. Accessing value parameters is thus faster than accessing pointer/reference variables. Listing 8.13 shows this through the assembly that is generated for Listing 8.12.

Code Listing 8.13. Assembly Listing Showing Parameter Access for Value Arguments Versus Reference Arguments
// FunctValue Body.
input1 += input2;

    mov eax, DWORD PTR _input1$[ebp]    ; get value of input1.
    add eax, DWORD PTR _input2$[ebp]    ; add value of input2.
    mov DWORD PTR _input1$[ebp], eax    ; place addition back in
;   input1.
; value of input1 is subsequently lost.

// FunctRef & FunctPoint Bodies.
input1 += input2; or *input1 += *input2;

    mov eax, DWORD PTR _input1$[ebp]    ; get pointer to 'a'.
    mov ecx, DWORD PTR [eax]            ; get value of 'a'.
    mov edx, DWORD PTR _input2$[ebp]    ; get pointer to 'b'.
    add ecx, DWORD PTR [edx]            ; add value of 'b'.
    mov eax, DWORD PTR _input1$[ebp]    ; get pointer to 'a'.
    mov DWORD PTR [eax], ecx            ; place result back in 'a'.
; value of input1, as well as original variable 'a', has
							
							
							 been changed.

Choosing between passing parameters by reference or by value to time-critical functions is perhaps not as trivial as you might think. Certainly a reference or a pointer must be used when the passed parameter should be permanently changed by the function. When a parameter is passed for read-only use, a choice needs to be made which is based on the following two characteristics:

  • Size of the parameter to be passed

    When our parameter is a compound object (structure, array, class, and so on) it will most often be large enough to warrant passing it by reference; a quick sizeof(object_to_pass) will tell you exactly how many bytes will be pushed onto stack when you pass object_to_pass by value. However, it is not a fair conclusion that passing a parameter by pointer or reference is always better where stack footprint is concerned. Passing a character by value causes only a single byte to be placed on stack, for instance, where passing a character by reference causes at least four bytes to be placed on stack!

  • Number of times the parameter is accessed.

    When a read-only parameter—or any of its compounded members—is accessed frequently within the function, accessing overhead as demonstrated by Listing 8.13 will begin to influence function performance. In some cases it might be better to make a local copy of the values which are accessed most often:

    int in = *input1;    // make a local copy of the value pointed to by
                         //  input1.
    

    But when the whole parameter—or many of its compounded members—is accessed frequently, it becomes a performance/footprint tradeoff whether or not to pass the parameter by value.

Global Data

The previous section demonstrated how the kind and number of parameters passed to a function can influence function performance. The use of global data makes it possible to decrease the amount of data placed on the stack for a function call. Global data is data that can be accessed by all functions of a program; thus, global data does not have to be passed in a function call. This makes the call itself faster as well as the access to the data, compared to data that is passed by reference. When this globally defined data is used by several functions (but does not become the subject of a multiple access dispute between threads), defining the data globally is even more useful.

Listing 8.14 takes a structure with authentication data—consisting of a username, a password and a registration code—and checks whether this data is valid. For this, four different functions are created:

  • int CheckAuthentication_Global()

    Takes a globally defined instance of the authentication structure and evaluates its validity. No stack data is used.

  • int CheckAuthentication_ByValue(Authentication value)

    Takes an authentication structure by value and evaluates its validity. This instance of the structure is copied completely to stack.

  • int CheckAuthentication_ByReference(Authentication &ref)

    Takes an authentication structure passed by reference and evaluates its validity. A pointer to the instance of the structure is placed into the stack.

  • int CheckAuthentication_ByPointer(Authentication *ref)

    Takes an authentication structure passed via a pointer and evaluates its validity. A pointer to the instance of the structure is placed into the stack.

The timing results of the different functions are presented at the end of this section:

Code Listing 8.14. Comparing Calling Techniques
#define OFFSET 10       // secret value used in authentication check.

// Structure to pass as data to the functions.
struct Authentication
{
    unsigned char regCode[21];
    unsigned char user[7];
    unsigned char password[7];
} ;

// Globally defined instance of data, used in the global version of the
//  CheckAuthentication function.
Authentication glob =
       { { 0,3,1,0,0,4,0,4,2,1,0,5,0,2,1,0,5,0,2,} , "Bond01","shaken"} ;


// Global function.
int CheckAuthentication_Global()
{
    int result = 1;

    glob.user[result] = result;
    for (int i = 0; i < 7; i++)
    {
        if (glob.user[i] + glob.password[i] !=
             (glob.regCode[i*3]-OFFSET))
        {
            result = 0;
            break;
        }
    }

    return
							
							
							 result;
}

// Pass-By-Value function.
int CheckAuthentication_ByValue(Authentication value)
{
    int result = 1;

    value.user[result] = result;

    for (int i = 0; i < 7; i++)
    {
        if (value.user[i] + 
							
							
							value.password[i] !=
            (value.regCode[i*3]-OFFSET))
        {
            result = 0;
            break;
        }
    }

    return result;
}

// Pass-By-Reference function.
int CheckAuthentication_ByReference(Authentication &ref)
{
    int result = 1;

    ref.user[result] = result;
    for (int i = 0; i < 7; i++)
    {
        if (ref.user[i] + ref.password[i] !=
            (ref.regCode[i*3]-OFFSET))
        {
            result = 0;
            break;
        }
    }

    return
							
							
							 result;
}

// Pass-By-Reference -using a pointer- function.
int CheckAuthentication_ByPointer(Authentication *ref)
{
    int result = 1;

    ref->user[result] = result;
    for (int i = 0; i < 7; i++)
    {
        if (ref->user[i] + ref->password[i] !=
            (ref->regCode[i*3]-OFFSET))
        {
            result = 0;
            break;
        }
    }

    return
							
							
							 result;
}

A program using Listing 8.14 can be found in the file 08Source04.cpp on the Web site. The result of the program is a list of relative numbers indicating the execution speed of the various data passing techniques used. Table 8.1 shows these timing results.

Table 8.1. Calling Technique Timing Results
Technique Relative Time Spent
Calling by reference 220
Calling via a pointer 220
Calling by value 380
Using global data 170

The results presented in Table 8.1 take into account different calling methods as well as variable access for the different calling methods. From this table, you see that calling by pointer and calling by reference are indeed equally fast. As expected, calling by value is quite a lot slower. The clear winner is the function which uses global data.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset