Techno-Plaza
Site Navigation [ News | Our Software | Calculators | Programming | Assembly | Downloads | Links | Cool Graphs | Feedback ]
 Main
   Site News

   Our Software

   Legal Information

   Credits

 Calculators
   Information

   C Programming

   Assembly

   Downloads

 Miscellaneous
   Links

   Cool Graphs

   Feedback Form

C Programming Lessons

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:

TI-89 AMS 2.05 ptrmath.89z TI-92+ AMS 2.05 ptrmath.9xz

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

 

Copyright © 1998-2007 Techno-Plaza
All Rights Reserved Unless Otherwise Noted

Get Firefox!    Valid HTML 4.01!    Made with jEdit