References & Pointers

Started by
5 comments, last by Zahlman 16 years, 6 months ago
I'm studying a C++ book and I'm stuck on one of the exercises. I was asked whether or not the following code would display the same memory addresses:

#include <iostream>
using namespace std;

int main()
{
	int a = 10;
	int& b = a;
	int* c = &b;

	cout << &a << endl;
	cout << &b << endl;
	cout << &(*c) << endl;
}



I suspected that they would, but I wasn't sure, so I typed it into my IDE, compiled, and ran it. They were the same, but heres what I don't get. &(*c) would equal &b, of course, but why would &b equal &a? b should equal &a, but shouldn't b have it's own memory address? I really shouldn't have read the chapter on pointers in front of the TV. [grin] [Edited by - Silicon Viking on October 10, 2007 6:32:49 PM]
----------------------------From the fury of the Northmen, O Lord, deliver us!
Advertisement
A reference could be understood as an alias, nothing but another name for the same object or an always-dereferenced pointer.
So in your case you should just think that b is another name for a.

You seem to think that a reference is actually a pointer (saying "b should equal &a") but this is not the case.
My Blog
Oh, okay! Thanks!
----------------------------From the fury of the Northmen, O Lord, deliver us!
References forward all operations, including the ampersand 'address of' operator, onto the referenced instance.

A reference can have its own address, however theres not much overly useful you can do with the address of a reference so there's no easy way to get it. If you need to do things like that then you'd use pointers instead as getting the address of a pointer is a simple case of using a pointer to a pointer.
The answer to this question may be too advanced for me, or there may be simply too many answers, but for what would you use the address of a variable? The book keeps talking about how efficient it is to pass a reference to a function, but other than that, why would I use either a reference or a pointer?
----------------------------From the fury of the Northmen, O Lord, deliver us!
Use a reference when:

  • you pass a complex type to function or method

  • you need to manipulate the value of a function/method argument

  • your method/function requires valid (e.g. non-null) input


Use a pointer if you accept invalid (e.g. null) input, since references cannot be null. Use a pointer also, if you need to perform pointer-arithmetic, because you can't do that with references. This includes the use of pointers as iterators for SC++L algorithms.

I hope this is somewhat correct - been a while since I last toyed with C++ [smile]
1) Forget about pointers. Until you get to the step where I tell you to remember them again, they don't exist.

2) A reference is a way of aliasing a variable that already exists. It has a type like Foo&, where Foo is the type of the thing being aliased. This is basically just so the compiler knows that you want to alias something, rather than create a new variable of the same type.

3) Because a reference *has* to alias something that *already exists*, a variable of a reference type *must* be initialized in its declaration, or the compiler will report an error (i.e. "what is this supposed to reference right now?")

4) Once the reference exists, for all intents and purposes, it *is* the referred-to variable. If you assign to it, it doesn't change what the reference refers to; instead, it changes the value of the referred-to thing. You *can't* make it refer to something else.

5) Why is this useful? Well, normally, when you pass data between functions (in via parameters, or out via a return value), the data is copied. The function works with its own copy of the parameters, and the caller's data is not changed. Similarly, if the function returns, say, some data member of a structure, then the caller gets a copy of that data member: if it changes the copy, the structure is left unchanged.

Sometimes, that's not what you want. By "passing by reference" (i.e. passing a reference to the caller's variable) or "returning by reference" (i.e. returning a reference to some already-existing data), you enable "sharing" of data between functions. That can make a difference to performance (since copying things takes time), but the *primary* reason for using references is because of the *semantics*; i.e. use them for situations where using a separate copy would be *wrong*. (In cases where we use them for performance, we often mark the reference as 'const': this is a promise not to change the referred-to value via that reference. If you don't change something, there's no risk of the calling function "seeing" a change that it shouldn't.)

References can also be used in some circumstances to avoid repeating code, but this technique, its practical applications, etc. are well beyond the scope of this post. (Besides which, when it *is* useful, it's often a sign that the code is badly organized in more fundamental ways.)

6) This can allow for writing obscure code: in particular, it's tempting to use pass by reference to communicate data back from a function, where a return value is more appropriate (i.e. by having the caller examine the changes to the thing it passed in). But it can also be very useful: for example, the standard library container std::vector does this, to allow you to treat it like an array. In particular, its operator[] overload returns one of the elements by reference, so that if the caller assigns to it (e.g. 'myvec[3] = 12;'), it changes the actual contents of the vector automatically.

7) How do we make this work? Well, conceptually, and making huge simplifications for teaching purposes, every variable has a location in memory - its address. We think of memory as linear: a billions-long line of buckets, each of which has numbers 0 through to whatever painted on them, and each of which holds one byte of data. We'll model this with tennis balls: the bucket can hold up to 255 balls in it, and it represents a number, according to the number of balls we put in the bucket.

8) Actually, all our variables are like this. We represent individual characters, for example (and again, we make huge simplifications by ignoring things like Unicode) with a bucket, using an *implicit understanding* that each possible number of balls in the bucket represents some specific symbol; e.g., 65 balls represents a capital 'A'.

9) Of course, some variables could be in more than 256 possible states, so we need multiple buckets (we take a few consecutive ones) to represent them. Common home PC hardware is designed to prefer taking the buckets in groups of four. The C++ 'int' type, on typical compilers for home PC hardware, will use a group of four buckets, which represent a number up to a few billion or so, by basically writing it in base 256 (i.e. the "most significant bucket" contains balls that are 256 times as "valuable" as balls in the next bucket, etc. down to the "least significant bucket", where balls have unit value, and we calculate the number as the sum of the total value of the balls).

10) Remember, though, the buckets have numbers written on the outsides, too. So, we can store a number in a 'quad' of buckets, and have, again, an *implicit understanding* that the number represents some *other* bucket. In particular, we could have it represent the first bucket in a sequence that's used for another variable.

11) So, *one* way that the compiler *could* handle references is to make a reference-variable store a number that indicates the referred-to variable, and *translate* all the code that modifies the reference, so that it instead fetches the number, finds the corresponding bucket(s) (referred-to variable), and modifies that instead.

12) But as far as you're concerned, references are magic. You don't need to think about any of these things; it's the compiler's responsibility to get them right. Sometimes it doesn't need to do any "indirection". It depends on context.

13) Now pointers exist. I deliberately arranged for this to be item 13 ;)

14) The pointer is a variable which is a special kind of number: it represents some memory address (bucket ID). But we can set it to *any* bucket ID. Because of how modern hardware works, the bucket we refer to might be in the middle of a group used for a variable, be part of a variable we're not interested in, be "allocated" (assigned for use) by another program (on modern operating systems, though, if we try to use those buckets, we'll simply crash; we can't actually affect other programs so easily), or in fact not exist.

15) Because of this, they are potentially very dangerous. However, they can also be quite useful, and it is possible in C++ to design classes that 'wrap' them and let us avoid doing "wrong" things with them.

16) There is no magic with pointers. If you assign to the pointer variable, you really assign to it, and change what it points at. (Notice the difference in terminology: "refer to" vs. "point at".) If you want to access the pointed-at thing, you do it manually, via the '*' syntax in C++ ('*ptr' gives you the thing that ptr points at, assuming it really is a pointer).

17) Because there is no magic, C++ doesn't require that you initialize pointers right away. But of course, an uninitialized pointer could be storing *any* memory location, and trying to grab what's at an *unknown* location is a *very* bad idea ("go on, I DARE you to reach in there").

18) For these reasons, the rule of proper C++ design is: use references when you can, and pointers when you have to. There are, of course, a few other such rules of the form "use X when you can, and (raw) pointers when you have to" - the idea being, take whatever steps you can to protect yourself, because in most cases, you won't really pay any performance cost.

19) One of the main reasons for using (wrapped) pointers is the creation of complex data structures, which either need to "share" data, hold on to "optional" data, or hold on to different kinds of data (of different sizes) depending on context. We can share data by declaring a structure that holds a pointer-to-something, and have two instances of the structure have pointers which point at the same thing. In C++, we most commonly hold different "kinds" of data by setting them up to be related through inheritance, and then using a pointer to the base class (which is allowed to point at instances of any derived class, as well, without a cast). For "optional" data, the convention exists that a pointer can be "null"; such pointers are considered to not, in fact, be pointing at anything (another thing references can't do). Note that assigning an integer 0 to a pointer causes it to be null, but the reverse does NOT hold: depending on your platform, the bits that make up a null pointer do NOT necessarily represent a 0 value (i.e. there may be balls in those buckets). The value 0 is handled as a special case by the C++ compiler, and assigning other integers to a pointer value is not legal in C++. (Although you can certainly assign an integer to a pointed-at thing, if that thing is of an appropriate type.)

20) With the '&' syntax, you can ask for the 'address of' a variable - i.e., a value which is a pointer to the variable. This lets you check where things are in memory. In particular, comparing '&a == &b' checks whether a and b are "the same thing", as opposed to merely being equal. (Here we assume that they are of the same exact type.)

Unfortunately, the syntax is a bit confusing, but you must remember that the '&' and '*' you use in expressions are totally different things from the '&' and '*' you use to declare a variable type. Context is king.

21) In C, there were no references, only pointers. They made the '*' symbol work like that so that you could read 'int *foo' and think "*foo is an int". In C++, we are more interested in reading 'int* foo' and thinking "foo is a pointer to an int", because there is more emphasis on type theory in C++. But in both languages, you could write either 'int *foo' or 'int* foo' and have it mean the same thing - or even 'int * foo', for that matter. The whitespace is simply ignored here, in the same way that '2 + 3' and '2+3' are equivalent.

Unfortunately, C++ inherited all of C's behaviour in this regard - in particular, 'int* foo, bar' means that 'foo' is a pointer to an int, but 'bar' is an int. (Looked at from the C perspective, '*foo' and 'bar' are both ints.)

C used the '&' symbol as well to mean "address-of" (to create pointer values), but didn't have the reference meaning (since there were no references in C). The confusion between the uses of '&' in C++ is therefore particularly unfortunate (because it seems to have been set rather arbitrarily). However, it's not that difficult to understand, and should not cause you any problems with a little practice.

22) Therefore, in the original code, 'b' is a reference to 'a', meaning that it IS 'a', for all intents and purposes. So it should come as no surprise that asking for its address gives the same result.

This topic is closed to new replies.

Advertisement