views:

750

answers:

8

In the late years of college, I had a course on Compilers. We created a compiler for a subset of C. I have always wondered how a pass-by-ref function call is compiled into assembly in C++.

For what I remember, a pass-by-val function call follows the following procedure:

  • Store the address of the PP
  • Push the arguments onto the stack
  • Perform the function call
  • In the function, pop from stack the parameters

What's different for pass-by-reference? (int void(int&);)

EDIT:

I may sound totally lost but, if you could help me I'd really appreciate it.

Everyone's answer is basically that it passes the address instead of the value. I understood that to be basically what passing a pointer is. So how come, these two functions, behave differently?:

struct A {
    int x;
    A(int v){
        x = v;
    }
};

int byRef(A& v){
    v = A(3);
    return 0;
}

int byP   (A* v){
    v = &A(4); //OR new A(4)
    return 0;
}

int _tmain(int argc, _TCHAR* argv[])
{
    A a (1); A b (2);
    byRef(a); byP  (&b);
    cout << a.x << " " << b.x;

    system("pause");

    return 0;
}

I know that in byP(A*), v is being passed by value, thus, it won't affect the caller's argument. Then, how would you implement byRef(A&) in terms of A*?

A: 

The difference is that it passes the address of the parameter, rather than the value of the parameter.

Kevin Beck
+4  A: 

You pass a pointer to the referand, exactly as you would any other pointer, and the callee knows how to use it. Depending on the implementation it therefore might not be on the stack - some parameters are passed in registers in some calling conventions.

There may be other ways to do it, since the C++ standard doesn't specify how references are implemented afaik, and even if they are implemented as pointers I suppose they might be distinguished in the calling convention. Pointers are the most obvious implementation, though.

Steve Jessop
A: 

I haven't looked at something like GCC's implementation, but the general idea is that, instead of passing in the contents of the variable, you pass in its address (just like if the C++ code had used "*" instead of "&" on the parameter).

Other schemes are available, but generally this will be done by pushing the values (or addresses) on the stack.

T.E.D.
+4  A: 

Call by reference will involve passing a pointer to a value rather than a copy of the value itself. If you are interested in the gory details, you can get your compiler to emit assembly language and examine it yourself.

Edit: Your pointer example should really be:

int byP (A* v) {
    * v = A(4);    // modify thing referenced by the pointer
    return 0;
}
anon
A: 

Hi

I think in pass-by-val, the procedure would be: * Store the value of the PP * Push the arguments onto the stack * Perform the function call * In the function, pop from stack the parameters

and in the pass-by-ref, the procedure would be the one which you have written

cheers

Andriyev
A: 

You could have a look for yourself. Many debuggers allows you to switch between a code and disassembly view. I'd set a breakpoint on the function call in the code view, run the app to that point, switch to the assembly, and step over each command.

[SPOILER ALERT]

.

It's a pointer.

Aardvark
+3  A: 
int byRef(A& v){
  v = A(3);
  return 0;
}

This invokes the assignment of the temporary object to the object passed by reference, the object used in the function call is modified. A shallow copy will be performed if no assignment operator is provided.

int byP   (A* v){
  v = &A(4); //OR new A(4)
  return 0;
}

This copies a pointer to a temporary object to the passed in pointer value. No assignment function is called. The value of 'v' is changed but the object v pointed to, the object address passed as an argument, is unchanged.

If you did this:

struct A {
  int x;
  A(int v){
    x = v;
  }
  A &operator = (A &rhs){
    cout << "assignment!";
  }
};

then the "assignment" will be output in the byRef function but not the byP function.

Although the & is implemented using pointers 'under the hood', as others have said, they are treated as the object passed to the function by the language.

So, to implement the byRef using pointers:

int byRefUsingP (A *v)
{
  *v = A(3);
  // or you could do:
  // v->operator = (A(3));
  // if an operator = is defined (don't know if it will work without one defined)
  return 0;
}

Skizz

Skizz
A: 

When passing by value, the compiler emits code to do a bitwise copy of the source bytes to the destination variable. For large structures and/or frequent assignments this can get quite inefficient. If your struct overrides the assignment operator, it will be called instead, and then you have control over what gets copied, but you're still pushing the whole structure on the stack.

When passing by reference the address of the struct gets pushed onto the stack, which is just an int. This is the most efficient way to pass large objects.

When passing by pointer the address of the pointer gets pushed on the stack. The pointer has to be dereferenced to get the address of the struct. There is an extra operation involved.

BTW, there is a serious bug in the byP function: it is assigning a temporary local variable to a pointer. The struct is allocated on the stack, and will (very likely) be overwritten after it goes out of scope. You must use "new" to allocate it on the heap, or assign the struct to the value referenced by the pointer, as per Neil Butterworth's example.

alekop