TIGCC Programming Lessons
The following is part of a series of lessons designed to help teach
people to program in C for the TI-89, 92+, and V200 calculators
using the TIGCC development environment.
If you wish, you can download the program source code, project
files, and binaries
here.
Lesson 5: Pointers and Arrays
Step 1 - An Introduction to Pointers
Pointers are core to the C programming language. They allow us to access
memory directly, which lets us write very efficient and thus very fast
programs. Most modern programming languages have some form of pointers,
and pointers are probably one of the most difficult concepts to master
in C.
So, what is a pointer? Basically, it's an address in memory somewhere.
We don't know where, and we don't need to know where. That's why we have
the pointer. What do we use pointers for? Everything! No, I'm not
kidding. Pointers are one of the most useful concepts in C. The reason
is that pointers allow us to keep a single copy of a variable (i.e. the
memory we are using) and change that memory from anywhere in the
program. This may sound like a bad idea, and in practice, it's a
difficult thing to control, but once mastered, you will realize why C is
the default standard language for building most any real program today.
We have already seen pointers, and used pointers, but we haven't really
talked about what a pointer is and why it is special. This is what this
lesson is for.
Step 2 - Arrays and their Connection to Pointers in C
This lesson does not cover only pointers, but talks about arrays too, so
we should probably discuss the special relationship between pointers and
arrays.
A pointer is a tool used for keeping track of a certain spot in memory.
In C, arrays are simply groups of data stored sequentially in memory.
Because they are stored sequentially, we can keep track of the first
item in an array with a pointer to that item, and use that as an index
point for every other item in the array.
Step 3 - Using Arrays in C
Now that we know what arrays are in relation to pointers, let us talk
about how we will use them in C. C makes array handling very simple by
abstracting all the hard parts we don't want to deal with. Basically,
there are two things we need to handle when dealing with arrays: how to
create and address them, and how to manipulate the array (that is, move
data into or out of the array).
Step 3a - Creating Arrays
Let's look at a simple array definition.
int array[20];
This is one of the ways we create an array. We declare the type of the
array (what kinds of values the array will hold -- this tells C how to
access the array internally so we don't have to deal with that stuff),
and the size of the array (we have to know how big the array will be
before we can use it so the computer can reserve space for it). So, to
recap, an array is declared by using it's type, the name we want to call
the array, and the size of the array specified inside brackets [].
Suppose we wanted a character array instead.
char array[20];
All we had to do was change int to char. Simple, isn't it?
Step 3b - Manipulating Arrays
Well, creating an array is pretty useless if we don't have a way to
manipulate the data inside the array. So let's talk about how to
manipulate the data inside the array. We will start with how to put data
into an array.
array[index] = value;
Again, pretty simple. We just put the name of the array, then the
position in the array we want to move data into, and use the equality
operator. It's fairly intuitive, when you see the syntax.
The index can be a number, or a variable with a number stored in it. So,
both of the following are legal:
int index = 2;
array[0] = 20;
array[index] = 13;
Most of the time, it will be easier to use a variable to keep track of
the index, but there are times when you may want to access the array
directly.
Well, now that we understand the basic syntax for storing data in an
array, what about taking data out of an array? Well, it's pretty much
the same syntax, only we reverse the source and destination. (Remember,
the stuff on the left is always the 'variable')
So, the basic syntax for grabbing data from an array would be:
variable = array[index];
Again, the index can be a number or a variable. The variable must be a
variable, obviously.
Step 3c - Working with Arrays
Well, you know the basic syntax for using arrays, so how are they used
in practice? Mainly in loops. We have a variable, which I always call
'loop', then we use a for loop and use the loop variable as the index.
Then we do something to the array, as needed. I'll put a small
pseudo-example, but I think you can get the idea even without it:
int loop;
for (loop = 0; loop < arraysize; loop++) {
array[loop] = something; // or
somevar = array[loop]; // or
somefunction(array[loop]); // only one of these three things is generally used
}
As you can see, arrays are very easy to use in C, and they're also a
good topic to cover when we're talking about pointers, as it helps me
explain other concepts.
Step 4 - An Introduction to Pointers
Okay, now that we covered arrays, let's talk about pointers in general.
A pointer, in general, is a number representing a memory address. We
call it a pointer because it is pointing to a place in memory we want to
work with.
We have used pointers before, but have not gone into detail about them.
So, let's start by how to declare them.
int *ptr; // declares an integer pointer
void *ptr; // declares a pointer of unknown type (useful, but easy to misuse)
char *ptr; // character pointer, often used for string literals
Basically, a pointer is declared by putting a asterisk * in front of the
variable name. In addition to regular variable types, you can also have
void pointers, which are just pointers of which the type is unknown.
Eventually, you have to treat a void pointer as a certain kind of data,
because there is no standard way to handle it, but that's a little
advanced for this introduction, so I will avoid void pointers until we
have a real use for them. You saw a void pointer in the lesson 2 in the
OSdequeue example.
We can use any type of variable as a pointer. You might wonder why we
would need to know the memory address of a variable. Two important
reasons come up. First, when dealing with arrays and other large blocks
of data, it would be inefficient to pass all that data around rather
than just tell you where it is. Second, if we use a pointer, we can
change the specific variable without needing to copy it's value and then
assign a new value, such as a return value.
Step 4a - Pointer Basics
Well, we know what pointers are, and have a general idea of why they are
used, so now that we have this information, how do we put it into
practice? Easy! Let's try this for starters:
Start TIGCC and begin a new project. Create a new C Source File called
pointers.
#include <tigcclib.h>
void change_vars(int *x, int *y, int *z) {
*x = 256;
*y = 128;
*z = 64;
}
// Main Function
void _main(void)
{
// create three variables
int x = 50, y = 75, z = 150;
// clear the screen
clrscr();
// print the "before" values
printf("Before:\n x: %d\n y: %d\n z: %d\n\n", x, y, z);
// change the variables and print them again
change_vars(&x,&y,&z);
printf(" After:\n x: %d\n y: %d\n z: %d\n", x, y, z);
// wait for user to press a key before exiting
ngetchx();
}
Step 4b - Compile and Run the Program
Save the file and compile the project. Send it to TiEmu and run the
program. It will look like this:
Step 4c - Program Analysis
As we have done in every lesson up to this point, we will analyze our
program. This program is fairly simple, but is also an idyllic example.
Most pointers you use won't be so clear and easy. But let's take it one
step at a time.
// create three variables
int x = 50, y = 75, z = 150;
// clear the screen
clrscr();
You have seen these things before, but don't forget the clrscr() is used
when we need to use printf(), and ClrScr() is used when we don't use
printf(). Since we are about to use printf(), we need to use clrscr() to
clear the screen as opposed to ClrScr(). This distinction becomes
important when you do many printf()'s.
// print the "before" values
printf("Before:\n x: %d\n y: %d\n z: %d\n\n", x, y, z);
Now we are going to print two sets of values. The values of the
variables before we change them using pointers, and the values
after we change them using pointers. We have used printf()
before, but I will reiterate it's use in case you have forgotten. The
string has several escape sequences, the \n's, which are new line
markers. Every time we encounter a new line marker, printf will move to
the next line. The other kind of special character sequence is the
format specifier. The one used here '%d' tells the printf() function to
replace this with an integer value, which we will give it later. The
integer values we use here are the x, y, and z integer variables. So,
this function prints our string with the values of the x, y, and z
variables and some new lines. Printf() is a very powerful function, but
it is also slower because of its versatility.
// change the variables and print them again
change_vars(&x,&y,&z);
Our next program part is a function call. We are going to call the
function change_vars, and we are passing it three parameters, the x, y,
and z variables. But to send them as pointers, (remember we declared
them above as regular variables), we need to use a special operator,
the address of operator &. The & operator will return the
address of a variable so we can use it as a pointer. Remember that
pointers are just memory addresses.
You may have noticed that we didn't use a function prototype. Function
prototypes are only strictly needed if you are going to use functions
before the compiler has seen them. If the compiler has already seen the
function, it can get the signature itself. Since change_vars is defined
before the _main function, and is only used inside _main, there is no
strict need for a prototype. You might want to try moving the function
to after the _main function and see what happens. Then try adding a
prototype at the top and see how it fixes the problem.
Now, we must also specify to the change_vars function that the values we
are sending it are pointers. We can do this by using the method above,
putting the * asterisk in front of the arguments of the change_vars
function. The asterisk is called the dereferencing operator, as you will
understand in a minute. So, let's take a look at the change_vars
function:
void change_vars(int *x, int *y, int *z) {
*x = 256;
*y = 128;
*z = 64;
}
We see the function has a void return type, which means the function
doesn't return anything. This is a good thing, because we are changing
three variables, and we know no way to return 3 values from a function.
As you can see in the function argument list, all the variables have an
asterisk * preceding them. This tells the function that these variables
are pointers, so do not treat them like regular variables.
You will notice one final quirk about what we do once we get into our
function. We have to use the dereferencing operator again. Why? Well, we
just told the compiler to treat the variable like a pointer. So if we
assign it a value now, we won't be changing the value of the memory
pointed at by the pointer, we will be changing the pointer to point to a
new address. That would pretty much ruin our day. Not in this simple
example, but if you tried to use the pointer after changing the address,
the program would almost certainly crash.
So, what does the dereferencing operator do here? Simple. We want to
assign a value to the memory pointed at by the pointer. To do that, we
must 'dereference' (reference being another word for point to,
dereference must mean to find what is being pointed to) the pointer to
get the place in memory to change. So, instead of changing our pointer,
we have changed the value of the memory the pointer pointed at, and C
did all the work for us. Wasn't that simple?
You might like to see just how much arrays and pointers are alike. Try
changing the *x to x[0]. You'll notice it does exactly the same thing.
Because array variables are just pointers to the first item in the
array, a pointer is effectively an array as well. In this case, we have
three arrays of size 1. Pointers are arrays and arrays are pointers.
A little glib, but basically true.
printf(" After:\n x: %d\n y: %d\n z: %d\n", x, y, z);
// wait for user to press a key before exiting
ngetchx();
Well, the prinf() function hasn't changed much. I made the printing line
up with the first printing, so it looks nice, but it's the same basic
thing. And we know that ngetchx() will wait for the user to press a key,
so they can see the output before the program exits.
See, pointers aren't so hard right? Well, they get harder, but that
covers the basics. You are probably asking yourself why you would use
pointers for such a trivial program. The short answer is you wouldn't,
but they became big time savers in larger programs, or in programs that
can't return a value (like a string -- you can't return a string, but
you can return a pointer to a character array). This is where pointers
are most important, well, that and for efficiency. So why not take a
look at a slightly harder program using pointers.
Continue this Lesson in Part II
|