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

     Introduction to C

     Keyboard Input

     Graphics Intro

     Slider Puzzle 1

     Functions

     Pointers

     Dynamic Memory

     Slider Puzzle 2

     Structures

     Bit Manipulation

     Advanced Pointers

       Part I

       Part II

     File I/O

     Graduate Review

   Assembly

   Downloads

 Miscellaneous
   Links

   Cool Graphs

   Feedback Form

C Programming Lessons

TIGCC Programming Lessons

Lesson 9: Advanced Pointers

Step 4 - Type Casting

A very powerful feature of C, which we have largely glossed over, is very useful when considering advanced pointers. Type casting is the ability to convert between different type variables. So, we could convert a long int into a short int (with some loss of data, naturally), or we could convert a float to an integer (with a loss of the decimal place of course), and to avoid warnings about signed and unsigned comparisons, we can cast unsigned values to signed values (which will place a different interpretation on the value).

So, how do we cast a variable to another type? Simple, simply put the new type in parentheses before the variable, and it will be cast to the new type. So, if we had a short int sInt = 780, but we needed a long int, we simply cast it. (long int)sInt; The value represented with sInt is now a long integer. This is mainly useful when we are expecting a variable of a certain type, but we don't have that type. We can convert pretty much anything to anything else, but some casts are more useful than others. You will quickly learn that type casts are a C programmer's best friend.

There is a lot of implicit type casting present in C as well. Smaller integral types such as char can be implicitly promoted to an int or a long int. For example, it is perfectly valid to say char x = '0'; int i = x; We didn't need a cast because there is no damage done here. If we were to go the other way however, int i = 257; char x = i; The compiler will give us a warning about loss of precision. This is because a char type cannot hold the value 257. We can ignore this loss with an explicit type cast, as in int i = 257; char x = (char)i; However, x will have the value 1 since we have lost the upper byte of the integer.

Now, the reason this is important for pointers is because of a special kind of pointer. There are pointers of type int, and type char, and type float, etc. However, there is one kind of pointer that does not have a type, and can be used to represent pointers of any type. This is known as a void pointer. Any type of pointer can be cast to a void pointer, and conversely, any void pointer can be cast to a pointer of a type. However, void pointers are useful for when we need a block of data, but don't know how that data will be interpreted. Remember from lesson 6 that malloc() returns a void pointer, though we didn't go much into depth about void pointers at that time. Since malloc() (and calloc() and realloc()) did not know how you would use the memory they allocated for you, they returned a void pointer to the memory block. This was done so we could cast the pointer to the type we actually needed. Another good use for void pointers is when it's not important what type of data we have. If we only need a block of data, and have no need of knowing what kind of data it is, we can simply use a void pointer. This helps us avoid using separate functions to handle separate types of data. The fread() and fwrite() functions are two examples of functions that take blocks of data without knowing what kind of data they are. This is because the only purpose is to read or write that data from/to a file. It does not matter what the data is, because they don't have to interpret it, they just put it somewhere. This will be nice in the next segment we will cover, which depends upon void pointers heavily.

To cast a void pointer to a pointer of a type, simply use the standard cast notation. char *ptr; void *vptr; ptr = (char *)vptr; One note on void pointers though. They implicitly cast to whatever type is provided. So, we could have just done ptr = vptr, and vice versa, if we wanted to assign the character pointer to the void pointer, vptr = ptr.

Step 5 - Pointers to Functions

A seldom used but very interesting topic in pointers is pointers to functions. You may think that pointers are only for variables, but that's not actually so. A pointer is any memory address, so a pointer to a function is a pointer to the address at the start of the function. This is actually how functions are called, by having the program jump to the address where the function starts, then return when the function ends, or processes a return call.

So, how do we create a pointer to a function? Simple, we just need a type, a name, and an argument list. So, assume we have a function short int mod(short int numerator, short int divisor). To create pointer to this function, we simply create it like so:

type (*name)(args); // replace type, name, and args
short int (*funcptr)(short int numerator, short int divisor);

However, this is only the declaration, not the implementation. If you tried to call the function funcptr(), the program would crash. So, we need to store the address of the function into our pointer. To do that, just use the assignment operator:

funcptr = mod;    // sans ()'s, functions are pointers inherently...

Now, we could call funcptr() just like a real function, and it would have the same effect as calling the mod() function.

This may not seem particularly useful, but it can be helpful in specialized ways, like passing a function as an argument to another function. Consider a sorting algorithm. To sort a list of elements, we need some way of comparing the elements relative to each other. However, there are many ways to compare two items. So, how can a general purpose sorting algorithm be defined? Using pointers to functions. We don't need to know how to compare two values, if the user can give us a function that gives us a value for comparing two items. So, if an item is analytically 'higher' than another item, it might return a positive value, and conversely, an item with is analytically 'lower' than another item might return a negative value.

Another use for pointers to functions is in event-driven programming. In event-driven programming, a function gets called when some event happens. Often this is the result of a timer, but this is not the only possible event. We may cover event-driven programming in more detail in a future lesson.

Step 6 - Using the qsort() General Purpose Sorting Algorithm

Start TIGCC and begin a new project. Create a new C file named qsort. Modify the file so that it looks like this:

qsort.c


#include <tigcclib.h>

long int compare(const void *value1, const void *value2) {
    return (*(short int *)value1 - *(short int *)value2);
}

void printList(short int *list) {
    short int loop, loop2;

    // display the separator
    printf("-----------------------\n");

    // print the integer list
    for (loop = 0; loop < 5; loop++) {
        // indent the list
        printf("| ");

        for (loop2 = 0; loop2 < 5; loop2++) {
            // display the integers
            printf("%hd ",*(list + (5 * loop) + loop2));
        }

        // skip to the next line
        printf("|\n");
    }

    // put the final line separator
    printf("-----------------------\n\n");
}

void fillList(short int *list) {
    short int loop;

    for (loop = 0; loop < 25; loop++) {
        *(list + loop) = (rand() % 900) + 100;  // find numbers between 100 and 999
    }
}

void _main(void) {
    short int list[25];

    // seed the random number generator
    randomize();

    // clear the screen
    clrscr();

    // get numbers for the list
    fillList(list);

    // display the title
    printf("Random List of Integers\n");

    // display the list
    printList(list);

    // give time for user to see the list
    printf("Press any key to sort...\n");
    ngetchx();

    // sort the list according to compare
    qsort(list,25,sizeof(short int),(compare_t)compare);

    // screen cleanup
    printf("Sorted List of Integers\n\n");

    // display the sorted list
    printList(list);

    // wait for user to press a key before exiting
    ngetchx();
}

Step 6a - Compile and Run the Program

Save and build the project, then send it to TiEmu. It will look something like this:

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

Step 6b - Program Analysis

Again is it time for the ever-essential program analysis. The program has a lot of display code to make the output look nice, which is where most of the code is. Skipping over that, let's start the analysis. We'll begin with the fillList() function.

void fillList(short int *list) {
	short int loop;
	
	for (loop = 0; loop < 25; loop++) {
		*(list + loop) = (rand() % 900) + 100;	// find numbers between 100 and 999
	}
}

Okay, the fillList() function's job is surely obvious. We need a list of 25 integers to sort, and rather than give a static list, I decided to make the computer generate them, if only to prove to you that the qsort function will work on any set of numbers.

Since we just covered pointer arithmetic, I decided to use it here in our list fill, instead of list[loop], we used *(list + loop), which we know is the same thing.

Now, the generated random number. Well, it works best if the numbers are all the same length (i.e. 2 digits or 3 digits, etc). It makes the output look good, and since 5 sets of 3-digit numbers nearly fills the screen on the TI-89, I decided to use that. Now, to make sure we have three digit numbers, we need to get numbers from 100-999. So, rand() generates a really really big number, and by performing modulus with 900, we get a number between 0 and 899, so just add 100 to that and we have the numbers from 100-999. So, now we have a nice list of 3-digit numbers.

Continuing in the analysis, the printList() function is next.

void printList(short int *list) {
	short int loop, loop2;
	
	// display the separator
	printf("-----------------------\n");
	
	// print the integer list
	for (loop = 0; loop < 5; loop++) {
		// indent the list
		printf("| ");
		
		for (loop2 = 0; loop2 < 5; loop2++) {
			// display the integers
			printf("%hd ",*(list + (5 * loop) + loop2));
		}
		
		// skip to the next line
		printf("|\n");
	}
	
	// put the final line separator
	printf("-----------------------\n\n");
}

This function is fairly straightforward, but I again chose to use pointer arithmetic, since it just seems like a good way to reinforce the concept, though you would probably use array notation here.

Basically, we have two loops here. The outer loop runs through 5 sets of 5 integers, so we can display the list in 5 rows instead of 25, which is too many to fit on the screen. We also use some characters to box in the list, so it looks nice, but let's look at the pointer arithmetic, as it's the most relevant. I think everything else should be familiar to you.

So, to find a value, we use *(list + (position - 1)). In this case, we have two loops governing the position, the first loop is one of our sets of 5. The second loop is which member of the set we are working on. So, if we want position 18, that's set 4, member 3. So, (5 * 3) + 2 = 17, or position 18 (remember, start counting at 0. Set 4 = 3, Member 3 = 2. array[17] = 18th element). This should be getting old hat by now. Just carry out the arithmetic in your head. list + (5 * 0) + 0 is list, the first element. So, the first time through the loops, we get the first element, then it's list + (5 * 0) + 1, or the 2nd element, and so on.

Anyway, this should not be hard for you by now. So, let's take a look at the compare() function.

long int compare(const void *value1, const void *value2) {
	return (*(short int *)value1 - *(short int *)value2);
}

The compare function returns a long integer value that tells the qsort() function how to treat the values. If value 1 is less than value 2, a negative value is returned. If value 1 is greater than value 2, a positive value is returned. If they are equal, 0 is returned. So, let's talk about how the compare function works. First, the qsort() wants a pointer to a function whose arguments are constant void pointers. This is done so any type of value can be used (void pointers can be anything, remember). So, to get a short integer from the pointer, we first need to cast the value to a short int pointer (we can't cast a void pointer to a value directly, because you cannot dereference a void pointer, since the type is unknown). So, after converting the void pointer into a short integer pointer, we can dereference that pointer to get the value. So, this is like performing (x - y), where x and y are short integers. Now, qsort() puts values that return negative towards the front, and positive values towards the back. Very simple, no?

Now, the final segment of the program is to use the qsort() function:

// sort the list according to compare
qsort(list,25,sizeof(short int),(compare_t)compare);

qsort() takes an array, the number of elements in the array, the size of one element, and the comparison function. It is defined like this:

void qsort(void *array, unsigned long int elements, unsigned long int size, 
  	   long int (*compare)(void *value1, void *value2));

The important part is how the pointer to the function is defined. A comparison function that takes two void pointer values (which is up to the compare function to interpret) and returns a long integer value based on the two values.

After we reprint the list, you'll see that the list is in perfect ascending order. This is because values return negative when they are less than the compare value. If we wanted to perform a descending sort, we would simply need to reverse the value 1 and 2 in the subtraction.

Step 7 - Conclusions

Advanced pointers are nothing you haven't already dealt with in the course of these lessons, and probably your independent programming projects as well. People need multi-dimensional arrays, and now that you know pointer arithmetic, you will use it. Trust me. Type casting, though we have used it extensively in the past, will become an even more important part of your C programming toolkit. This lesson may have seemed short or pointless, but it addresses the cornerstone of C programming, and it's not something to be taken lightly. These are the most used concepts in C programming, I guarantee it, with the exception of pointers to functions which aren't used that often. Pointers and type casting are among the most important concepts in C, if not the most important. Be prepared, you are now ready to take the step beyond simple programming skills, and advance to a degree of real programming skill.


Lesson 9: Advanced Pointers
Questions or Comments? Feel free to contact us.


 

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

Get Firefox!    Valid HTML 4.01!    Made with jEdit