5.7.5. Parameter Passing


Call by value

Normally, when a parameter is passed from a caller to a function, the value of that parameter is copied into a variable in the function's frame. For example, if successor is defined by

  int successor(int n)
  {
    return n+1;
  }
then statement
  r = successor(5);
is performed as follows.

  1. A frame for successor is created in the run-time stack. Within that frame is a spot to store parameter n.

  2. Parameter n in the new frame is set to hold 5.

  3. The body of successor begins running. It computes n+1, or 6.

  4. Successor returns 6. The result is copied into a place where the caller can get it. Then the frame for successor is destroyed, and the caller resumes.

  5. The caller gets the result of successor and stores it into variable r.

C++ allows a function's body to change the value of a call-by-value parameter. It only changes the variable in the function's frame, and has no effect other than that. But the coding standards for this course require a function not to change the value of any call-by-value parameter.


Call by reference

If you write an & character after the type of a parameter in a function heading, the parameter is said to be a reference parameter, or to use call by reference.

When the function is called, the parameter passed to it must be a variable, and that variable's memory address is passed to the function. Any time the function's body uses the parameter, it uses the variable at the address that was passed. For example, suppose that inc is defined as follows.

  void inc(int& n)
  {
    n = n + 1;
  }
Then statements
  int w = 1;
  inc(w);
are performed as follows.

  1. Variable w is created and initialized to 1.

  2. A frame is created for inc. In that frame there is a spot for parameter n.

  3. The memory address of the caller's variable w is stored in n in the new frame. Any reference to n will use variable w.

  4. The body of inc begins to run. It needs to get the value of n so that it can add 1 to it. But n holds a memory address. The value at that memory address (which is the value of the caller's variable w) is fetched.

  5. Expression n+1 evaluates to 2. Now inc needs to store that into n. But since n is a reference parameter, it is a memory address. Value 2 is stored in the memory at that address (which is where w is stored). So w is set equal to 2.

  6. inc returns to its caller and its stack frame is destroyed.


Call by pointer

This part requires that you understand pointers.

Since call by reference passes a memory address, it is actually passing a pointer. But the pointer is hidden from you; it is taken care of by the compiler.

You often prefer to use an explicit pointer, and to manage the pointer yourself. Here is a version of inc (renamed incp) that passes an explicit pointer.

  void incp(int* n)
  {
    *n = *n + 1;
  }
If you compare that to the version of inc that uses & instead of *, you will see that this version has a * on each occurrence of n. What call by reference does is insert those asterisks implicitly for you.

To use incp, you must pass an explicit pointer. For example,

  int w = 1;
  incp(&w);
does that by using the & operator to get the address of a variable. With call by reference, the & is added for you at each place where you call the function.

So call by pointer is similar in spirit to call by reference, the difference being that pointers are dealt with behind the scenes with call by reference, but explicitly with call by pointer.

Call by pointer is really a special case of call by value where the value is a pointer.

When you pass an array as a parameter, you pass an explicit pointer. That is because notation A[i] is intended to work on explicit pointers, not on implicit ones.

Call by reference is available in C++ but not in C. In C, you must pass an explicit pointer instead of an implicit one. (optional)


Logical calling modes

Call by value and call by reference are mechanisms for passing parameters. But you often prefer to think in terms of what the parameters are used for from a more conceptual viewpoint. There are three logical calling modes.

  1. An in-parameter is information being passed from the caller to the function. For small things, such as integers or real numbers, you usually use call by value for an in-parameter. For example, if you think about computing sqrt(16.0), the caller tells sqrt which number (16.0) it wants the square root of.

    For larger pieces of information, you usually prefer to pass a pointer because you want to avoid copying all of that information. Typically, you pass a structure by reference, but pass an array as a pointer. But, to indicate that the parameter is an in-parameter, you write const in front of the parameter in the function definition heading. For example, the following function wants to look at the values in an array; it does not want to change what is in the array.

      // sum(A,n) yields the sum A[0] + A[1] + ... + A[n-1]
    
      int sum(const int* A, int n)
      {
        int result = 0;
        for(int i = 0; i < n; i++)
        {
          result += A[i];
        }
        return result;
      }
    

  2. An out-parameter represents information that is passed from the function back to its caller. The function accomplishes that by storing a value into that parameter. Use call by reference or call by pointer for an out-parameter. For example, the following function has two in-parameters and two out-parameters.

      // divide(x,y,q,r):
      //   x, y: in-parameters
      //   q, r: out-parameters
      // It sets
      //   q = the integer quotient when you divide x by y.
      //   r = the integer remainder when you divide x by y.
      //
      // This function uses the standard mathematical definition
      // of integer division, which requires that
      //
      //   y > 0
      //   0 <= r < y
      //   q*y + r = x
    
      void divide(int x, int y, int& q, int& r)
      {
        if(x >= 0)
        {
          q = x/y;
          r = x % y;
        }
        else
        {
          r = y - ((-x) % y);
          q = (x - r)/y;
        }
      }
    

    When an array is an out-parameter, where you want to store information into the array, pass it by pointer, not by reference. That is, just pass the array, since it is already a pointer.

    If you want to create an array and store the new array into a parameter for the caller to use, pass the pointer and by reference. For example, a function with heading

      int makeArray(int n, double*& A)
    
    can store a new array into A; the caller can get that array from the corresponding argument variable in the call.

  3. An in-out-parameter is used both to pass information to the function and to get new information from the function. For example, in function inc above, n is an in-out parameter since the function body (1) looks at the value of n before storing anything into it, and (2) stores a new value into n. Use call by reference or call by pointer for an in-out parameter.


Exercises

  1. Can you use call-by-value for an out-parameter? Answer

  2. Are all in-parameters passed using call by value? Answer

  3. Where are a function's call-by-value parameters stored? Answer

  4. What does jump( ) return, where jump is writtten below, using function jumpHelper?

     void jumpHelper(int x)
     {
       x = x + 1;
     }
    
     void jump()
     {
       int z = 40;
       jumpHelper(z);
       return z;
     }
    
    Answer

  5. What does hop( ) return, where hop is writtten below, using function hopHelper?

     void hopHelper(int& x)
     {
       x = x + 1;
     }
    
     void hop()
     {
       int z = 40;
       hopHelper(z);
       return z;
     }
    
    Answer

  6. What does romp( ) return, where romp is written below, using function rompHelper?

     void rompHelper(int a, int& b)
     {
       b = a + 2;
       a = b + 1;
     }
    
     int romp()
     {
       int x = 4;
       int y = 25;
       rompHelper(x,y);
       return x + y;
     }
    
    Answer