[Home]

Pass by Reference

“Pass by reference” means that a parameter (callee scope) acts as an alias to the passed argument variable (caller scope); that is, when the parameter is assigned to, it “writes through” to the argument in caller scope, since both the parameter and argument occupy the same place in memory. This type of argument passing essentially acts like an implicit pointer; note, however, that pass by pointer is different, and a type of pass by value (see definition of pass by value below for discussion).

This type of argument passing does not appear in C, but it does appear in C++ with the & parameter modifier:

#include <iostream>

void pass_by_reference(int &a) {
    a = 4;
}

int main() {
    int b = 8;
    pass_by_reference(b);
    std::cout << b << endl;
    return 0;
}

The above program will print out 4 since when we assign a in pass_by_reference to 4, it also modifies b in main, since they are essentially the same variable.

Pass by Value

“Pass by value” means that a parameter (callee scope) is a copy of the passed argument variable (caller scope); that is, when the parameter is assigned to, it does not affect the argument variable in caller scope, since the parameter and argument variables occupy different places in memory. This is acheived in C by allocating memory on the stack for the parameter and copying the value of the argument to the newly-allocated memory. This is why, historically, structs could only be passed by pointer: stack management was done with fixed offsets for parameters.

What is “pass by pointer?” Pass by pointer is a method which can allow a pass by value system to still affect the arguments in caller scope by passing pointers to the arguments by value. By assigning to the dereference of the pointer, the program can acheive pass by reference-type effects. However, if the pointer is directly assigned to another memory location, it effectively “decouples” the parameter and argument.

All argument passing in C is pass by value:

#include <stdio.h>
#include <stdlib.h>

#define ALLOC_INT (malloc(sizeof(int)))

void pass_by_value(int a) {
    a = 4;
}

void pass_by_pointer(int* b, int* c) {
    *b = 8;
    c = ALLOC_INT;
    *c = 15;
}

int main() {
    int d = 16;
    int* e = ALLOC_INT;
    int* f = ALLOC_INT;
    *e = 23;
    *f = 42;
    pass_by_value(d);
    printf("d = %d\n", d);
    pass_by_pointer(e, f);
    printf("*e = %d\n*f = %d\n", *e, *f);
}

The above program will print out:

d = 16
*e = 8
*f = 42

The value of d has not been modified since a in pass_by_value exists in a separate location in memory, and as such its assignment does not “write through” to d. The value pointed to by e has been modified since, although the direct value of b exists in a separate location in memory to e, their value is the same, and as such, they both point to the same place in memory, and so assigning to the dereference of b modifies the value pointed to by e.

Another language that only uses pass by value is Java. Although non-primitive types in Java are called “reference types,” reference types in Java are passed by pointer, hence why the following program outputs 8:

class A {
    int c;

    A(int c) {
        this.c = c;
    }

    static void pass_by_pointer(A a) {
        a = new A(4);
    }

    public static void main(String[] args) {
        A b = new A(8);
        pass_by_pointer(b);
        System.out.println(b.c);
    }
}

Java actually uses a stack system like C, and like historical C, it uses fixed offsets. This is why primitives in Java always use pointer-width memory in Java unless in an array. This simple fixed-offset system provides some advantages such as faster reflection, which is very important for the powerful runtime optimization of the HotSpot VM.


© Emberlynn McKinney