Week 12 - Chapter 11 Pointers Review Quiz 4 ------------- Review Chapter 10 Homework -------------------------- Pointers -------- Pointers are a critical concept in C, and understanding some even very basic parts of the way C works requires understanding pointers. Pointers are a special kind of C variable. They do not hold a value, but rather point to a position in memory that *does* hold a value. Pointer variables are type-specific, and are declared just like a regular variable, except for the addition of the star character: int *p; The star goes between the type name and the variable name. What you are telling the compiler here is "create a variable called p that points to an int." To understand better, a memory map may help: int i; int *p; i = 10; p = &i; Let's assume 16-bit ints for now. Now, i is an int type, so it gets two bytes of memory. ( i ) ( p ) +--+--+ ... +--+--+ |00|0a| |03|a7| +--+--+ ... +--+--+ ^ ^ | | 03a7 0499 i is created at position 03a7 in memory. At first, is has unknown data, but then it is set to 10. p is similar to i. It is a variable, so it gets a position somewhere in memory (where does not matter, but let's say 0499). Now, we have two new things going on in this example. First is the star, which does set up p as a pointer variable. Second is the & in front of i. Now, we've seen the & before, in scanf, and now we learn what it means. It means "address of." By using the & in front of a variable, we ask C to tell us what is the variable's memory location. That value is then stored in the p variable. Well, that's great. We now have a whole new class of variable that can store another variable's memory location - big deal. Well, not only can we store the memory location of a variable, but once a pointer is assigned to a variable, the pointer can be used to modify the variable: *p = 12; Here, we use the star operator again. But this time, we use it in front of p, where before we left it off. When you use the star on a pointer variable, you are not changing the pointer variable itself, you are changing the data the pointer is pointing to. This is the same as if we said: i = 12; except that we did it indirectly. This type of assignment is called "indirection," for precisely this reason. Note a few things: Each data type has its own pointer type. You cannot mix and match them: int i; float *p; p = &i; // Can't do it You also want to be very sure that you never do two things. First, never use the star operator on an uninitialized pointer: int *p; *p = 20; // Bad!! Remember, all unitialized variables hold garbage, and that garbage, in the case of a pointer, is interpreted as a memory location. Never, never do this. Also, never set a pointer to a raw number: int *p; p = 10; // Probably should have used *p = 10; Here, the problem is that p will now point to position 10 in memory, which is not always possible. The position may be inaccessible. You can set indirection up on both the left and right hand sides of an assignment: int i; int j; int *p; i = 20; // Set i p = &i; // point p to i *p = 30; // change i to 30 j = *p; // Set j to 30 j += 20; // Set j to 50 p = &j; // point p to j You can also assign two pointers to each other - of course, the information they pass is memory location information. int i, *p, *q; i = 10; p = &i; q = p; Both q an p now point to i. If we said instead: *q = *p; Then we would have trouble, because q points nowhere useful. Again, this is all very interesting, but how does this apply? It applies, because it allows us to pass by reference. Pointer parameters ------------------ When we talked about functions before, we learned that we always passed data to the function by value - we made a copy of the data, and the function used the data in any way it needed to, without changing the original. I also mentioned passing by reference, and this is when we start to learn about it. fn(i,j); fn cannot change the values of i and j, except internally. fn(&i,&j); By changing the function call sligthly, the function can now change the data values passed, as long as the function is set up, too: void fn(int *a, int *b); In the function declaration, we tell C that fn will be getting two pointer values, not two values. The data will be passed by reference. Once the function knows the position in memory of a variable, it can change it. This is exactly what scanf does. void swap(int *a, int *b) { int temp; temp = *b; *b = *a; *a = temp; return; } int x = 10; int y = 20; swap(&x,&y); // now x is 20 and y is 10 This also gets us around the problem of having functions that can only return one value. While a function can still only return one value, it can now change more than one piece of information. void split_date(long, int *, int *, int *); void split_date(long date, int *month, int *day, int *year) { if (date == 0l) { *month = *day = *year = 0; return; } *year = (int)(date % 10000l); date /= 10000l; // remove last four digits *day = (int)(date % 100l); date /= 100l; *month = (int)date; return; } Returning pointers ------------------ A function can not only take pointers in the parameter list, but it can also return a pointer, if needed. The max function takes a pointer to two ints, and returns the pointer to the int that is higher: int *max(int *a, int *b) { if (*a == *b) return(a); if (*a > *b) return(a); return(b); } Note that when we return, we do not use the & or the *, because a and b, at this point, are already pointer variables. To use: int a, b, *c; a = 10; b = 20; c = max(&a,&b); // c will point to b In this case, you cannot do the following: You cannot pass a number to max: max(&10,&20) - not possible You cannot set the return value equal to a normal int: int d; d = max(&a,&b); // Not legal Note in this case, we are returning the pointer value of a known variable - it is known because it had to be passed to us. We would never return a pointer to a local variable: int *max(int *a, int *b) { int max; if (*a == *b) max = *a; else if (*a > *b) max = *a; else max = *b; return(&max); } This code is syntactically correct, but has a major flaw - it is returning the address of an automatic local, which is destroyed when the block finishes. To fix this issue, we can use the static keyword in front of "int max". Of course, we could also always do it the first way. const ----- The const keyword can be used with pointers and arguments to denote a parameter that you know your function will never change. Perhaps it will only examine it. You might help the compiler be more efficient if you can use the const keyword: int *double(const int *a) { *a *= 2; // Can't do it - the pointer is protected return(a); }