Skip to main content
GameDev.net gamedev.net
🔒 Locked

Passing by reference or by pointer

Started by Akusei Nov 23, 2011 at 2:51 AM 15 replies 3k views
Original Post
Akusei
Akusei
I was curious about passing parameters by reference vs. and which is a better choice; then I was wondering if there is even a difference. So I did some testing and optimizations aside (I turned them all off), I wrote a small program that has 1 function that took some parameters by reference and I compiled it. I then changed the function to accept the parameters by pointer and compiled.

passing by reference
[source lang="cpp"]
int testing(int &a, int b);

int main(int argc, char *argv[])
{
int a = 0x66;
int b = testing(a, 0x10);

printf("%d %d", a, b);

return 0;
}

int testing(int &a, int b)
{
a = b + 0x0f;
return a + b;
}
[/source]

passing by pointer
[source lang="cpp"]
int testing(int *a, int b);

int main(int argc, char *argv[])
{
int a = 0x66;
int b = testing(&a, 0x10);

printf("%d %d", a, b);

return 0;
}

int testing(int *a, int b)
{
*a = b + 0x0f;
return *a + b;
}
[/source]

Assembly of by pointer AND by reference are listed here; EXACTLY the same!
[source lang="asm"]
push 10h ; b
lea eax, [ebp+a]
push eax ; a
call testing
ret
...
proc testing
push ebp
mov ebp, esp
mov eax, [ebp+b]
add eax, 0Fh
mov ecx, [ebp+a]
mov [ecx], eax
mov edx, [ebp+a]
mov eax, [edx]
add eax, [ebp+b]
pop ebp
endp
[/source]

I reverse engineered the 2 binaries and compared them, it appears to me that passing by reference and passing by pointer do the exact same thing... I'm talking EXACT, down to the same assembler instructions. Am I just crazy or are they indeed the same and choosing one over the other is simply aesthetics? Is there a benefit to one over the other?
-akusei
Cornstalks
Cornstalks
Behind the scenes, they're pretty much the same. There are, however, a couple of minor differences between the two, which may or may not be significant to you.

First, a pointer can be null or some potentially invalid value. A reference cannot. So if you want to ensure an input is always valid, references are nice. Second, a pointer can be reset (that is, you can change the address to which it points), but a reference cannot. Once a reference's reference (the address it "points" to) is set, it's set for good.

That's the main difference between the two.
HappyCoder
HappyCoder
Cornstalks pretty much hit it right on. Compile time differences.

I would like to point out some nice things about doing things by reference.



// I can define a class here
class Vector2
{
public:
double x, y;
Vector2 operator-(const Vector2& other) const;


double length() const;

...

Vector2(double x, double y);
}

// Vector2.cpp

Vector2 Vector2 ::operator-(const Vector2& other) const
{ return Vector2(x - other.x, y - other.y);
}


double Vector2::length() const
{
return sqrt(x * x + y * y);
}

// Some source.cpp

double distance(const Vector2& a, const Vector2& b)
{
// draw your attention here
return (a - b).length(); // <-----------------------------
}



When dealing with classes by reference you can perform overloaded operators without dereferencing them and don't need to check for null pointers, this makes the code a lot cleaner.
Passing by value and by reference is only different when declaring the parameters, after that they are the same. That makes it easy to switch between the two if needed.

The one advantage passing by pointer has is there can be a null pointer if you want to make a parameter optional, however null pointers can cause problems if the parameter is not optional.

So my advice, pass by reference whenever you can, use pointers only for optional parameters.
My current game project Platform RPG
Akusei
Akusei
Thanks for the great information. I didn't even stop to think about the compile time differences and those are very good points and make it out to be a very non cosmetic issue. I will definitely be trying to use references instead of pointers as much as possible. Thanks again.
-akusei
luca-deltodesco
luca-deltodesco

I reverse engineered the 2 binaries and compared them...


You realise compilers can be instructed to output assembly themselves?
rip-off
rip-off
Some programmers prefer references because they indicate clearly that the value is expected to exist. They also make it easier to write the called code. Finally it makes it harder to attempt to deallocate the object, it sends a stronger "something else owns this" message.

Others prefer the call site to be more explicit about the fact that the passed object will be modified, so they use pointers as you can see the address-of operator being used at the call site. I don't think this is a compelling reason because it can't really be applied consistently. It only works for the "initial" call, if you pass the pointer on to a helper function you don't get the same notification, and now you have to deal with potentially null values, and the called code becomes littered with pointer dereference and arrow member select operators.

It is important to note that const references enable particular language features, such as passing a temporary directly to such a parameter and the most important const for returned temporaries.

Both C++ pointers and C++ references are implemented using addresses in the underlying assembly, but that is fairly irrelevant. Neither C++ pointer or C++ reference semantics map totally to the hardware, both tend to impose restrictions (in terms of undefined behaviour) that the hardware doesn't enforce.
taz0010
taz0010
Const reference should be your default way of passing anything other than a primitive type or iterator. Obviously remove the const when the passed reference is mutable. If the parameter is intended for use as a second "return value", it's a good idea to mark it as such (prefix with 'out' for example).

On the other hand you'll want to pass by pointer when:

  • The parameter is allowed to be NULL
  • The parameter is optional (make NULL the default value of the parameter)
  • You're passing an array. Kind of obvious, but don't write functions that expect array parameters but are designed to take references instead.
  • You have a group of functions which manipulate pointers or structures of pointers, and passing in a reference would add needless address-of operations (i.e. & before every use of the variable). This pops up when you're rolling your own data structures, but the public interface should still use references, even though we all know structures make heavy use of pointers behind the scenes
  • You have a class with multiple constructors and not every constructor binds all references to an instance of an object. References can never be NULL, meaning any class with reference members has to bind ALL references to objects in ALL of it's constructors, inside the constructor initialiser list.
rip-off
rip-off

You have a class with multiple constructors and not every constructor binds all references to an instance of an object. References can never be NULL, meaning any class with reference members has to bind ALL references to objects in ALL of it's constructors, inside the constructor initialiser list.
[/quote]
Having references as members is usually best avoided, as the compiler generated and hand-written assignment operators for such types are likely to be somewhere between non-intuitive and simply wrong.
Akusei
Akusei

You realise compilers can be instructed to output assembly themselves?


Yes, but I am a malware reverse engineer so I have better tools available to analyze the assembly, such as flow charts and jump node graphs in IDA. It makes it easier on the eyes to look at, plus that's just the way I wanted to do it.
-akusei
Akusei
Akusei

Having references as members is usually best avoided, as the compiler generated and hand-written assignment operators for such types are likely to be somewhere between non-intuitive and simply wrong.


So having class member variables as references is not a good thing? If this is the case then what if I pass an object by reference to a class constructor for the purpose of that instantiated class using the reference over the life of the object. Would it be better to pass by reference and store it in a pointer member variable instead of a reference variable?
-akusei
Slavik81
Slavik81

Having references as members is usually best avoided, as the compiler generated and hand-written assignment operators for such types are likely to be somewhere between non-intuitive and simply wrong.

I regularly use references as members for classes with identity semantics. They are non-copyable and non-assignable, so this makes little difference.
rip-off
rip-off

So having class member variables as references is not a good thing?
[/quote]
It depends. Often it will be harmless. But you might be surprised at the behaviour if you were to attempt to assign such an object.


If this is the case then what if I pass an object by reference to a class constructor for the purpose of that instantiated class using the reference over the life of the object. Would it be better to pass by reference and store it in a pointer member variable instead of a reference variable?
[/quote]
That is what I do, yes. The constructor signature tells the client it expects a valid object. Internally, the class can then reasonably assume that the pointer will be not-null as an invariant thereafter.


I regularly use references as members for classes with identity semantics. They are non-copyable and non-assignable, so this makes little difference.
[/quote]
If the class is not copyable, then you have suppressed the problematic behaviour.
taz0010
taz0010
If the class is not copyable, then you have suppressed the problematic behaviour.[/quote]

There are some hackish solutions to the assignment operator problem involving an explicit destructor call and placement new. Is this the only way to get an assignment operator to work properly with references?

rip-off
rip-off

There are some hackish solutions to the assignment operator problem involving an explicit destructor call and placement new. Is this the only way to get an assignment operator to work properly with references?
[/quote]
Probably, but even that can't be done in an exception safe manner...
Antheus
Antheus

I'm talking EXACT, down to the same assembler instructions.


Due to the way C++ compilers are allowed to transform source code, generated assembly is never proof of anything. Trivial code like one listed here is by far worst offender, since it's possible to completely analyze it statically.

When it comes to codegen, compilers that use BURG-like engines need to trade space vs. time in both, generating code as well as final output. Coupled with template bloat prevention, there is simply so many heuristics involved that seemingly unrelated changes may cause unexpected changes. It gets even trickier when one links across compilation units, where different kinds of duplication, inlining and code removal may take place.

The best one can say is that given certain code, using certain compiler settings, that is the result. But no generalizations can be made, unless one studies the source of compiler itself.

References alone, if being completely conservative, cannot offer anything beyond pointers. But optimistic compiler can probably make certain assumptions, just like it does about many other aspects. I've seen references coupled with templates resulting in slightly better code, but nothing I could reliably claim.

So I did some testing and optimizations aside (I turned them all off),[/quote]

Debug versions are somewhat useless, since they are conservative. If anything, they will force all references and pointers to behave in most conservative way, preventing any kind of different optimizations from being applied.

The result is therefore correct, compiler treats both the same, making pessimistic assumptions about use of references, including them being invalid.
Akusei
Akusei


I regularly use references as members for classes with identity semantics. They are non-copyable and non-assignable, so this makes little difference.

If the class is not copyable, then you have suppressed the problematic behaviour.
[/quote]

What exactly is the problematic behavior? If you have a reference variable as a private class member which is assigned in the constructor in an initialization list and references/pointers are essentially the same thing in machine language (with the mentioned compile-time differences), what would be the issue with having them in this way and what would go wrong with copying the object they reside in? I would think that since a reference is kind of a type-safe pointer that when a copy occurs it is a copy of an address and not a shallow or deep type copy of the object itself.
-akusei
rip-off
rip-off

What exactly is the problematic behavior? If you have a reference variable as a private class member which is assigned in the constructor in an initialization list and references/pointers are essentially the same thing in machine language (with the mentioned compile-time differences), what would be the issue with having them in this way and what would go wrong with copying the object they reside in? I would think that since a reference is kind of a type-safe pointer that when a copy occurs it is a copy of an address and not a shallow or deep type copy of the object itself.

It is the assignment I was referring to.

Consider the following program:

#include <iostream>

class Foo
{
public:
Foo(int &r) : r(r)
{
}

Foo &operator=(const Foo &foo)
{
r = foo.r;
return *this;
}

private:
int &r
};

class Bar
{
public:
Bar(int &r) : p(&r)
{
}

Bar &operator=(Bar &bar)
{
p = bar.p;
return *this;
}

private:
int *p;
};

template<typename T>
void test(const char *name)
{
int x = 1;
int y = 42;

T a(x);

std::cout << name << " 1 x: " << x << ", y: " << y <<'\n';

T b(y);

std::cout << name << " 2 x: " << x << ", y: " << y <<'\n';

b = a;

std::cout << name << " 3 x: " << x << ", y: " << y <<'\n';
}

int main()
{
test<Foo>("Foo");
test<Bar>("Bar");
}

The behaviour of the reference is quite unexpected for client code.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.