Pointers

Pointers –an explanation.

Pointers, pointers, pointers.
How best to deal with this issue I wonder?

Pointers are easy to explain once you get past the initial conception issues, but they can get very complicated very quickly once you try to digest all you can do with them, and the implications this can bring up.

Simply put – quoting from an earlier lesson, -:

Pointers are a way of pointing at where variables sit in memory rather than the value of the variable. If we have a variable named x, and it has a value of 10, then x would equal 10. x = 10. Ok. However, this variable is actually stored somewhere in memory. We might want to know the address of this location in memory (for reasons that will become more clear later) and we can do this by looking at a pointer to x. If we put an & in front of the variable x, then we are talking about the location of variable x, not the actual value of variable x. For instance

x = 10;

Will set x to 10. Ok. However

pointer_to_x = &x;
Will give us the location in memory of the variable x inside of the variable pointer_to_x. This can be desirable, if not just because some functions demand pointers. We’ll get into implications of this later.

Ok. That seems fairly straightforward. A pointer then is a variable that points to memory. What is at that location can be anything, depending on how the pointer is defined to begin with. So how do we define pointers then?

Aha. Here is the secret bit:).

Pointers are defined almost the same as variables are. Lets say we have a variable type we have defined ourselves, like this

typedef struct
{
int            emp_num;
char         name[50];
char         addr_line1[50];
char         addr_line2[50];
char         town[50];
char         state[50];
char         zip[5];
} emp_rec_t;

We then want to define this an actual variable called emp_rec like so; -

emp_rec_t            emp_rec;

This will, according to all we have been taught so far, create a variable of this type, and go away and reserve space for this variable within the scope of where ever we defined this variable. If it’s in a procedure, then we can only access this variable within the procedure, the memory that this variable uses is released once we exit the process.

However, if we declared something like

emp_rec_t            *emp_rec;

We will get something entirely different. What we get this time is a pointer to a variable of type emp_rec_t. emp_rec itself will not contain anything, in actual fact emp_rec will be a four byte variable, that is un-initialized, which means we have no idea what it will be pointing at. It’s important to realize that putting the asterisk infront of the variable name means that instead of having a variable to use as want to, we have the pointer to a variable. The idea is that you will then create a variable yourself through other means (and we’ll go into that in a second) and use emp_rec as a pointer to that bit of memory where that variable resides.

Assuming that emp_rec does actually point at some valid memory, then you would access your variable data with a ‘->’ symbol rather than the period we would use if you declared the variable directly.

So if we declared the variable

emp_rec_t            emp_rec;

then we would access the variable emp_num by the syntax ‘emp_rec.emp_num’.
However, if we declared the variable

emp_rec_t            *emp_rec;

assuming that emp_rec actually pointed at valid memory, we would access the variable emp_rec with the syntax ‘emp_rec->emp_num’. Get the difference? One-way means “I have the complete variable in my scope” the other means “I have no idea where the memory this variable uses is, and I don’t care, since I have a pointer to it”.
Malloc, Free and Memory

Now of course all of this assumes that the pointer emp_rec does actually point at something valid. If we just declare it as we have above, it’s not pointing at anything, so this is not good. If we want it to point at something legal, we need to actually reserve some memory for the variable type to reside in. We do this using the malloc command.

Right now, before we get into that, we need a little bit of background on how memory is managed inside of the C run time system. When you build a C program, the compiler looks at each procedure you define and reduces the code out to assembly language. Not only does it do that, but for each procedure it reserves some memory for each variable you declare. So, if you have a procedure that has the variable emp_rec defined as a direct variable, then the size of the procedure in compiled code (called Object Code) will be the compiled code plus the size of the emp_rec_t structure. Get the idea? This is called static variable definition (and has nothing to do with the static C command either). However, when you run your 640k program, it’s obviously not using all 32 megs you have your disposal, but you can actually get at this memory and use it. When the C program runs, it basically looks at how much memory is free and steals some of it (and how much is variable, depending on the memory management system your computer system uses) and creates what is called the ‘heap’. The heap is all the free memory your program is going to have access to, and is typically all the free memory in your system once you operating system is running and your program is running. It isn’t always though, since you might have two programs running at the same time. Your operating system takes care of all this so it’s transparent to you, unless you require the entire memory of your machine to run your program. Even then, the Windows Virtual Memory system can help out, where it uses some hard drive space as memory. But that’s getting a bit too in-depth.

Suffice to say there is this heap of memory. You can steal ‘bits’ of it to store your data in, or variables, or anything you want to really. There is a memory management system that will keep track of what you’ve used, and what’s free, and let you have it, assuming it has enough space to give you. This is what the C commands Malloc and Free do for you.
Malloc will return a pointer to a chunk of memory that you have requested, and the memory management system will then internally mark that memory as used, and as such will not give it to you again if you ask for more memory until you free it up up.

To use Malloc you would code something like this.

emp_rec_t            *emp_rec;

emp_rec = malloc(sizeof(emp_rec_t));

This is fairly straight forward. You are requesting malloc (or the system) to allocated you some memory the size of which is the emp_rec_t data type definition size. It will return a pointer to this memory and put this memory address inside of the pointer emp_rec. Now you have to be careful with this pointer and not lose it, since it’s the only place you have this memory address, and you’re going to need it to pass to the Free function inorder to give this memory back to the system. To give the memory back, you code something like this

free(emp_rec);

This tells the system – “ok, I’m done with this bit of memory, you can have it back now”.
Some notes – When malloc returns a pointer, it might also return a zero instead of a memory location. If it does, there is trouble. You have asked for either more free memory than the heap will allow you to have, or there is enough memory, but the heap has become fragmented, and the memory you need is all free in one chunk. Either way, it’s trouble, and whenever you call malloc you should put some error checking in to make sure you did actually get memory back like you thought you should. If you didn’t, you need to make the user aware of this fact, and exit the routine/program gracefully. Sometimes the amount you ask for might be totally beyond your control – you have no idea how much is being asked for at any given time, (for instance, if you want to load a file into memory you would need some memory from the system to load it into. You would malloc the size of incoming file to put that data into, and the file might be absolutely huge), and in these instances it’s even more important to check to see if the value returned by malloc is valid.

Heap Fragmenting

But what if it isn’t? What might cause this? One is just asking for too much memory. There’s not much you can do about this if this occurs, beyond informing the user. However the second instance this might occur might be avoidable by good programming. When the heap fragments it means that a bit in the middle might be still being used by your program, but most of the top and the bottom is free. If you add together all the free space, you might have 2 megs. You ask for 1.5 megs, but malloc returns an error. What is going on? Well, if you’ve used the bit of memory right in the middle of the heap then there isn’t 1.5 megs of consecutive memory free for you to have. You can have two bits of .75 megs if you want, but not 1.5 in one lump sum. Knowing this is possible it can be arranged when you code to ensure that you malloc and free consecutively in order to grow and shrink the amount the heap is used in one lump, rather than mallocing a ton a objects, then freeing all the first ones, and leaving the last ones being used. This will totally fragment the heap.
Think a bit about this – this is an important concept to get your head around, and it’s important that you understand it thoroughly.

Something else to bear in mind that if you attempt to free memory that you haven’t malloced, or have already freed, or if the pointer is just pointing off into wonderland memory, then the program will CRASH. Yes Crash. Its meant to do this, that’s what the memory manage is designed to do. So be careful.

Arrays & getting a memory addressSo what if we have a variable we’ve already created, and we need to know where it is actually located in memory? Actually, we have already done that in an earlier example. Remember the line in the original definition of a pointer that went like

pointer_to_x = &x;

Here we see how to get the location in memory of a variable type. By placing an ‘&’ in front of the variable type we are telling the system “I don’t want the actual variable contents, I just want to know where it is in memory”. This can be extremely handy later on when we get into pointer passing.

Incidentally, when you create an array of anything, you are actually doing the equivalent of creating a pointer and doing the malloc in one go. For instance

char            blah[100];

Is exactly the same as

char      *blah;
blah = malloc(100};

Except you don’t have to free blah once you’ve finished using it. This has an implication. This means that effectively you can use a pointer as an array type once it points to something meaningful. That’s a bit complicated, so I’ll try and explain. Lets look at the following code

emp_rec_t            *emp_rec;

emp_rec = malloc(sizeof(emp_rec_t) * 10);

emp_rec[0]->emp_num = 1;
emp_rec[1]->emp_num = 2;
emp_rec[2]->emp_num = 3;

And so on. Obviously in-efficient code. What we are doing is creating a pointer, pointing it at enough memory to fit 10 copies of the emp_rec_t structure in, and the setting some of the values in that memory.

But the point being made here is that even though emp_rec was declared as a pointer, we are using it as an array type of pointer. C allows you to use a pointer effectively as the start of an array in memory. The size of each entry in the array is defined by the type of pointer we are using. So each entry in the array of emp_rec would be the size of the definition of emp_rec_t. If emp_rec was defined as a char* then each entry in the array would be one byte.

Imagine memory as one long chain of bytes. By having a pointer defined to look at a certain data type pointing at it, you are effectively organizing memory in a way defined by you. The memory itself remains exactly the same, you’ve just put some new glasses over the way you look at it that organizes it a certain way.

It’s quite possible to have two pointers look at the same bit of memory but impose two different data structures on it. For instance

char      *string;
byte            *numbers;

Numbers = string = malloc(100);

Would mean that both string and numbers point to the same bit of memory. If you were to use string[10] then you would be looking at the tenth character in the string. If you were to look at nmbers[10] then you would be looking at the tenth number in the array of numbers. But it’s the same memory, just two different ways of looking at it. If you were to modify string[10], then numbers[10] would also change to reflect what you did to this memory location.

Think about this, since this is a big concept. If you don’t get it, go back, re-read this until you do get it. In this once instance you might want to ask someone that knows C if you still don’t get this.

I don’t honestly know how else to put this that makes it easy yet makes sense. Pointers are complicated and have many implications.

Casting

As if that last item wasn’t bad enough, now we have to get into casting. Assuming you understood the concepts that we were getting at in the last bit, you understand that the pointer to memory defines the way the code looks at it. Now this can get even more complicated when you consider that you can copy a defined pointer from one type to another. The example we used above will work because at its core, a char is actually the same as a byte. They are both 8 bits wide. What happens though when we have one pointer and we need to look at it with two radically different data structures?

Or how about when a function we call returns a pointer in one format, but we need it in a different one?

Well, C provides us with a way to do this. It’s called casting, and this little baby allows us to do all sorts of interesting things and to get ourselves into all sorts of trouble.
You can get the idea of casting from this bit of code

byte            *data_b;
short            *data_s;
float            *data_f;

data_b = malloc(100);

data_s = (short*)data_b;
data_f = (float *)data_b;

ok – what we have here is a byte* pointer that is set up from the malloc call to point at some memory (By the way, malloc always returns a byte* pointer type, never any other type). However, for some reason we also need to be able to look at memory in a different way, instead of just looking at it as straight bytes. So we create some different pointer types, and then cast the original pointer type to a new type so these new pointers can use it. Phew. That was a mouthful. Lets go over that again.

When we look at memory using the data_b pointer, we will get back individual bytes. If we wrote

a = *data_b;

Then we would get back a byte value in a. However, if we wrote

b = *data_f;

Then we would get a float value back from where data_f is pointing at. It’s the same principle as the section above, that of looking at memory in different ways, all we have done here is converted a pointer type from one type to another to let us look at memory in a different way. The actual value of data_b and data_f is exactly the same. It’s just when you ask the system to go away and give you the values it finds at the location that data_b or data_f points at, it returns it in a different format.

So casting is just basically the conversion process whereby we copy a pointer value across from one pointer to another, but changing the data structure type it points at.
The actual conversion is done by the (short*) part in front of the pointer we want to copy. This is saying “ok – get me the pointer address this pointer is looking at, but I want to redefine the data type it’s look at with this new one”. If you don’t do this, more often than not the compiler will puke.

One thing to bear in mind here is that C is actually very free and easy about casting. Sometimes you can even get away with not casting, and the compiler will not complain. The return address from malloc for instance often doesn’t need to be cast properly in C. This is one major difference from C++ where it insists that correct casting is performed everywhere. Bear that in mind.

Procedural Pointer passing.

Now we’ve got passed the hard bit, this is where we can use pointers to our advantage. When dealing with procedures, it’s almost always better to pass data structures across from one procedure to another using a pointer to that structure than the whole structure itself. I explain why in the good habits part of this tutorial. Just trust me now when I say this is a good idea.
There is a difference between passing a pointer and just passing the whole structure itself. When you pass the structure itself, you are actually passing a copy, not the original.
If, for instance, you were to do this.

Void modifyName(char name[100])
{
strcat (name, “ is stupid”);
}

void setUpName(void)
{
char            name[100];

sprintf(name, “Jake”);
modifyName(name);
printf(“%s”, name);
}

When you run the program you will only get the result

Jake

Printed out. This is because when you call modifyName the system is making a copy of the name structure and passing that into the new function. When the function modifyName operates on the name structure, it’s only doing it on it’s own local version of it, which goes away once the function is done. Don’t worry what strcat or sprintf do – we’ll cover that later- besides, you can probably work it out for yourself anyway.
However, if we did this ;-

void modifyName(char *name)
{
strcat (name, “ is stupid”);
}

void setUpName(void)
{
char            name[100];

sprintf(name, “Jake”);
modifyName(name);
printf(“%s”, name);
}

we would see

Jake is stupid

Printed out instead. This is because instead of sending an entire copy of the name structure, we just sent the pointer to memory of where that structure resides. When the strcat operates, it operates on the actual memory that the original function created. So when you return, that structure has been updated. This principle is very important since it actually allows you to effectively have more than one data item return from a function.
You could do this.

bool modifyName(char *name, int *data, int desire)
{
if (desire < 100)
            {
strcat (name, “ is stupid”);
*data = 100;
return true;
}
return false;
}

void setUpName(void)
{
char            name[100];
int             data;

sprintf(name, “Jake”);
if (modifyName(name, &data, 20) )
            {
printf(“%s”, name);
}
}

Ok – so the code doesn’t actually do anything constructive, but you get the idea of being able to pass into a function the address of a data variable, and being able to modify it within that function, and have that result available to the calling function. This is a very powerful ability, just be careful that you don’t abuse it since it can get you in deep doo doo if you don’t know what you are doing.

Gotcha’s.

There are a couple of things that I should mention here that C allows you to do with pointers that can lead to trouble. The first is when you use a pointer as an array pointer. Remember the code where we malloced space for 10 copies of the emp_rec_t data structure? And we accessed the emp_rec using an array index? Well there is nothing at all to stop you going off the end of the array and into uncharted memory. For instance

emp_rec_t            *emp_rec;

emp_rec = malloc(sizeof(emp_rec_t) * 10);

emp_rec[10]->emp_num = 1;
emp_rec[11]->emp_num = 2;
emp_rec[12]->emp_num = 3;

will compile just fine, but will crash horribly. When you used the index suffix 10, 11 and 12 you went off the end of the space you had malloced. One of two things will happen here. Either you will be trying to access space that memory management system says you don’t have, and it will crash telling you so, OR, and this is infinitely worse,  you might be running off into memory you have malloced, but for something else.  For instance

emp_rec_t            *emp_rec;
blah_rec_t            *blah_rec;

emp_rec = malloc(sizeof(emp_rec_t) * 10);
blah_rec = malloc(sizeof(blah_rec_t) * 5);

emp_rec[10]->emp_num = 1;
emp_rec[11]->emp_num = 2;
emp_rec[12]->emp_num = 3;

will mean that when you start setting the emp_rec[10]->emp_num to a value, you are actually stomping all over the memory you allocated for the blah_rec array – assuming the two mallocs give you consecutive memory chunks. This is really bad news, since not only will this compile, but it will run too, however you will get memory corruption and have no idea where it came from. Be careful with your coding is all I can say.

The second gotcha is incrementing a pointer. It’s quite possible to increment a pointer. However, the size of the increment is actually proportional to the size of the data structure that the pointer defines. To explain, look at this code.

char                  *string;
emp_rec_t            *emp_rec;

string = malloc(100);
emp_rec = malloc(sizeof(emp_rec_t) * 10);

for (i= 0; i < 100; i++)
{
*string = I;
string++;
}

for (i= 0; i < 10; i++)
{
memset(emp_rec, i, sizeof(emp_rec_t));
emp_rec++;
}

Badly written code I know – there are much better ways of doing this more efficiently, but its written simply to drive the point home.
When we do the increment of the pointer string the memory address that string points at goes up by one. If we were pointing at memory 1000 hex, we are now pointing at 1001. However, when we increment the pointer emp_rec we are adding the total size of the emp_rec_t structure to it, rather than just 1. Or you might consider that we are adding the value of 1 data structure to it. This is something important to grasp. Again, there is nothing to stop you from incrementing the pointer right out of allocated memory. Once you do this and you attempt to use it or free it, bang, crash, game over.
This is important to remember when casting pointers from one type to another, since incrementing the pointer when it’s one type is very different from incrementing it when it’s another – this is a particular gotcha when you are switching between pointers to char*, short* and int* / float*. Chars go up by 1, shorts by 2 and ints / floats by 4.

Stuff to remember.

Remember the point about only freeing memory that you’ve malloced, and only with the memory location that the malloc call passed to you. You shouldn’t be mallocing some memory, moving the pointer a bit so it’s just inside that memory area, then calling Free with that pointer. The memory management system won’t like that at all, and will tell you so with another crash.

Remember to keep the original value of what malloc passes you back somewhere for posterity so you can free memory correctly once the program or routine shuts down.
A common mistake when starting out C coding is to create a pointer in a process, malloc some memory, use the pointer, then just exit the process. This is a no-no. The malloc process doesn’t care about scope of processes. When you exit the process the memory is still used according to the memory manager, and you’ve just lost the only pointer that knew where it was. At this point, memory has been used and you are never going to get it back. Often this doesn’t matter, since all memory used by the heap is automatically freed once the program exits properly. But programming practices like this are not good and will most definitely get you into trouble once you start creating larger programs using big gobs of memory. You’ll run it a few times, then start getting out of memory errors, and not know why. Plus also, if the program crashes rather than exiting properly, any memory allocated by the program STAYS ALLOCATED. Since the memory manager for that program didn’t get to exit properly, that memory is indicated as being used by the operating system and nothing short of a reboot is going to get it back again. The operating system doesn’t know who allocated it, and it doesn’t care. All it knows is that someone used it, so no one else can. Bad news. And don’t think for a moment you won’t hit this sooner or later if you get into professional programming. There are tools out there that will help you track this kind of thing, like profilers, but that’s beyond the scope of what we are doing here.

One last thing. Keep in mind that pointers can point at anything. They can point at variable types, raw memory, data files, screen video ram addresses, procedures, anything. They are infinitely flexible, and therefore infinitely dangerous. Clever pointer usage can make code really small, but without the proper error checking can also make it extremely bug prone. Be careful until you are comfortable with pointers that you don’t try too many things with them.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>