Close
Register
Close Window

Programming Languages

Chapter 6 Variations on Parameter Passing

Show Source |    | About   «  5.2. Tying The Knot   ::   Contents   ::   6.2. Lazy Lists  »

6.1. Parameter-Passing Mechanisms

6.1.1. Call By Reference

Parameter-passing techniques may be broken down as follows:

  • Eager evaluation (applicative order) techniques
    • Call-by-value
    • Call-by-reference
    • Call-by-copy-restore (also known as value-result, copy-in-copy-out)
  • Lazy evaluation (normal order) techniques
    • 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 SLang 1 and SLang 2. 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.

This problem will help you review the difference between call by value and call by reference in 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.

6.1.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 techniques dictates that the function make a copy of the parameter 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 copy of the parameter be "copied into" the original parameter using the address of the parameter, hence potentially modifying that parameter. Note that although the original parameter 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;
}

Figure out what the output of the preceding program would be before tackling the next practice problem, which consequently will help you review the differences among call by value, call by reference, and call by copy-restore. To earn credit for it, you must complete this randomized problem correctly three times in a row.

6.1.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 is substituted for the macro call in the caller program.

  3. Evaluation

    The body of the macro is executed in the caller’s environment.

Let’s look at some examples of macros in C++, where a parameter like a and b in the example below must be wrapped in parentheses when it is actually used in the body of the macro.

#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++

One possible implementation of macro-expansion is to perform a double textual substitution. For example, the C++ preprocessor 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.

6.1.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.

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.

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.

6.1.5. Comprehensive review of the five methods studied so far

This problem will help you review the differences among 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. 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.

   «  5.2. Tying The Knot   ::   Contents   ::   6.2. Lazy Lists  »

nsf
Close Window