12

I'm trying to learn how C++ compilers handle references and pointers, in preparation for a compiler class that I'm taking next semester. I'm specifically interested in how compilers handle references in C++.

The standard specifies that a reference is an "alias," but I don't know exactly what that means at the compiler level. I have two theories:

  1. A non-reference variable has an entry in the symbol table. When a reference to that variable is created, the compiler simply creates another lexeme that "points" to the exact same entry in the symbol table (and not to the non-reference variable's location in memory).

  2. When a reference to that variable is created, the compiler creates a pointer to that variable's location in memory. The limitations on references (no null values, etc.) are handled when parsing the context of the language. In other words, a reference is "syntactic sugar" for a dereferenced pointer.

Both solutions would create an "alias," as far as I can tell. Do compilers use one and not the other? Or is it compiler-dependent?

As an aside, I'm aware that at the machine-language level, both are "pointers" (pretty much everything other than an integer is a "pointer" at the machine level). I'm interested in what the compiler does before the machine code is generated.

EDIT: Part of the reason I am curious is because PHP uses method #1, and I'm wondering if C++ compilers work the same way. Java certainly does not use method #1, and their "references" are in fact dereferenced pointers; see this article by Scott Stanchfield.

Karl Giesing
  • 1,654
  • 3
  • 16
  • 24
  • why do you think that compiler cannot use both depend on context? – Slava Jan 15 '14 at 18:17
  • The standard doesn't require references to even require storage, so either of your two theories could be correct. The only way to really know is to open up the source for your compiler and check (or ask someone who implemented your compiler.) – bstamour Jan 15 '14 at 18:21
  • @Slava: I suppose it could, I just assumed that treating it one way or the other would make the compiler's code "better" (more consistent, easier to maintain, etc). On the other hand, I don't know what advantages one approach has over the other, or even if there are any. – Karl Giesing Jan 15 '14 at 18:23
  • @KarlGiesing I would say theory N2 is universal and can be used everywhere, though N1 can be used on special cases for optimization. But compiler developers should know better. – Slava Jan 15 '14 at 18:41

2 Answers2

10

I will try to explain how references are implemented by g++ compiler.

    #include <iostream>

    using namespace std;

    int main()
    {
        int i = 10;
        int *ptrToI = &i;
        int &refToI = i;

        cout << "i = " << i << "\n";
        cout << "&i = " << &i << "\n";

        cout << "ptrToI = " << ptrToI << "\n";
        cout << "*ptrToI = " << *ptrToI << "\n";
        cout << "&ptrToI = " << &ptrToI << "\n";

        cout << "refToNum = " << refToI << "\n";
        //cout << "*refToNum = " << *refToI << "\n";
        cout << "&refToNum = " << &refToI << "\n";

        return 0;
    }

Output of this code is like this

    i = 10
    &i = 0xbf9e52f8
    ptrToI = 0xbf9e52f8
    *ptrToI = 10
    &ptrToI = 0xbf9e52f4
    refToNum = 10
    &refToNum = 0xbf9e52f8

Lets look at the disassembly(I used GDB for this. 8,9 and 10 here are line numbers of code)

8           int i = 10;
0x08048698 <main()+18>: movl   $0xa,-0x10(%ebp)

Here $0xa is the 10(decimal) that we are assigning to i. -0x10(%ebp) here means content of ebp register –16(decimal). -0x10(%ebp) points to the address of i on stack.

9           int *ptrToI = &i;
0x0804869f <main()+25>: lea    -0x10(%ebp),%eax
0x080486a2 <main()+28>: mov    %eax,-0x14(%ebp)

Assign address of i to ptrToI. ptrToI is again on stack located at address -0x14(%ebp), that is ebp – 20(decimal).

10          int &refToI = i;
0x080486a5 <main()+31>: lea    -0x10(%ebp),%eax
0x080486a8 <main()+34>: mov    %eax,-0xc(%ebp)

Now here is the catch! Compare disassembly of line 9 and 10 and you will observer that ,-0x14(%ebp) is replaced by -0xc(%ebp) in line number 10. -0xc(%ebp) is the address of refToNum. It is allocated on stack. But you will never be able to get this address from you code because you are not required to know the address.

So; a reference does occupy memory. In this case it is the stack memory since we have allocated it as a local variable. How much memory does it occupy? As much a pointer occupies.

Now lets see how we access the reference and pointers. For simplicity I have shown only part of the assembly snippet

16          cout << "*ptrToI = " << *ptrToI << "\n";
0x08048746 <main()+192>:        mov    -0x14(%ebp),%eax
0x08048749 <main()+195>:        mov    (%eax),%ebx
19          cout << "refToNum = " << refToI << "\n";
0x080487b0 <main()+298>:        mov    -0xc(%ebp),%eax
0x080487b3 <main()+301>:        mov    (%eax),%ebx

Now compare the above two lines, you will see striking similarity. -0xc(%ebp) is the actual address of refToI which is never accessible to you. In simple terms, if you think of reference as a normal pointer, then accessing a reference is like fetching the value at address pointed to by the reference. Which means the below two lines of code will give you the same result

cout << "Value if i = " << *ptrToI << "\n";
cout << " Value if i = " << refToI << "\n";

Now compare this

15          cout << "ptrToI = " << ptrToI << "\n";
0x08048713 <main()+141>:        mov    -0x14(%ebp),%ebx
21          cout << "&refToNum = " << &refToI << "\n";
0x080487fb <main()+373>:        mov    -0xc(%ebp),%eax

I guess you are able to spot what is happening here. If you ask for &refToI, the contents of -0xc(%ebp) address location are returned and -0xc(%ebp) is where refToi resides and its contents are nothing but address of i.

One last thing, Why is this line commented?

//cout << "*refToNum = " << *refToI << "\n";

Because *refToI is not permitted and it will give you a compile time error.

Prasad Rane
  • 691
  • 8
  • 16
-2

Understanding pointers and references is quite different than implementing the code for them.

I suggest you learn how to use them properly and focus on the core of compiler theory. The fundamental compiler theory class is difficult enough without the concepts of pointers, references and inheritance. Pointers and references are left for a more advanced class.

Simply put: use references when you can, pointers when you must.

Edit 1:
Compilers can implement references and pointers in any way they want as long as their syntax and semantics behave according to the language specification.

A simple implementation is to treat references as pointers with additional attributes.

Everything in memory has a location, i.e. address. The compiler may have to use internal pointers to load from memory into registers and to store register contents into memory. So to refer to a variable in memory, whether by pointer, reference or alias, the compiler needs the address of the variable. (This does not include register variables which are treated differently.) So using pointers for references or aliases saves some coding.

Thomas Matthews
  • 56,849
  • 17
  • 98
  • 154