Confusion between the term “reference” and “pointer”

A few years ago, I wrote an article saying that Java has pointers. However, by typing Java pointer into Google, there are a lot of Stack Exchange questions shown on the top, some says Java does not have pointers, some says Java has pointers, and some even says Java references are pointers. For example, the top two answers of this question say “it is impossible to use pointers in Java” and “there are pointers in Java” respectively.

I think it is caused by the confusion of the definition what the term “pointer” means. Wikipedia says that “a pointer is a programming language object that stores a memory address.” Even if we take this definition, it already creates confusion.

In C++, we can write the following code:

Dog dog; // create a new Dog object on the stack as the variable "dog"
Dog *p = &dog; // take the address of the object represented by the variable "dog" and store into variable "p"

In C++ object model, there is a distinction between an “lvalue” and an “rvalue”, which originates from C. In C, an “lvalue” is something which can appear on the left hand side of an assignment operation (i.e. something which can be assigned to), while an “rvalue” is something which can’t. The distinction between an “lvalue” and an “rvalue” is that, an “lvalue” represent an object in a particular location of the memory and you can apply the & operator to take its address and assign it into a pointer variable, and given a pointer value, you can apply the * operator to get the object inside that address, which is an lvalue which can be assigned to, in contrast to the result of arithmetic operators which isn’t stored in memory and can’t be assigned to. Therefore I prefer to call an lvalue expression as an object expression, and a rvalue expression as a value expression respectively.

In C++, there is no distinction between a class type, a primitive type and a pointer type in terms of memory model (however array types are treated differently). All these are objects which have a fixed size in the memory defined by the type. When an object is passed by value into another object, its content is copied from the old object into the new object (assuming that the copy constructor is not overloaded). In particular, if a subclass object is passed by value into a base class object, the additional fields not in the base class are lost. Therefore it is not possible to preserve polymorphism in arrays or in STL containers where the objects are passed by value.

Return to the example above, it is even possible to do the following:

Dog **q = &p;
Dog ***r = &q;

Although p is a pointer, p is also an object because it is a variable declared and defined, which occupy memory, so we can also apply the & operator to take its address. We can have multiple layer of indirection in C or C++ although pointers of more than 2 levels are rarely seen in C, and in C++ by using references, STL containers, and OOP in most cases 1 level is already enough.

Note that the following is illegal:

Dog *pp = &(Dog()); // illegal
Dog **qq = &(&dog); // also illegal

It is because Dog() is a value expression representing a newly-made Dog which isn’t stored into any object in memory. Unless the value is assigned into an object like dog, it is meaningless to take the address of a value expression. Similarly, &dog is a value expression representing the address of dog, not an object expression. However, &*p is legal and guaranteed to have the same value as p for any valid non-NULL p, which is the definition of the referencing / dereferencing operation, similarly *&dog is guaranteed to represent the same object as dog for any object dog. (Note the distinction of “object” and “value” here.)

Note that the ability of doing “pointer arithmetic” is completely not mentioned. It is not relevant to the definition of a pointer at all.

Now we can start comparing Java code and its equivalent C++ code. For example, Java’s Dog d = new Dog(); is equivalent to C++’s Dog *d = new Dog();. In C++, new Dog() creates a Dog object with its default constructor dynamically and returns its address in memory, which is assigned to the pointer variable d.

People who claim that Java does not have pointer with this definition thinks that, in Java, d does not represent an address. This can be interpreted as both true and false. In C and C++ implementations, a pointer is commonly implemented as the physical memory address in hardware, and the * operator directly correspond to a machine instruction to deference the memory address. However this is not the only possible implementation as the standard only specifies the abstract machine and its observable behaviour. The compiler is free to optimise as long as the behaviour specified in the standard is followed. In Java, as Java bytecode is run in a virtual machine with garbage collection, d is normally implemented in some other ways rather than as a direct physical memory address.

If we take the implementation detail away and think at an abstract level, in Java, new Dog() also creates a Dog object with its default constructor dynamically and returns something so-called a “reference”. There is no memory model in Java. The concept of sizeof doesn’t exist in Java. There is no pointer arithmetic. Array access is not done by doing maths on pointer. There are no * and & operators in Java to dereference an address and take the address of the object. The concept of address simply doesn’t exist in Java.

However, consider the following pieces of semantically-identical C++ and Java codes, assuming there is a class call Foo and an integer member called value:

// C++
Foo *f = new Foo();
f->value = 5;
Foo *g = f;
g->value = 3;
if (f->value == 5) {
    // this won't be run
}
// Java
Foo f = new Foo();
f.value = 5;
Foo g = f;
g.value = 3;
if (f.value == 5) {
    // this won't be run
}

In C++, f is a pointer which is assigned to the address of a Foo object allocated dynamically, and g is assigned to the same address, so they point to the same object. Look how similar the Java code is even if we don’t use the word “address” in Java at all. That’s because, in C++, the fact that a pointer is the memory address is irrelevant to how we use the pointer. We are only using it as a “handle” to the object. Therefore, we can also say that, in Java, f and g are handles to the same object.

Wikipedia says that a handle is an abstract reference to a resource that is used when application software references blocks of memory or objects that are managed by another system like a database or an operating system. f and g in Java fit this description perfectly. In contrast, f and g in C++ are managed manually by the programmer, but we can pass them into some “smart pointer” classes which are managed by the library, so those “smart pointers” are actually handles as well.

By contrast, the following C++ code will create distinct objects on the stack which isn’t directly supported in Java and we have to use Object.clone() instead for the same purpose.

// C++
Foo a;
a.value = 5;
Foo b = a;
b.value = 3;
if (a.value == 5) {
    // this WILL be run
}

Note that the above C++ code doesn’t deal with any memory or object handle at all. The objects are used directly and not via any handles, which isn’t possible in Java. It isn’t possible to write the same code in Java without using new to create the object. In Java, object can only be used by handles.

Then we look at the definition of the term “reference”. Wikipedia says that (emphasis added by me)

A reference is a value that enables a program to indirectly access a particular datum, such as a variable’s value or a record, in the computer’s memory or in some other storage device. The reference is said to refer to the datum, and accessing the datum is called dereferencing the reference.

A reference is distinct from the datum itself. Typically, for references to data stored in memory on a given system, a reference is implemented as the physical address of where the data is stored in memory or in the storage device. For this reason, a reference is often erroneously confused with a pointer or address, and is said to “point to” the data. However a reference may also be implemented in other ways, such as the offset (difference) between the datum’s address and some fixed “base” address, as an index into an array, or more abstractly as a handle. More broadly, in networking, references may be network addresses, such as URLs.

This creates another layer of confusion. By this definition, a pointer in C / C++ is actually a reference, and what we calls a “reference” in C++ is actually not a reference. In Java, what we call a Java “reference” is actually a “reference” (implemented as a handle).

In C++, b of the following is a “reference”. However it does not meet the definition above:

// C++
Foo a;
a.value = 5;
Foo &b = a;
b.value = 3;
if (a.value == 5) {
    // this won't be run
}

In the above piece of code, a is b and b is a. They are the same object. There is no observable difference between a and b. They are just two names for the same object. There is no layer of indirection. There is no dereferencing operation at all. a and b are not distinct. More accurately, we can say that a and b are aliases of each other.

Consider the following piece of C++ code:

void swap(int &a, int &b) {
    int c = a;
    a = b;
    b = c;
}

int x = 5, y = 4;
swap(x, y);
// x and y are swapped

Note that both x and y are passed by reference into swap in C++, which enable the function to modify the actual object passed into it. It isn’t possible in Java. When the function is called, a inside is the same object as x outside, b inside is the same object as y outside. However, a and b do not fit the definition of reference quoted from Wikipedia above. C (not C++) does not support calling by reference, however, “call by reference can be simulated in languages that use call by value and don’t exactly support call by reference, by making use of references (objects that refer to other objects), such as pointers (objects representing the memory addresses of other objects).” (Wikipedia, emphasis added by me) It’s strange that a reference can be used to when “call by reference” is not supported, and in C++, call by reference is supported by declaring the parameters as a reference type which doesn’t fit the definition of reference (object that refer to other objects) above.

So it’s the term “reference” that is confusing. If we say in clearer terms, C++ has pointers, Java has handles, both can be used to indirectly refer to objects in a similar fashion as demonstrated by the parity of the code.

Leave a Reply

Your email address will not be published. Required fields are marked *