[Pointers] [Rob Bowden] [Harvard University] [This is CS50] [CS50.TV] Let's talk about pointers. Up until now, we've always just referred to things in memory explicitly by name. We've said int n=42; and then when we want to use the variable n, we just call it by the name we give it by writing something like n*2. But that variable must live somewhere in memory. When you want to use the value that is currently stored inside n, or update the value that n is holding, your program needs to know where in memory to look for n. Where in memory a variable lives is called its address. It's like a house address. I can find someone's house as long as I know their home address, and a computer program can find a variable as long as it knows its memory address. What pointers provide is a way of directly dealing with these memory addresses. A lot of the power in C comes from being able to manipulate memory like this. But with great power comes great responsibility. Pointers can be used dangerously enough that a lot of programming languages hide pointers entirely. So, why does C give us pointers then? As you know, arguments to a C function are always copied into the parameters of the function. So, calling something like swap on some variables x and y can't interchange the values of x and y in the calling function, even though that might be handy. As we'll see later, rewriting swap to take pointers to the locations needing to be swapped allows it to affect its caller's variables. Let's walk through a relatively straightforward example of what pointers can do. Let's say we have int n=4; and int * pointer_to_n=&n. Whoa! A bit of new syntax to cover. First, let's interpret this &n. Remember that everything in memory has some address. The ampersand is called the "address of" operator. So, &n refers to the address in memory where n is stored. Now, we are storing this address in a new variable, pointer_to_n. What is the type of this new variable? The asterisk is a part of the variable's type, and we'll read the type as int*. Int* means that pointer_to_n is a variable that stores the address of an integer. We know that &n is an int* since n is an integer, and we're taking the address of n. Int* is an example of a pointer type. As soon as you start seeing asterisks in the type, you know that you're dealing with pointers. Just like we can declare a variable as int x and char y, we can say int* z and char* w. Int* and char* are just new types for us to use. The location of the * can go anywhere before the variable name. So, both int* pointer_to_n--with the * next to int, as we have here-- and int *pointer_to_n with the * next to pointer_to_n are valid. But here, I'll place the * next to int. It doesn't matter which you prefer, just be consistent. Let's draw a diagram for this. We first have the variable n, which we'll draw as a little box of memory. For this example, let's say that this box is located at address 100. Inside of this box, we're storing the value 4. Now, we have a new variable, pointer_to_n. It has its own box in memory, which we'll say is at address 200. Inside of this box, we are storing the address of n, which we before said was 100. Frequently in diagrams, you'll see this shown as a literal arrow leaving the pointer_to_n box pointing to the box that stores n. Now, what can we actually do with pointer_to_n? Well, if we say something like *pointer_to_n=8, this is a different use for the asterisk that is completely separate from the use of the asterisk in declaring a variable of a pointer type. Here, the asterisk is called the dereference operator. In our diagram, what *pointer_to_n=8 means is, go to the box containing pointer_to_n, follow the arrow, and then assign to the box at the end of the arrow the value 8. This means that after this line, if we try to use n it will have the value 8. The word 'pointer' is used in a lot of different contexts. Here, we'll try to be consistent. A pointer type is something like int*. In this video, a pointer will only be used to mean a value with a pointer type, like pointer_to_n which has type int*. Anywhere we used to just say n, we can now instead say *pointer_to_n, and everything will work just as well. Let's walk through another simple example. Let's say we have int n=14; int* pointer=&n; n++, and (*pointer)++. The first line creates a new box in memory labeled n. This time, we won't label the box with an explicit address, but it still has one. Inside of the box, we're storing the number 14. The next line creates a second box labeled pointer. And inside of this box, we're storing a pointer to the box labeled n. So, let's draw the arrow from pointer to n. Now, n++ increments the value in the box labeled n, so we go from 14 to 15. Finally, (*pointer)++ goes to the box labeled pointer, dereferences the value in the box, which means follow the arrow to where it points, and increments the value stored there, so we go from 15 to 16. And that's it. N now stores the number 16 after having been incremented twice-- once directly by using the variable name n, and the other through a pointer_to_n. Quick quiz. What do you think it means if I try to say something like &&n? Well, let's rewrite this as &(&n) which accomplishes the same thing. The (&n) returns the address of the variable n in memory. But then then outer ampersand tries to return the address of the address. That's like trying to do &2. It doesn't make sense to get the address of just some number since it's not being stored in memory. Using two ampersands in a row is never the right idea. But now, what does it mean if I try to say int** double_pointer=&pointer? Now, I'm creating a new box labeled double_pointer, and inside of that box I'm storing the address of pointer, which means I draw an arrow from the double_pointer box to the pointer box. Notice the type of double_pointer, an int**. N was an integer, pointer stored the address of n, and so it has type int*. Now, double_pointer stores the address of pointer, so it has type int**. So, what do we think this means-- **double_pointer=23? Notice that I'm now dereferencing twice. Just follow the box-and-arrow diagram we've already set up. First, we go to the box labeled double_pointer. The first * means follow the arrow once. Now, we are at the box labeled pointer. The second star says follow the arrow again, and now we're at the box labeled n, and we set the value of this box to 23. Notice that the dereference and 'address of' operators are inverses of one another. This allows me to do something like *&*&n=42. While this works, you should never do something like this in practice. What are we actually doing here? The first ampersand grabs the address of the variable n. Then, we have a dereference operator, which means we are going to that address in memory, so we're back at n. Now, we grab the address of n again and immediately dereference, so we're back at n and store 42. So, each pair of *& just cancels out. There is a special pointer called the null pointer. This is a pointer that we should never dereference. Such a pointer is important because it gives us a way to distinguish between a pointer that should and should not be dereferenced. If you try to dereference a null pointer, typically your program will crash with a segmentation fault, which you may have seen before. So, let's say we have the code int* x=null; *x=4. In this example, it may seem obvious that we're doing something bad, but remember that null could really be a value returned from a call to a function like malloc, if malloc is unable to allocate the memory requested by the user. For this reason, if instead we set the value of x from a call to malloc, as in int*x=malloc(sizeof(int)), then we should always explicitly check to see if null was returned. If (x==null); //uhoh! return; else we can continue on and say *x=4. So, again, why should we ever use pointers? Let's look at an example of a program where we need to use pointers-- a simple swap function. Let's say I have two integers, int x=4; and int y=15; and I want to write a function called swap that I can use like so: swap(x,y). After this line, the values inside of the variable x should be 15, and the value inside variable y should be 4. The values inside of x and y have been swapped. Without pointers, we might try something like void swap(int a, int b); int tmp=b; b=a; a=tmp. But, do you notice the problem with this? Remember that the value stored in the variable a is just a copy of the value of x, and the value in b is copied from y. Any changes made to a and b will not be reflected in x and y. So, while the values of a and b are correctly swapped, x and y haven't changed at all. Now, let's change the swap function so that its arguments are pointers to the variables it should swap, like so: void swap (int*a, int*b); int tmp=*b; *b=*a; *a=tmp. Remember that swaps arguments are now pointers, and so we need to pass the address of x and y in the call to swap, like so: swap(&x,&y). This now correctly swaps the values of x and y. Let's draw a box-and-arrow diagram to see why this works. We start with our two boxes in memory, x and y. Inside of the box for x is the number 4, and inside of the box for y we have 15. Now, inside of the call to the swap function, we have two more boxes for the arguments a and b; a points to the box for x, and b points to the box for y. A new box is created for the variable tmp, and inside of it we store the result of dereferencing b, which means 'follow the arrow from the box labeled b.' So, we store 15 inside of tmp. Then, we follow the arrow at b and store here the result of dereferencing a, which is the value 4. Finally, we follow the arrow at a and store what's currently inside of tmp, which is 15. Notice that the boxes labeled x and y have correctly swapped values. Once we learn more about malloc and dynamic memory management, we'll see that we have no choice but to use pointers. Walking through the box-and-arrow diagram for any program can help you figure out what the program is really doing. My name is Rob Bowden, and this is CS50. [CS50.TV] This is a different use for the asterisk--bleah, I hate that word. Anywhere we used to just say n, we can now say pointer_to_n--no you can't--*pointer_to_n.