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 9: Advanced Pointers
Step 1 - Reintroduction to Pointers
Up to this point, we have used pointers in a variety of fashions. We
have seen the unique connection between pointers and arrays, and how we
can treat them differently even though they are very similar. We have
also touched upon subjects like multidimensional arrays, but have not
covered the topic in any detail. This lesson focuses on the more complex
use of pointers and their myriad of uses in C. Remember: pointers are
the most important concept in C. That's why they are also the hardest.
The first thing we should talk about is multidimensional arrays. We have
seen how to create arrays using the [] array bracket specifiers. We have
seen how to use arrays using array notation, and how to use them as
pointers, because all arrays are pointers. We have even used arrays
which are multidimensional, but I glazed over the topic because I wasn't
ready to discuss it in detail at that time. Most C programming books and
teachers cover multidimensional arrays at the same time as arrays, but
this usually leads them to talk about arrays and pointers as though they
are separate concepts, which they really aren't. This is why I chose to
forego the discussion of multidimensional arrays until now.
We know that to create an array of size x elements, say of type
character, we can do this:
char string[40]; // create an array of 40 characters
This tells the compiler to reserve 40 characters in memory to use for
this array. We can also create a dynamic array using the memory
management functions malloc(), calloc(), and realloc().
char *string = (char *)malloc(40 * sizeof(char));
We have also seen how to create arrays of string literals, like this:
const char *str[] = {"String 1","String 2","String 3","String 4"};
This last example is a kind of multidimensional array. Remember that
arrays are pointers, so an array of pointers is an array of arrays, and
that is exactly what a multidimensional array is. An array of arrays. We
can also create arrays of arrays with numeric values, like when we
create sprite arrays, like in the slider puzzle game (version 2):
static unsigned short int pieces[16][16] = {
{0xFFFF,0xC003,0x8001,0x8181,0x8781,0x8181,0x8181,0x8181,
0x8181,0x8181,0x8181,0x8181,0x8181,0x8001,0xC003,0xFFFF}, // piece 1
{0xFFFF,0xC003,0x8001,0x83C1,0x8661,0x8661,0x8061,0x80C1,
0x8181,0x8301,0x8601,0x8601,0x87E1,0x8001,0xC003,0xFFFF}, // piece 2
{0xFFFF,0xC003,0x8001,0x83C1,0x8661,0x8061,0x8061,0x81C1,
0x8061,0x8061,0x8061,0x8661,0x83C1,0x8001,0xC003,0xFFFF}, // piece 3
{0xFFFF,0xC003,0x8001,0x80C1,0x81C1,0x83C1,0x83C1,0x86C1,
0x86C1,0x8CC1,0x8FF1,0x80C1,0x80C1,0x8001,0xC003,0xFFFF}, // piece 4
{0xFFFF,0xC003,0x8001,0x87E1,0x8601,0x8601,0x8601,0x87C1,
0x8661,0x8061,0x8061,0x8661,0x83C1,0x8001,0xC003,0xFFFF}, // piece 5
{0xFFFF,0xC003,0x8001,0x83C1,0x8661,0x8601,0x8601,0x87C1,
0x8661,0x8661,0x8661,0x8661,0x83C1,0x8001,0xC003,0xFFFF}, // piece 6
{0xFFFF,0xC003,0x8001,0x87E1,0x8061,0x80C1,0x80C1,0x8181,
0x8181,0x8181,0x8301,0x8301,0x8301,0x8001,0xC003,0xFFFF}, // piece 7
{0xFFFF,0xC003,0x8001,0x83C1,0x8661,0x8661,0x8661,0x83C1,
0x8661,0x8661,0x8661,0x8661,0x83C1,0x8001,0xC003,0xFFFF}, // piece 8
{0xFFFF,0xC003,0x8001,0x83C1,0x8661,0x8661,0x8661,0x8661,
0x83E1,0x8061,0x8061,0x8661,0x83C1,0x8001,0xC003,0xFFFF}, // piece 9
{0xFFFF,0xC003,0x8001,0x8C79,0xBCCD,0x8CCD,0x8CCD,0x8CCD,
0x8CCD,0x8CCD,0x8CCD,0x8CCD,0x8C79,0x8001,0xC003,0xFFFF}, // piece 10
{0xFFFF,0xC003,0x8001,0x8C31,0xBCF1,0x8C31,0x8C31,0x8C31,
0x8C31,0x8C31,0x8C31,0x8C31,0x8C31,0x8001,0xC003,0xFFFF}, // piece 11
{0xFFFF,0xC003,0x8001,0x8C79,0xBCCD,0x8CCD,0x8C0D,0x8C19,
0x8C31,0x8C61,0x8CC1,0x8CC1,0x8CFD,0x8001,0xC003,0xFFFF}, // piece 12
{0xFFFF,0xC003,0x8001,0x8CF1,0xBD99,0x8C19,0x8C19,0x8C71,
0x8C19,0x8C19,0x8C19,0x8D99,0x8CF1,0x8001,0xC003,0xFFFF}, // piece 13
{0xFFFF,0xC003,0x8001,0x8C0D,0xBC1D,0x8C3D,0x8C3D,0x8C6D,
0x8C6D,0x8CCD,0x8CFD,0x8C0D,0x8C0D,0x8001,0xC003,0xFFFF}, // piece 14
{0xFFFF,0xC003,0x8001,0x8CFD,0xBCC1,0x8CC1,0x8CC1,0x8CF9,
0x8CCD,0x8C0D,0x8C0D,0x8CCD,0x8C79,0x8001,0xC003,0xFFFF} // piece 15
};
It is helpful to think of arrays such as these like tables, with rows
and columns. The first [x][] set of numbers are the rows, and the [][y]
set are the columns within the rows. Multidimensional arrays two levels
deep like this are very common, because tables are a very useful and
simple representation of many sets of data. Graphs, Maps, Matrices, and
anything else that have a grid-like structure. I'm sure you can think of
more.
In C, we represent arrays in sets of dimensions. Single dimension arrays
are the most common. Two dimensional arrays are also very common,
because there are many uses for table-like structures. I'm sure some
people will have use for three dimensional arrays, but probably not many
for your calculator. Arrays of more than 3 dimensions are possible, but
it is unlikely you would ever need such an array.
Step 2 - Pointer Arithmetic and Multiple Indirection
Arrays of a single dimension are pointers. This is because we have a
pointer to the first element, and to get all other elements, we simply
look beyond this point. Multi-dimensional arrays are also pointers, we
just have a special notation for handling them. For example, if you had
a single dimensional array, you would have something like int array[25],
and to get the 13th element, you would use array[12] (remember that
numbering starts at 0, not 1). We have talked little about pointer
arithmetic, so we should probably start to cover it now. Pointer
arithmetic is arithmetic used for finding data relative to a pointer.
However, instead of counting in units of 1 (like we normally would), 1
is equivalent to the size of the data. For example, a character takes up
1 byte, so pointer arithmetic on characters is the same as normal
arithmetic. However, short integers take up 2 bytes, so adding 1 to a
short integer pointer will add 2 bytes, not 1. This allows us to
interact with pointers easier, since we don't have to remember how much
to add to the pointer. Using our array above, we could find the 13th
element of the array this way instead: array + 12. This is entirely
equivalent to array[12], except for one thing. We have not dereferenced
our value. This means, instead of having the value of the 13th element
of the array, we have a new pointer starting at the 13th element instead
of the first. To fix this, we can dereference our pointer arithmetic
value, like this: *(array + 12). This statement is equivalent to
array[12]. We touched upon this slightly in the sprintf program back in
lesson 4.
Now, before we get into pointer arithmetic within multi-dimensional
arrays, it's necessary to understand the concept of a pointer to a
pointer. Multi-dimensional arrays are pointers to pointers. So, a two
dimensional array is a pointer to a pointer. A three dimensional array
is a pointer to a pointer to a pointer. You get the idea. Just add a
pointer for each dimension. So, what are pointers to pointers? Well,
they're a way of defining sets of data. Each pointer represents a
dimension in the array. The top-most pointer encompasses the top-most
dimension of an array. For example, if we had a three-dimensional array,
short int arr3d[5][7][20], then this is similar to ***arr3d, and arr3d
is the top-most level or the first dimension of the array. It has 5 sets
of (7 * 20) short integers. The second dimension has 7 sets of 20 short
integers, and the third dimension has 20 short integers. Now, to find
the second and third dimension, using pointers, we must use multiple
indirections, or dereferences. Remember from lesson 5, that to find the
value of a pointer, we must dereference its value. This is known as
indirection, because we are indirectly using the pointer to find a value
based on the pointer. This is in contrast to direct values, which are
normal variables. So, to use multiple indirection, simply add another *.
So, if we were to dereference the arr3d pointer, we would get *arr3d,
which is a pointer to the second dimension. **arr3d is the third
dimension of our three-dimensional array. And ***arr3d is the
dereferenced value of the first item in the third dimension of the first
item in the second dimension of the first item in the third dimension.
Easy, no? Well, it gets easier with time and practice.
Pointer arithmetic is a little different for multi-dimension arrays than
for single-dimensions. It's harder. Okay, I know that's probably no big
revelation, but don't be discouraged, it's not so difficult once you get the
basic concept down. Remember that array + n is the (n + 1) element of an
array. It is similar for multi-dimension arrays, but with a twist. We
must add the values of all the secondary dimensions. So, for a
two-dimensional array, like short int arr2d[5][20], if we add 1 to the
arr2d pointer, we would get &arr2d[1][0], which is a pointer to the
20th element in the array, or the first element of the second set, if
you prefer. This may not have been exactly what you were looking for,
especially if you were looking for the 2nd element in the first set. To
get this, we need to use our knowledge of multi-dimensional arrays as
pointers to pointers. If we were to find a pointer to the first set, we
could then add 1 to that pointer to find the value we actually were
looking for. So, we can do this instead (*arr2d)+1. The (*arr2d) gives
us the pointer to the second dimension. Remember, this is not a value,
because a two-dimensional array is a pointer to a pointer. This is an
important distinction to keep in mind. * indirection does not give a
value necessarily, it gives the value that we are a pointer to. This
value could be a pointer as well. So, arr2d[0][1] is the same as
*((*arr2d)+1), or, the indirection of the pointer to the second
dimension plus 1.
This may seem complicated now, but it will get easier with practice.
Now, what about three-dimensional arrays. Well, these are like
two-dimensional arrays, but we need to understand that there are three
pointers involved here. So, to get arr3d[0][0][1], we would need to use
*((**arr3d)+1). Okay, what was that? Easy, the multiple indirection (**)
we used gave us a pointer to the third dimension, so, adding this
pointer by one gave us the address of the second element, and the final
indirection gave us the value. Let's add some complexity now. What would
be the value arr3d[6][18][233], for a three dimensional array defined as
long int arr3d[10][20][400]? Easy. *(x) = the value, but what does our x
value equal? Well, we need to find the value of each pointer, and add
those values together. So, we need to find the 7th plane, the 19th row,
and the 233rd column. Then we simply add the values together. ((arr3d+6)
+ (*arr3d)+18 + (**arr3d)+233) becomes our x value. This will give us a
pointer in the third dimension. The combination of all these values says
that arr3d[6][18][233] = *((*((*(arr3d + 6)) + 18)) + 233). I think you
can see why array notation is favored.
Step 3 - Pointer Arithmetic using Sprites
Start TIGCC and create a new project. Create a new C Source File named
ptrmath. Modify the file so that it looks like this:
ptrmath.c
#include <tigcclib.h>
enum Dimensions {WIDTH = 32, HEIGHT = 78};
unsigned long int graph[5 * HEIGHT] = {
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00100000,
0x00100000,0x006e0000,0x00690000,0x00438000,0x00878000,0x00e68000,0x01616000,0x01216000,
0x0110f000,0x01109800,0x01087e00,0x01087e00,0x06086f00,0x06061f80,0x07061ef0,0x07021ef0,
0x0701663c,0x0881670f,0x08808687,0x0880c2c3,0x0860e169,0x086091ff,0x1011091e,0x1011091e,
0x101108e1,0x100906e0,0x100901c3,0x100901c7,0x600600f8,0x600600f0,0x100700e8,0x100700ec,
0x10068117,0x08086111,0x0808610e,0x0808610e,0x06081109,0x06080e07,0x03100607,0x01100603,
0x01100781,0x00900672,0x00900436,0x0090080f,0x00600806,0x00600808,0x00200808,0x00100808,
0x00081008,0x0006100c,0x00061008,0x00061010,0x00011010,0x0000e010,0x0000e010,0x00007010,
0x00000f6f,0x000000f0,0x000000f0,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000003,0x00000003,0x0000001c,0x00000030,0x000000d0,
0x00000090,0x00000110,0x00000220,0x00000420,0x00000820,0x00001020,0x00003020,0x00003020,
0x00003020,0x0000d021,0x0000e0ce,0x000160de,0x000120f1,0x000120c1,0x000221c1,0x00062381,
0x00062301,0x000c2d02,0x000cd10f,0x0008d10f,0x0010e132,0x0010c3cc,0x0011c20c,0x0011c60c,
0x0011ce13,0x00631c3c,0xe0632dff,0xe0632dff,0xff6d3fe2,0xffff9fff,0xddffffff,0x99ffffff,
0x87fffcff,0xfcff03fc,0x10ff81c3,0x10ffc1c7,0x1ff83fff,0x6df80c0d,0x73ffffff,0x77ffffff,
0xe7f0103e,0xe9e021fc,0xdf603f60,0x9f607f20,0x13ffe1c0,0x26c0cf00,0xc5813e00,0x89013e00,
0x8901cc00,0x710e1000,0x12f63000,0x86f26000,0x7f0cc000,0x06090000,0x040d0000,0x080e0000,
0x081c0000,0x0c100000,0x08100000,0x10e00000,0x17000000,0x3c000000,0x78000000,0xe0000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x3c000000,0xdb800000,0x93c00000,0x10300000,0x201c0000,0x201a0000,
0x20120000,0x2021c000,0x20202000,0x20201000,0x20201800,0x20201c00,0xc0c02200,0xc0c02300,
0xc0c02100,0xffe02100,0xc0dec0c0,0x809ec060,0x0101c020,0x0100f010,0x01010c2c,0x01010424,
0x02010322,0x3fc201fd,0xc23f1f33,0x823f1f3b,0x0c0cf11c,0x0c0d0e0e,0x3ffe0dcd,0x7ffe0dcd,
0xe033cc3d,0xc02c3e0e,0xfffff3ff,0xfffff3ff,0xff033fff,0xfeffff0f,0xefffffff,0xefffffff,
0x1fffffe3,0xffffffff,0xfffffcff,0xfffffcff,0xfffffffc,0xfe00001f,0xe0000000,0xe0000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0xc0000000,0xc0000000,0xf0000000,0x3c000000,0x3a000000,0xb2000000,
0xcd000001,0x2ec00006,0xde20007d,0xde2000fd,0xff183fdf,0xfffffffc,0xfffffe2d,0xfffffe2d,
0xffffccfe,0xffffffc3,0xc38f9fff,0xc38f9fff,0xffcfc301,0xfdfff303,0x7fecfe42,0x3fec7ec2,
0x0fde31ff,0x01ef303c,0x00f67e2f,0x007afe27,0x001fdd98,0x000f1a58,0x000f91d8,0x0007d9fc,
0x0003ec26,0x0001fc59,0x0000fec8,0x00006e44,0x00003f02,0x00003cc1,0x00003c40,0x00001c20,
0x00001e20,0x00000e10,0x00000e18,0x00000604,0x00000302,0x000003c3,0x000003c3,0x000001c1,
0x000001e2,0x000000e4,0x0000006c,0x00000038,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00001800,0x00002600,0x00002600,0x00004500,
0x0000c580,0x0001d980,0x00039840,0x0007b040,0x0007a040,0x000641c0,0x001e41a0,0x001f4120,
0x003f8220,0x007d8220,0x01c60420,0x018e0420,0x0f1e0418,0x1e199818,0x3c219818,0x3c219018,
0xfce1a018,0x7fe06004,0x9da04004,0x9da0c004,0xe641c018,0x3a422018,0xc1c42020,0xc1c42020,
0x41c42020,0x81982040,0xffe01040,0xffe01840,0x81c01980,0x87c01980,0x7b201a00,0xf2201a00,
0xc2200600,0xc2200400,0xa2100800,0xa2181800,0x64182000,0x3c184000,0x1c188000,0x1c198000,
0x1c060000,0xfc7c0000,0xdff80000,0x1f000000,0x18000000,0x18000000,0x90000000,0x60000000,
0x20000000,0x20000000,0x20000000,0xc0000000,0x40000000,0x80000000,0x80000000,0x80000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000
};
unsigned long int *graph2d[5] = {graph, graph+HEIGHT, graph+(2 * HEIGHT),
graph+(3 * HEIGHT),graph+(4 * HEIGHT)};
// these two functions accomplish the exact same thing
void draw(void) {
short int loop;
for (loop = 0; loop < 5; loop++) {
Sprite32(loop*32,22,HEIGHT,graph+(loop * HEIGHT),LCD_MEM,SPRT_XOR);
}
}
void draw2d(void) {
short int loop;
for (loop = 0; loop < 5; loop++) {
Sprite32(loop*32,22,HEIGHT,*(graph2d+loop),LCD_MEM,SPRT_XOR);
}
}
void drawMenu(void) {
// setup the medium font
FontSetSys(F_6x8);
// clear the screen
ClrScr();
// draw the menu options
DrawStr(0,0,"F1: Draw/Erase 1D Graph",A_NORMAL);
DrawStr(0,8,"F2: Draw/Erase 2D Graph",A_NORMAL);
DrawStr(0,16,"ESC: Exit the Program",A_NORMAL);
}
void _main(void) {
short int done = 0, key;
drawMenu();
while (!done) {
key = ngetchx();
if (key == KEY_ESC) {
done = 1;
} else if (key == KEY_F1) {
draw();
} else if (key == KEY_F2) {
draw2d();
}
}
}
Step 3a - Compile and Run the Program
Save the project and build it. Send it to TiEmu and it will look
something like this:
Step 3b - Program Analysis
Based on what we have learned so far, this should be a very simplistic
program. We use a single sprite definition, but define it two ways. The
first way is a single dimension array, and the second is a two-dimension
array. This lets us use work with pointer arithmetic of single and
multiple indirections.
Since the sprite is the same sprite, it should not come as a surprise to
you that drawing the 2-dimension version is the same as drawing the
single-dimension version. Since they are drawn using XOR, drawing one
will cancel out the other, as well as drawing the same one twice, just
like any XORed sprite.
Before we get down to the main() method, let's take a look at the sprite
definition.
unsigned long int graph[5 * HEIGHT] = {
I won't copy the entire sprite. This is the key line to notice. The
sprite is an unsigned long integer array of one dimension. It has 5 *
HEIGHT (or 5 * 78 = 390) members. The total size of this array then is
1560 bytes, because that's 390 * sizeof(unsigned long int) = 390 * 4 =
1560. Now, to draw this sprite, we need to draw it in 5 pieces, because
it's 160 pixels wide (32 * 5). Since the widest sprite we can draw is
32 pixels, we have to break it up into 5 sets, hence our graph being
defined in multiples of 5.
Now, to find each set of the sprite, we will user pointer arithmetic.
Since we need a pointer to use the spriteXX() functions, pointer
arithmetic is the perfect choice. So, graph + (0 * HEIGHT) is our first
tile, then graph + (1 * HEIGHT), etc. As you will soon see.
I think the drawMenu() and the loop is fairly straightforward, so I will
skip looking over those. It is the drawing functions that we should be
interested in here. So, let's start with draw().
void draw(void) {
short int loop;
for (loop = 0; loop < 5; loop++) {
Sprite32(loop*32,22,HEIGHT,graph+(loop * HEIGHT),LCD_MEM,SPRT_XOR);
}
}
The loop is rather simple, we call the Sprite32() function 5 times. As
we've used the SpriteXX functions before, they should be no problem for
you, but let's recap real quick. The first parameter is the x
coordinate, then y, then the sprite height. The important one is our 4th
parameter, the sprite pointer. This is where our pointer arithmetic
comes in. Final two parameters are the LCD address and the sprite mode,
which is XOR.
So, what about that pointer arithmetic? Is it simple enough for you?
Well, we start with our pointer (remember this is the one-dimensional
array). We add the value loop * HEIGHT to get one of the 5 sets of data
associated with our sprite. Because each sprite is 78 pixels tall, we
have 78 long integers that define the rows. Remember, since we did not
use the dereferencing operator (*), this is still a pointer, not a
value, so the Sprite32() function accepts it as-is.
The two-dimensional version isn't much more complicated, but let's take
a look at the sprite definition first.
unsigned long int *graph2d[5] = {graph, graph+HEIGHT, graph+(2 * HEIGHT),
graph+(3 * HEIGHT),graph+(4 * HEIGHT)};
Instead of redefining the data again, we used an array of pointers,
which is the same thing as a multi-dimensional array. Since we have a
pointer to a set of pointers, it's a two-dimensional array. The first
set is just the graph pointer, which is the same as the graph array,
only this time, we are a pointer to this pointer, so, to get the graph
pointer from the graph2d, we would have to dereference the first
element. The other patterns are similar, we just add a multiple of the
height to each one up from the previous one. This gives us the 5 sets
that is our graphic. So, let's take a look at the draw2d() function.
void draw2d(void) {
short int loop;
for (loop = 0; loop < 5; loop++) {
Sprite32(loop*32,22,HEIGHT,*(graph2d+loop),LCD_MEM,SPRT_XOR);
}
}
As you can see, it's very similar to the other function, with one key
difference, we don't have to add a multiple of the height. We simply add
our loop variable to the pointer, and dereference the pointer to get the
other pointer, which is what we want. You'll note that we could have
accomplished the same thing by doing graph2d[loop], because array
notation does the first dereference for us.
Continue the Lesson in Part II
|