views:

552

answers:

11

Hi all,

Reading http://www.cprogramming.com/tutorial/references.html, it says:

In general, references should always be valid because you must always initialize a reference. This means that barring some bizarre circumstances (see below), you can be certain that using a reference is just like using a plain old non-reference variable. You don't need to check to make sure that a reference isn't pointing to NULL, and you won't get bitten by an uninitialized reference that you forgot to allocate memory for.

My question is how do I know that the object's memory hasn't been freed/deleted AFTER you've initialized the reference.

What it comes down to is that I can't take this advice on faith and I need a better explanation.

Can anyone shed some light?

Thanks, jbu

+26  A: 

You can't know if references are invalid:

There is no way to know if your reference is referencing valid memory except by taking care of how you use references. For example you don't want to use a reference with something created on the heap if you are unsure when the memory will be deleted.

You also can never know whether the pointer you are using is pointing to valid memory or not as well.

You can do NULL checks with both pointers and references but typically you would never do a NULL check with a reference because no one would ever write code like this:

int *p = 0;
int &r = *p;//no one does this
if(&r != 0)//and so no one does this kind of check
{
}

When to use a reference?

You probably want to use references in cases like this:

//I want the function fn to not make a copy of cat and to use
// the same memory of the object that was passed in
void fn(Cat &cat)
{
   //Do something with cat
}

//...main...
Cat c;
fn(c);

Shooting yourself in the foot is hard with references:

It's much harder to shoot yourself in the foot with references than it is with pointers.

For example:

int *p;
if(true)
{
  int x;
  p = &x;
}

*p = 3;//runtime error

You can't do this sort of thing with references since a reference must be initialized with it's value. And you can only initialize it with values that are in your scope.

You can still shoot yourself in the foot with references, but you have to REALLY try to do it.

For example:

int *p = new int;
*p = 3;
int &r = *p;
delete p;
r = 3;//runtime error
Brian R. Bondy
VERY nice write up. +1.
Billy ONeal
"you probably don't want to use a reference with something created on the heap"? Why not? If I create an instance on the heap, pass it by reference to a method and then free it, I'm fine. The only problem would be if the method stored that reference and assumed it would remain valid.
Steven Sudit
@Steven Sudit: Surely there are some scenarios out there so that's why I used probably. At least I can say in my own code if I do it then it is relatively rare.
Brian R. Bondy
@Brian: I would say that you want to use a reference when you need the method to modify the object, or a const reference if you need to avoid copying but don't want to modify it. Where in memory the object is allocated makes no difference so long as its lifespan exceeds that of the method call. So, not, it's not rare at all.
Steven Sudit
@Steven: I specialized my comment a little more and made it more definitive: "For example you don't want to use a reference with something created on the heap if you are unsure when the memory will be deleted."
Brian R. Bondy
I have seen the null reference in running code - function takes a reference parameter, is passed a dereferenced pointer from another function that returns an object pointer but under rare circumstances returns null. It was a pain to debug. I still wouldn't make a habit of checking for null references, but I might do it in the throes of debugging.
Mark Ransom
@Mark: Iv'e never run into such a scenario unless I was trying to do it for the sake of answering someone's question :). Interesting to hear though.
Brian R. Bondy
@Mark: I'm pretty sure that if x is a reference parameter, the compiler is allowed to optimize ` delete p;` and delete is inlined, then then `null` check in delete might be omitted), so I wouldn't be utterly astonished if a null check of a reference was sometimes deleted too. Haven't tested.
Steve Jessop
@Brian: Thank you, I think that improves an already-good answer.
Steven Sudit
This answer is great (+1). Ironically, my own answer was in agreement with this one, but has been taken in an entirely different direction.
mrjoltcola
+2  A: 

You need to maintain sanity of your variables -- i.e., only pass a reference/pointer to some function if you know the function scope will not outlive you reference/pointer.

If you go and free some handle and then try to use said reference you will be reading free'd memory.

Hassan Syed
+5  A: 

You can't. You also can't with a pointer. Consider:



struct X
{
  int * i;
  void foo() { *i++; }
};

int main()
{
  int *i = new int(5);
  X x = { i };
  delete i;
  x.foo();
}

Now, what code could you put in X::foo() to make sure that the i pointer is still valid?

Answer is that there is no standard check. There are some tricks that might work on msvc in debug mode (checking for 0xfeeefeee or whatever), but there's nothing that will consistently work.

If you need some sort of object that makes sure the pointer does not point at freed memory you'll need something much smarter than a reference or standard pointer.

This is why you need to be pretty darn careful with ownership semantics and lifetime management when working with pointers and references.

Noah Roberts
same question posed to another response: what if when I delete pointers, i set the pointer to null afterwards and check for null? Wouldn't that be a way I can check if pointers are no longer valid?
jbu
You can always have an aliased pointer. Say that `p` and `q` are both pointers to the same memory. You delete `p` and set `p = NULL`. Now, what about `q`? It's not valid and also not NULL. It's not always trivial to know what all of the aliases to a pointer are when you delete one.
Tyler McHenry
+1  A: 

Because by the time you reach there you have made an undefined behavior for sure. Let me explain :)

Say you have:

void fun(int& n);

Now, if you pass something like:

int* n = new int(5);
fun(*n); // no problems until now!

But if you do the following:

int* n = new int(5);
...
delete n;
...
fun(*n); // passing a deleted memory!

By the time you reach fun, you will be dereferencing *n which is undefined behavior if the pointer is deleted as in the example above. So, there is no way, and there must be now way actually because assuming valid parameters is the whole point of references.

AraK
what if when I delete pointers, i set the pointer to null afterwards and check for null? Wouldn't that be a way I can check if pointers are no longer valid?
jbu
@jbu: It wouldn't matter. If you have a reference to an object, and the object it refers to goes away, you have undefined behavior. Better is to actually fix the problem and make sure if you're referring to something it won't drop from underneath you.
GMan
@jbu: if after deletion you set the pointer to null, then after deletion you *must not* dereference the pointer to initialise a reference.
Steve Jessop
+1 for pointing out that this is UB. Other answers seem to blatantly ignore the fact.
Tronic
+1  A: 

There is no syntax to check whether reference is valid. You can test pointer for NULL, but there is no valid/invalid test for a reference. Of course, referenced object can be released or overwritten by some buggy code. The same situation is for pointers: if non-NULL pointer points to released object, you cannot test this.

Alex Farber
codymanix
You can test a reference for null.
mrjoltcola
@mrjoltcola: no you can't. What @codymanix suggests is undefined because the existence of a null reference is undefined.
jalf
Tyler McHenry
@Tyler: Of course it is legal to take a reference, make a pointer to the referenced object, and then compare that pointer to zero. But it's irrelevant to this question *because* it will always be false. Because there is no such thing as a null reference.
jalf
Tyler McHenry
@jalf: see my comment on your answer. I used "loose language" when I said "test a reference for null". The point is, it can be an alias for nothing (null address).
mrjoltcola
@mrjoltcola: it can only be an alias for nothing in a program which has incurred undefined behavior. The result of the comparison is therefore undefined, as is the number of demons flying out of your nose. For the test to be useful, you'd need some particular additional guarantee from your implementation what (if anything) it will do about a dereferenced null pointer.
Steve Jessop
@Steve: I'm convinced, but not happy. Standards are one thing, but practice is another. How else can you mix dynamic allocation with libraries that use reference parameters? Feel free to comment on my downvoted answer below.
mrjoltcola
@mrjoltcola: "How else can you mix dynamic allocation with libraries that use reference parameters?". Just don't dereference null pointers. In any context where you would not write `p->some_member()`, don't write `some_function(*p)` either. If you place the responsibility on the caller, then the callee doesn't need to contemplate doing the impossible. You could ask the same question about `this`: "should I check it for null in every member function?". The answer is that there is no need to do so, but if you feel like trying to catch UB, knock yourself out.
Steve Jessop
@Steve: No, I never check references! This is a big misunderstanding. As I said in my other comment; unfortunately by trying to demonstrate that in C++ a reference is subject to pointer semantics, I mislead the thread.
mrjoltcola
Alex Farber
"...in a well defined program."
mrjoltcola
+1  A: 

The short is that it could happen -- but if it does, you have a serious design problem. You also have no real way of detecting it. The answer is to design your program to prevent it from happening, not trying to build some sort of check that won't really work (because it can't).

Jerry Coffin
+3  A: 

In C++, references are primarily intended to be used as the parameters and return types of functions. In the case of a parameter, a reference cannot refer to an object that no longer exists (assuming a single threaded program) because of the nature of a function call. In the case of a return value, one should restrict oneself to either returning class member variables whose lifetimes are longer than the function call, or reference parameters that are passed in to the function.

anon
Unless the single threaded program happens to delete the address of the reference it knows is on the heap. One wouldn't do this but it's possible :)
Brian R. Bondy
Neil's answer is correct.
Steven Sudit
"assuming a single threaded program". Eh? `void f(int r += 1; } int main() { int *a = new int(); f(*a, a); }`. Creates a dangling reference. I don't see what the single or multi-threadedness of the program has to do with anything. Even considering only automatics as referands, once somebody has a reference, they can store it in an object (via a constructor), store a pointer to that object (in a global) in order to escape the scope of the object, and pass it later as a function parameter. UB, of course, so "cannot happen", but nothing to do with the nature of a fn call.
Steve Jessop
@Steve The question is explicitly about having to check references, not if is possible to write pathological code.
anon
OK, but IMO the statement "a reference cannot refer to an object that no longer exists (assuming a single threaded program) because of the nature of a function call" is nevertheless terribly misleading. The only thing stopping that bad thing from happening is that it's forbidden by the language. Neither single-threading nor the nature of a function call prevents the bad thing happening. I gave two examples (aliasing and pathologically poor resource handling), another would be that in single-threaded code a callback or functor can subvert this "nature of a function call".
Steve Jessop
The point is it's not the nature of a function call, or avoiding multiple threads, that protects the referand. It's the caller who must arrange to protect the referand. You don't want people who write calling code to go around thinking "ah, well, my code's single threaded, so I don't have to think about the lifetime of objects I pass in to functions by reference".
Steve Jessop
@Steve Dare I say that if I hadn't put in that proviso, you would have come up with a contra example to my answer using multi-threading?
anon
@Steve ... and from your eloquent comments here, can I take it that you believe that you must check that references refers to a valid object? If so, perhaps you could also say how you go about doing this?
anon
Plausible. Although if multi-threading had been my *only* example, I'd have left myself wide open to you saying "my comments referred to standard C++ without extensions", so I hope I'd do better than that ;-). I think that reading what you wrote just raised shades of people saying "I have this `const*` parameter, right, so that means the referand can't change during the course of my function, yeah?". Of course I know that *you* understand all this, but I also know what misconceptions beginners come up with when they see or deduce over-simplifications of these concepts.
Steve Jessop
"can I take it that you believe that you must check that references refers to a valid object?" No. If you direct me to what I said that suggested that, I'll retract/rephrase it. I thought the closest I'd come was in another thread to this question, where I said, "if you want to try to catch UB, knock yourself out". I don't think references should be checked for null (and as I've said elsewhere, I wouldn't trust the compiler not to optimise away any code that tried). But I do think that the basis on which references (and parameters in general) are assumed to be valid should be logically sound.
Steve Jessop
A: 

I think it "depends". I know, that is not an answer, but it does really depend. I think coding defensively is a good practice to follow. Now if your stack track is 10 levels deep and any failure down the trace causes the entire operation to fail, then by all means, check at the top level and let any exceptions rise to the top. but if you can recover from someone passing you a null reference, the check where appropriate. In my experience, where I have to bring code together with other companies to integrate together, checking ( and logging ) everything at the public api level lets you deflect the finger pointing that happens when integration does not go as expected.

Rob Goodwin
A: 

C++ references are aliases. The effect of this is that dereferences to pointers don't necessarily happen where they appear, they happen where they are evaluated. Taking a reference to an object doesn't evaluate the object, it aliases it. Using the reference is what evaluates the object. C++ cannot guarantee references are valid; if it does, all C++ compilers are broken. The only way to do so is to eliminate all possiblity of dynamic allocation with references. In practice, the assumption is that a reference is a valid object. Since *NULL is undefined & invalid, it follows that for p = NULL, *p is also undefined. The problem with C++ is *p will be effectively passed to a function, or delayed in its evaluation until which time the reference is actually used. Arguing that it is undefined is not the point of the asker's question. If it were illegal, the compiler would enforce it, and so would the standard. Neither does, that I am aware of.

int &r = *j; // aliases *j, but does not evaluate j
if(r > 0) // evaluates r ==> *j, resulting in dereference (evaluate j) at this line, not what some expect
  ;

1) You can test a reference for aliasing a NULL pointer, &r is simply &(whatever r aliases to) (EDIT)

2) When passing a "dereferenced" pointer (*i) as a reference parameter, the dereference doesn't happen at the callsite, it may never happen, because it is a reference (references are aliases, not evaluations). That is the optimization of references. If they were evaluated at the callsite, either the compiler is inserting extra code, or it would be a call by value and less performant than a pointer.

Yes, the reference itself is not NULL, it is invalid, just as *NULL is invalid. It is the delaying of evaluation of dereference expressions that is not consistent with claiming it is impossible to have an invalid reference.

#include <iostream>

int fun(int & i) {
   std::cerr << &i << "\n";
   std::cerr << i << "\n"; // crash here
}

int main() {
   int * i = new int();
   i = 0;
   fun(*i); // Why not crash here? Because the deref doesn't happen here, inconsistent, but critical for performance of references
}

EDIT: Changed my example as it has been misconstrued as suggestion for testing references, not what I wanted to demonstrate. I only wanted to demonstrate the invalid reference.

mrjoltcola
Your "*i" is undefined behavior... maybe it will work, maybe it will crash latter, or maybe reach with a hand behind your back and strangle you with an old sock...
VirtualBlackFox
Undefined behaviour means the specification does not make a judgement. The point is it is legal behaviour in every compiler I know and happens in real-world code that mixes pointers with libraries that make use of reference parameters.
mrjoltcola
"Some of the answers" are talking about what is defined by the standard. The fact that some implementations either guarantee certain behaviour in particular cases of UB, or else don't guarantee it but currently do something predictable, does not mean that the answers are wrong. It's just the difference between describing an interface, and describing one or more particular implementations of that interface.
Steve Jessop
GCC and Visual C++ are the predominant implementations, so as much as I agree with you, I am not satisfied with answers that say "cannot happen" or even "should not happen". The asker is one who wants to know the gory details.
mrjoltcola
Elsewhere, you asked someone to point to the part of the standard which prevents null references. I would likewise ask you to point to the part of the GCC or MSVC documentation which allows them, which defines exactly what operations on the resulting reference will result in what hardware faults, etc. If this really is defined behaviour, then maybe it's worth discussing (a) whether APIs should be written such that callers can expect predictable behaviour when they do it, and (b) how to implement that predictable behaviour.
Steve Jessop
I never claimed it is defined behaviour. It is legal and prevalent behaviour. Legal, in that you can do it, the compiler won't warn you, but you may pay the penalty by leaving the lawful area. Same as walking the stack and misc other ugly things that we do. They work because we carefully observe compiler implementation. I only contend that in C++, a reference is only as good as the thing it aliases, and compilers don't enforce or even warn you when you alias things that are not valid. The C++ reference is a _contract_ that is not implemented nor enforced by anyone but the programmer.
mrjoltcola
+4  A: 

My question is how do I know that the object's memory hasn't been freed/deleted AFTER you've initialized the reference.

First, there is never any way to detect if a memory location has been freed/deleted. That has nothing to do with whether or not it is null. The same is true for a pointer. Given a pointer, you have no way to ensure that it points to valid memory. You can test whether a pointer is null or not, but that's all. A non-null pointer may still point to freed memory. Or it may point to some garbage location.

As for references, the same applies in that you have no way of determining whether it references an object that is still valid. However, there is no such thing as a "null reference" in C++, so there is no need to check if a reference "is null".

Of course, it is possible to write code that creates what looks like a "null reference", and that code will compile. But it won't be correct. According to the C++ language standard, references to null can not be created. Attempting to do so is undefined behavior.

What it comes down to is that I can't take this advice on faith and I need a better explanation

The better explanation is this: "a reference points to a valid object because you set it to point to a valid object". You don't have to take it on faith. You just have to look at the code where you created the reference. It either pointed to a valid object at that time, or it didn't. If it didn't, then the code is incorrect and should be changed.

And the reference is still valid because you know it is going to be used, so you have made sure not to invalidate the object it references.

It's really that simple. References stay valid as long as you don't destroy the object they point to. So don't destroy the object it points to until the reference is no longer needed.

jalf
@jalf: Ignoring features based on "is undefined" is not helping the asker, in my opinion. We all know that "fun(\*i)" happens. Any function with reference params is potentially receiving a "null reference" that won't be found until evaluated in the callee. That is the difference. Yes, you should check the pointer prior to the call, I agree. But if C++ _really_ made the guarantee you state, it would have to check pointer dereferences at the point of the expression "*i", prior to the function call. Please point me to the standard that covers this.
mrjoltcola
@mrjoltcola: when C++ "guarantees" anything, anything at all, that guarantee is subject to the program being well-formed and not invoking undefined behaviour. If C++ doesn't "really guarantee" that a reference is non-null, then it also doesn't "really guarantee" that 1+1==2. Either or both of those can fail once undefined behavior has been invoked. 1.3.12 says "ignoring the the situation completely with unpredictable results" is permissible.
Steve Jessop
You've convinced me, but answers that say "cannot happen" are wrong and that is what prompted my answer. "Cannot happen" is the wrong choice of words. I think everyone has misunderstood my point.
mrjoltcola
@mrjoltcola: yes, I think I did misunderstand your point a bit. It "cannot happen", if the caller's code is correct. I guess the point of departure is I think that closes the deal: if the caller's code is this incorrect, the callee shouldn't make imperfect efforts to debug it. But if you'd check pointers for null (when null is banned by the API), you might want the same for references. I think that if the API doc says "don't do X", and likewise if the language spec does, then in C or C++, you don't normally want to check it. In Eiffel, maybe you have the tools to check contracts properly.
Steve Jessop
@Steve: I agree, and it was the fault of my example that you even thought I was suggesting checking the reference in the callee. The example had an if() statement just to show it affected the execution of the code. But it was a poor decision to provide such example, so I edited it. I only wanted to demonstrate that the evaluation of the "null reference" (and thus, the runtime fault) happened in the callee, and not at the point in the program that it appeared *i would be evaluated (in the caller).
mrjoltcola
A: 

I think you could benefit from a simple parallelism:

  • T & is similar to T * const
  • T const & is similar to T const * const

References are very similar to const in their intent, they carry a meaning and thus help write clearer code, but don't provide different runtime behavior.

Now to answer your question: yes it is possible that a reference be null or invalid. You can test for a null reference (T& t = ; if (&t == 0)) but it should not happen >> by contract a reference is valid.

When to use reference vs pointer ? Use a pointer if:

  • you wish to be able to change the pointee
  • you wish to express the possible nullity

In any other case, use a reference.

Some examples:

// Returns an object corresponding to the criteria
// or a special value if it cannot be found
Object* find(...); // returns 0 if fails

// Returns an object corresponding to the criteria
// or throw "NotFound" if it cannot be found
Object& find(...); // throw NotFound

Passing arguments:

void doSomething(Object* obj)
{
  if (obj) obj->doSomething();
}

void doSomething(Object& obj) { obj.do(); obj.something(); }

Attributes:

struct Foo
{
  int i;
  Bar* b; // No constructor, we need to initialize later on
};

class Foo
{
public:
  Foo(int i, Bar& b): i(i), b(b) {}
private:
  int i;
  Bar& b; // will always point to the same object, Foo not Default Constructible
};

class Other
{
public:
  Other(Bar& b): b(&b) {} // NEED to pass a valid object for init

  void swap(Other& rhs);  // NEED a pointer to be able to exchange

private:
  Bar* b;
};

Functionally references and pointers play the very same role. It's just a matter of contract. And unfortunately, both can invoke Undefined Behavior if you delete the object they refer to, there's no winner there ;)

Matthieu M.