Close
Register
Close Window

OpenDSA Stand-alone Modules

Chapter 0 modules

Show Source |    | About   «  0.256. Tying The Knot   ::   Contents   ::   0.258. Lazy Lists  »

Parameter-Passing Mechanisms

1. Call By Value vs. Call By Reference

Author's Note: All of the visualizations of parameter-passing methods in this were developed by University of Wisconsin Oshkosh CS major Cory Sanin. His work on these has greatly improved the original version of the module.

Parameter-passing techniques may be broken down as follows:

  • Eager evaluation (applicative order) techniques. What these methods have in common is that the arguments passed in for a function's parameters are evaluated before the function is called.
    • Call-by-value
    • Call-by-reference
    • Call-by-copy-restore (also known as value-result, copy-in-copy-out)
  • Lazy evaluation (normal order) techniques. What these methods have in common is that the evaluation of the arguments passed in for a function's parameters is delayed until the argument is actually used in the execution of the function.
    • Macro expansion
    • Call-by-name
    • Call-by-need

The difference between call-by-value and call-by-reference is exemplified by the difference between denoted values in our interpreters for SLang1 and SLang2. That is, in call-by-value, the argument for a function parameter is a copy of the value of the argument whereas, in call-by-reference, the function is given the address of the argument. Given the address, the function has the capability of modifying the argument.

To see how call-by-value works, step through a few sample programs using the slideshow generator below. Once you're confident that you understand each step, test yourself with the proficiency exercise that follows.

Test yourself on call-by-value by completing the following proficiency exercise.

In comparison to call-by-value, call-by-reference is illustrated by the following slideshow generator. Again step through a few of the generated slideshows until you're ready for the proficiency exercise that follows.

Test yourself on call-by-reference by completing the following proficiency exercise.

Now that you've seen the difference between call-by-value and call-by-reference, we will end this section with a problem that will help you review the difference between call by value and call by reference in the language C++, where the presence of an ampersand in front of the parameter's name is used to indicate call-by-reference semantics. To earn credit for it, you must complete this randomized problem correctly three times in a row.

2. Copy-Restore

In copy-restore parameter passing, the function is still given the address of the argument, as it was in call-by-reference. However, the protocol for this technique dictates that the function make a copy of the argument before executing the function body. This copy is then worked with in the function body. When the function body has completed, the protocol for copy-restore dictates that the copy of the argument be "restored into" the original argument using the address of the argument, hence potentially modifying that argument. Note that although the original argument is modified, the timing of when the modification occurs is slightly different from what it was under call-by-reference semantics. In the Ada programming language, the programmer could choose to use copy-restore semantics by designating a parameter as an in-out parameter. Although C++ does not offer copy-restore as a parameter-passing technique, we can simulate it in the following C++ code.

#include <iostream>
using namespace std;

void by_value(int a, int b) {
  a = b;
  b = 6;
}
void by_reference(int &a, int &b) {
  a = b;
  b = 6;
}
void by_copy_restore(int &a, int &b) {
  int copya, copyb;
  copya = a;       // copy-in phase
  copyb = b;
  copya = copyb;   // function proper
  copyb = 6;
  a = copya;       // copy-out phase
  b = copyb;
}
int main() {
  int x,y;
  x = 4; y = 5;
  by_value(x, y);
  cout << "Call-by-value semantics: " << x << " " << y << endl;
  x = 4; y = 5;
  by_reference(x, y);
  cout << "Call-by-reference semantics: " << x << " " << y << endl;
  x = 4; y = 5;
  by_copy_restore(x, y);
  cout << "Call-by-copy-restore semantics: " << x << " " << y << endl;
}

As you've done with by-value and by-reference, use the following slideshow generator to step through a few examples of the copy-restore method and then test yourself by working on the proficiency exercise that follows.

And next test yourself with a copy-restore proficiency exercise.

We've now covered the three parameter-passing methods that use eager evaluation of function arguments. To compare and contrast these three methods, figure out what the output of the program in the next practice problem would be under call by value, call by reference, and call by copy-restore. Doing this should clarify the subtle differences among these three methods. To earn credit for the practice problem, you must complete it correctly for the randomized program it generates three times in a row.

3. Macro Expansion

Call-by-value, call-by-reference, and call-by-copy-restore all use eager evaluation: The arguments of a function call are evaluated immediately, that is, even before the body of the function is executed.

The remaining three parameter-passing mechanisms use lazy evaluation: The arguments of a function call are passed without being evaluated to the function. Then, during the execution of the function’s body, the parameters are evaluated only when, and as often as, they are needed.

The first lazy-evaluation technique we will discuss is macro-expansion.

Steps involved in macro-expansion are:

  1. No evaluation: The literal text of each argument in the macro call is substituted for the corresponding formal parameter everywhere in the macro’s body.
  2. No evaluation: The body of the macro's code resulting from Step 1 is textually substituted for the macro call in the caller program.
  3. Evaluation: The body of the macro is executed in the caller’s environment. That is, because of the textual substitution of the macro's code in the caller program, the scope of the variables involved is determined on the basis of where the macro is called from rather than where the definition of the macro appears in the program. You will see this in the second step of the following slide show, where the code resulting from Step 1 and Step 2 above is presented side-by-side with the original code.

Once you have observed enough example slideshows to fully understand the details of each step in macro-style parameter passing, test yourself with the following proficiency exercise.

We will finish this section on macro-style parameter passing by considering the use of macros in C++, where a parameter like a or b in the example below must be wrapped in parentheses when it is actually used in the body of the macro. Try to determine the output of the main program in each example.

#include <iostream>

using namespace std;

#define by_macro( a, b )  { (a) = (b); (b) = 6; }  // Note parens around use of parameter

int main()
{
  int x,y;

  x = 4; y = 5;
  by_macro(x, y);
  cout << "Call-by-macro semantics: " << x << " " << y << endl;
}
#include <iostream>

using namespace std;

#define by_macro( a, b )  \
         { (a) = (a) + (b); (b) = (a) - (b); (a) = (a) - (b);  }  // Again parens wrap use of param

int main()
{
  int x,y;

  cout << "\nNo aliasing" << endl << endl;
  x = 4;  y = 5;
  by_macro(x, y);
  cout << "Call-by-macro semantics: " << x << " " << y << endl;

  int z;
  cout << endl << endl << "With aliasing" << endl << endl;
  z = 4;
  by_macro(z, z);
  cout << "Call-by-macro semantics: " << z << endl;
}

Implementation of macro-expansion in C++

The implementation of macro-expansion suggested by the 3-step process described previously is to perform a double textual substitution. For example, the C++ pre-processor performs this double substitution, and then the compiler processes the resulting code, never seeing the macro call. Of course, no function call is executed at run-time either.

Because the body of the macro is, at least conceptually, spliced into the caller’s code after the arguments have been substituted (without being evaluated) for the parameters, the whole body of the macro is executed in the caller’s environment. This allows us to use macro-expansion to simulate dynamic scoping, as illustrated in the following code.

#include <iostream>

using namespace std;

int n = 6;

#define dynamic_scoping  { cout << n << endl; }

void static_scoping()    { cout << n << endl; }

void test_dynamic() {
  int n = 5;
  cout << "Using dynamic scoping --> ";
  dynamic_scoping;
}

void test_static() {
  int n = 5;
  cout << "Using static scoping --> ";
  static_scoping();
}

int main() {
  test_dynamic();
  test_static();
}

This problem will help you review the differences among call by reference, call by copy-restore, and call by macro. To earn credit, you must complete this randomized problem correctly three times in a row.

4. Call By Name

In macro expansion, the body of the macro is spliced into the caller's code after the actual parameters have been substituted (without being evaluated) for the formal parameters. Therefore, the whole body of the macro is executed in the caller's context (i.e., the caller's environment).

In call-by-name, no code is spliced into the caller's code. Instead, the body of the function is executed in its own context, but the actual parameters, which are substituted for the formal parameters, are evaluated in the caller's context.

Call-by-name differs from macro expansion in that only the parameters are evaluated in the caller's context, not the whole body of the function. Step through a few slide shows of some call-by-name examples to see what the ramifications of this are. When you are confident that you understand the subtleties involved, try the proficiency exercise that follows.

Now it is time for you to do a proficiency exercise to see how well you understand call-by-name. When you do this proficiency exercise, each assignment statement will require two steps. In the first step corresponding to an assignment statement, you will have to compute the value on the right-hand side and then click the location where that value will be stored. In the second step, you will have to click on a potentially new pointer destination resulting from the computation and assignment that comprised your answer for the first step.

This problem will help you review the differences among call by copy-restore, call by macro, and call-by-name. To earn credit for it, you must complete this randomized problem correctly three times in a row.

5. Comprehensive review of the five methods studied so far

In the next section, we will examine call-by-name versus call-by-need in greater depth in the context of a specific example known as a lazy list. However, before proceeding test your comprehensive understanding of all five techniques studied so far: call-by-value, call-by-reference, call-by-copy-restore, call-by-macro, and call-by-name. To earn credit for it, you must complete this randomized problem correctly three times in a row.

   «  0.256. Tying The Knot   ::   Contents   ::   0.258. Lazy Lists  »

nsf
Close Window