20
Pointers, demystified by John Runchey www.teach-me-c.com

Pointers Demystified

  • Upload
    gunaj

  • View
    93

  • Download
    3

Embed Size (px)

Citation preview

Page 1: Pointers Demystified

Pointers, demystified

by

John Runchey

www.teach-me-c.com

Page 2: Pointers Demystified

1 IntroductionBeginners at my website, www.teach-me-c.com, are often confused about pointers more than anything else. This is totally understandable, as pointers can be a real challenge to wrap your mind around when you are first exposed to them.

I believe part of the confusion is just the name 'pointer'. It isn't exactly very descriptive, is it. Pointer, pointer, what the hell is a pointer? Is it a laser pointer? Is it a hunting dog? Maybe it's my index finger, pointing at some kid riding his bike through my lawn.

No, a pointer in C is none of these things. My sincere hope is that by the end of reading and studying this document, when you hear the name 'pointer', you'll have a very clear picture in your head of just what that means.

And unless I fail miserably, that picture in your head will not be a hunting dog.

Page 3: Pointers Demystified

2 Don't let the name scare you – it's still just a variableThink about an integer variable, or a floating point, or a character. Pretty easy to understand, right? It's just a place to hold a value – an int variable holds an integer, a char holds a character, and so on. This can be grasped pretty quickly, I believe, because you can kind of imagine a holding place, like a box, where you stick an integer or a character.

A pointer variable is the same thing – it's also just a container that holds a value, just like an int or a char variable holds a value. The only difference is, the value that gets put in that variable box is a memory address, instead of a number in the case of an int, or a character in the case of a char.

Let's make a little picture of that. Imagine the memory within your computer, as a bunch of storage boxes all in a row ('address' is the memory address inside your computer):

Now when we do some variable assignments, they get put into these boxes:int x, y, z;

x = 5;

y = 10;

z = 15;

Now keep in mind the 0,1,2,3 I used for addresses are in concept only – the actual memory addresses in your PC will not look like this, I know. So please

Page 4: Pointers Demystified

don't email me correcting me on this – I'm just illustrating the principles.

Now let's bring a pointer into the mix, a pointer to an int:int * myPointer; // declare our pointer

myPointer = &z; // initialize it to point to Z

Notice that the address of Z was put into the myPointer variable location. This is what the & means – address of.So now we can say that myPointer points to variable Z:

NOW, when we access the pointer using the * operation, we get what's located in memory 2, because that's what myPointer points to:printf(“myPointer is pointing at: %d\n”,*myPointer);

“myPointer is pointing at: 15”

Page 5: Pointers Demystified

3 Why do we use pointers?First off, on the simplest level, pointers are used to allow a function to modify a variable that is passed into it. Normally, when you pass, say, an int to a function, that int is copied into a local copy that the function operates on. For instance, consider the following:int x;

x = 5;

someFunction(x);

void someFunction( int n )

{

n = n + 1;

}

In the above example, the variable x is passed into the function, but x is unchanged by what happened inside the function. It makes no difference what the variable names are, either:int x;

x = 5;

someFunction(x);

void someFunction( int x )

{

x = x + 1;

}

Exactly same result. The x in the function declaration, void someFunction(int x), is local to the function someFunction. If you were to print the value of x after the function call, it would still be 5.

So what do you do if you want to modify the value of the variable x that you are passing to the function?

In that case, you pass a pointer to the variable, and the function is declared as receiving a pointer instead of an int:int x;

x = 5;

someFunction(&x); // notice the & (the “address of” operator)

void someFunction( int * x )

{

Page 6: Pointers Demystified

*x = *x + 1;

}

NOW when you return from that function, x is changed to be 6 instead of 5. Because you passed in a pointer to the variable, in other words the address of x, the function was able to operate on the actual variable itself.

Secondly, pointers enable you to do operations with far less overhead, or extra work, involved. What do I mean by this? Let me give you an analogy.

Suppose you have a friend who is a very fast reader. You've found a small local library that contains 800 books that you know your friend will want to read.

Consider the following 2 options you have in order to get these books to your friend. You could,

a. check out all 800 books, load them up in your car, and drive them over to your friends house, or

b. call your friend and give him the address of the library

Obviously, option b involves less work, less overhead, correct? Of course it does, because you aren't actually physically moving those 800 volumes - you are simply letting your buddy know where they are located. You might say you are pointing him to their location.

This is the essence of pointers – rather than copying blocks of data and moving them around, you pass around addresses of where these blocks of data are located.

Remember, a pointer is just that – an address in memory.

Applications that do a lot of data manipulation can be made far more efficient by using pointers to refer to data, rather than moving around the data itself.

Lastly, Pointers are also used to link together chunks of data in your code. For example, at www.teach-me-c.com in the membership section, I cover creating an electronic address book using a data structure called a linked list.I'll cover this in more detail later on in this document.

Page 7: Pointers Demystified

4 Declaring a pointer variable: pointer to an intAlright so that might have been a bit heavy for an introduction to pointers. Let's cover something simpler – how you declare a pointer to an integer.

The syntax is pretty straightforward:int * variableName;

The * can be separated from the name by a space, as I show above, or it can be connected together with the name itself:int *variableName;

Some prefer the former notation, some prefer the latter. Personally I prefer the former, where the * sits on its own, but that's just me.

A typical case for using a pointer to an int is as a function parameter, where you want the function to have access to an integer variable that is passed in to it.void squareIt(int * x) {

*x = (*x)^2; // replace *x with the square of *x

}

Now if we call this function:int y;

y = 10;

squareIt(&y);

When we return, y will have been squared, and will be 100. Since we passed in a pointer to y, the function could modify it directly.

5 Declaring a pointer variable: pointer to a doubleAlmost not worth mentioning since it is so similar to the int case:double * variableName;

6 Declaring a pointer variable: pointer to a charAgain very straightforward:char * variableName;

Page 8: Pointers Demystified

7 Pointers and character arrays (strings)Pointers and strings go together hand-in-hand, because in C a string is simply an array of characters. And since pointers and arrays go hand-in-hand, it follows that pointers and strings get used together a lot.

Let's define a string, a pointer to that string, and an additional char pointer, tmp:char testString[] = “this is a test”;

char * ts;

char * tmp;

ts = testString;

Now pictorially we have the following:

The reason I say tmp is “pointing at garbage”, is because char * tmp only allocates space for the variable tmp; we have not yet assigned tmp to point to anything.

Once we assign tmp to a value, then it points somewhere:tmp = &testString[5]; // point to element 5 of the array

Now remember, in C arrays start at 0 (zero). So by setting tmp to the address of element 5, we now have this situation:

Page 9: Pointers Demystified

Now we can do arithmetic on the pointers to 'move them around' the string. For instance, suppose we want to print each character of the string – we could do a loop something like this (remember ts is already pointing to the start of the string):while (*ts != NULL) {

printf(“%c “,*ts);

ts++;

}

What this does is 'walk the pointer' along until it hits the null at the end of the string (I removed tmp just to make it cleaner):

Or suppose we want to remove the 'space-A-space' and make the string “this istest”. We could do that as follows:tmp = &testString[7];

Page 10: Pointers Demystified

while (*tmp != NULL) {

*tmp = *(tmp+3);

tmp++;

}

Now think about what that is doing. We are setting tmp to point to the space right before the 'A'. When we do *tmp = *(tmp+3), this takes the 'T' from position 10 and puts it in position 7:

Then we move tmp to the next position, and do it again... and move it, and do it again, and so on, until we've finally moved the '\0' (the null) over as well:

So as tmp marches along, we copy from 3 spaces ahead of it:

T overwrites space (char at position 10 copied to position 7)

Page 11: Pointers Demystified

E overwrites A (position 11 to 8)

S overwrites space (position 12 to 9)

T overwrites the first T (position 13 to position 10)

\0 overwrites E (position 14 to 11)

Notice at the end, we still have the S T \0 in position 12, 13 and 14. These are of no consequence, because the string now ends at position 11, since the null is located there.

Character manipulation with char pointers might be a bit of a mystery at first, but eventually it will make sense. When in doubt, draw it out! Looking at a bunch of *this and *that and this++ and that++ is confusing; putting it in a picture will quickly clarify what the code is really doing.

Page 12: Pointers Demystified

8 Pointers to structuresRemember that a structure is a block of variables, brought together into a logical 'structure' (hence the name):struct myRecord {

char firstName[80];

char lastName[80];

int employeeID;

struct myRecord * nextRecord;

};

Now what is going on here – we've got a pointer, to a structure of type myRecord, within myRecord itself. Won't this cause a black hole or a quantum singularity or something?

No, it won't, it's perfectly normal. You can think of the above structure like this:

Where the nextRecord pointer allows you to connect one of these structures to another one. So as you put data into these structures you can connect them together:

You now have the beginnings of a linked list, which is discussed later in this document.

Page 13: Pointers Demystified

You can also declare a pointer to a structure outside of the structure itself, for instance:

struct myRecord * headOfList;

You'd do something like that to establish a pointer that allowed you to find the beginning of your list:

If you 'get' this so far, continue on with the linked list discussion in the next section. You can also see a linked list video at:

http://www.teach-me-c.com/lesson/introduction-linked-lists

Page 14: Pointers Demystified

9 Using pointers for a linked listIn a linked list, records, or blocks of data, are connected together using pointers. Suppose your structure contains a first name, and an age, and you want them sorted based on the name:

In this case you begin searching the linked list starting at the head, and move along via the 'next' pointer in each structure, until you find the name you are looking for. Inserting a new record just involves moving around the pointers:

Page 15: Pointers Demystified

Now what about actual code for managing this linked list. Here is a little bit of an example. We'll use the same structure that we defined earlier. In addition, we'll define a head pointer so we know where our list starts:

#include <stdio.h>

#include <stdlib.h>

struct myRecord {

char firstName[80];

char lastName[80];

int employeeID;

struct myRecord * nextRecord;

};

struct myRecord * headPtr; // point to first structure in the list

struct myRecord * currRec;

// this allocates space for the structure data

headPtr = (struct myRecord *)malloc(sizeof (myRecord));

strcpy(headPtr->firstName,”John”);

// allocate space for a second structure

headPtr->nextRecord = (struct myRecord *)malloc(sizeof(myRecord));

currRec = headPtr->nextRecord; // currRec now points to 2nd structure

strcpy(currRec->firstName,”Karl”);

currRec->nextRecord = NULL; // end of list

// now lets print the first name from each structure

currRec = headPtr;

printf(“%s\n”,currRec->firstName);

while (currRec->nextRecord != NULL) {

currRec = currRec->nextRecord; // move to next block in list via pointer

printf(“%s\n”,currRec->firstName);

}

(note: If you cut and paste this into a compiler, you may have to replace the double quotes (“). Visual c++ express didn't like the quotes that got pasted from here)

Alright that's a lot to absorb right there, so that's all I'm going to do for now.

Page 16: Pointers Demystified

Essentially what we've done is:

• allocate space for 2 structures worth of data

• set the head pointer to point to the first block

• set the 1st blocks nextRecord pointer to point to the second block

• used currRec to travel from the head block to the second block, printing the name field of each block as we go.

And what about that malloc business? Consider the following line: headPtr = (struct myRecord *)malloc(sizeof(myRecord))

What exactly is happening here? Let's take it one part at a time:

sizeof(myRecord)

This line is a library function call to sizeof(), which returns how many bytes (how much computer memory) is required for a structure of type myRecord. The sizeof() tells us how much memory is needed for the data in one structure.

Malloc( … )

This is the Memory Allocate function. It tells the compiler “Hey give me this many slots in memory for my use.”

(struct myRecord *)

This is what is called a cast. Malloc doesn't know anything about the structure that you are allocating memory for. It doesn't know that it has a firstName and a lastName field. It only knows it is setting aside a certain number of bytes of memory for you, and that it is going to return to you the address of the start of this memory block.

The cast, the (struct myRecord *), tells the compiler that headPtr is pointing to a block of memory that is a myRecord structure.

Don't worry if you don't get all this. I have to look it up in my C book or on google when I haven't used it for a while too.

Page 17: Pointers Demystified

10 Using pointers for a binary tree Another useful data structure that uses pointers is the binary tree. A binary tree is a data structure that is used to hold items in a sorted way to make finding an item efficient.

Suppose you have a large list of numbers, and you want to be able to determine if a certain number is in that list. Given a list of numbers like this:

You could load those number into a binary tree, where each element of the tree has a left and right pointer. You travel the left pointer if the value is less than the current, and the right pointer if the value is greater than. The first number, 32, is added to the tree. It becomes the root of the tree (think of it as an upside-down tree):

Then we add the 10. The rule in a binary tree is that if the item is less than the current one, it goes to the left. If it is greater than, it goes to the right.

So anything less than 32 goes to the left, greater than 32, to the right.

10 is less than 32, so it connects to the left pointer:

Get it? 10 is connected to the left pointer, because 10 is less than 32.

Now we add the 8. We start at the 32, go left to 10, go left again, and add it there ( 8 is less than 32 – go left; 8 is less than 10; go left):

Page 18: Pointers Demystified

We continue adding one number at a time, until our tree is completely built up:

Now think of a number, and starting at the top, compare it to 32 and go either left or right. If you find your number, you're done. If you find a pointer that goes noplace, then your number isn't in the list. (note: the 1, 9, 26, 42 and 112 at the bottom would have their left and right pointers pointing to NULL. This is how you know you've hit the end of the tree)

So let's say our number in question is 42.

42 compared to 32 – bigger, go right

42 compared to 46 – smaller, go left

42 compared to 42 – we've found a match, we're done.

Imagine you've got thousands of entries in this tree – having them sorted this way makes searching far more efficient than just a brute-force linear search. In the above example, it took only 3 comparisons to find our match within a set of 9 numbers.

Code wise, a binary tree would contain a structure with a couple of pointers. Suppose our list is sorted based on employee ID:struct myRecord {

char firstName[80];

char lastName[80];

int employeeID;

struct myRecord * smallerEmpID; // the 'left' pointer

struct myRecord * largerEmpId; // the 'right' pointer

};

I won't bore you with any more code; that is enough to get the basic idea.

Page 19: Pointers Demystified

11 Pointers to pointersI'll only touch briefly on pointers to pointers, because frankly they are a bit overwhelming for the beginner, and they aren't really that commonly used that you need to worry about them.

The usual example for pointers to pointers is when you are dealing with a bunch of character strings.

char ** bunchOfStrings;

bunchOfStrings points to a char pointer.

That first char pointer points to the first string.

Then when you do bunchOfStrings++, bunchOfStrings now points to the pointer to the second string.

This really needs a picture, but I don't have my tablet handy at the moment. If you have a need to use pointers to pointers for some reason, drop me an email at [email protected] and I'll do my best to help you out.

12 Pointers to functions Just when you thought it couldn't get any worse, right? What in the world is a pointer to a function, and why would you want one?

Again this is an advanced topic that you probably won't run into much, if at all. Pointers to functions allow you to 'pass around' a function within your code. So a function can become an input of another function.

For instance, you might have a very general purpose binary tree routine, that knows nothing about the information it needs to store. It just knows it needs to deal with left and right pointers, and inserting, deleting, and searching for an object.

How do function pointers come in here? Well you could have an insert function that took 2 parameters (and this is not real code, just for explanation):

insertIntoTree(itemToInsert, func * compareFunction);

The first parameter, itemToInsert, is what you want to put IN the tree. The second parameter, compareFunction, is a pointer to a comparison function.

This comparison function knows how to compare items of the type itemToInsert.

What your insert routine does, is calls your compareFunction with an item from the tree and the itemToInsert, and the compareFunction returns whether

Page 20: Pointers Demystified

the item needs to go to the left or the right in the tree.

If you are just getting started with pointers, don't even think about function pointers.

Just know in the back of your head that they exist.

13 About the authorI'm John Runchey, creator of www.teach-me-c.com. I'm sincerely interested in helping others learn computer programming. I've been doing this in one capacity or another for about the last 20 years.

I believe I have the ability to explain difficult concepts in terms beginners can understand. If you have any questions, visit www.teach-me-c.com or drop me an email at [email protected].

And if you've found this at all helpful, please pass it along to someone else who might benefit from it.

Happy Programming!

- John