How to play with pointers in C

Donotalo
3,550 views
undefined

Open Source Your Knowledge, Become a Contributor

Technology knowledge has to be shared and made accessible for free. Join the movement.

Create Content
Previous: Dynamic Memory Allocation

Similar to variables, all code written in a program are also stored in memory during program execution. Therefore all executable code have a distinct address to be referred to. C has the flexibility to obtain the starting address of a function through a pointer - known as function pointer.

Consider the following function

int multiply(short a, short b)
{
	return (int)a * (int)b;
}

To store the address of this function in a function pointer, following syntax is used:

int (*pf)(short, short) = multiply;
^     ^   ^               ^
|     |   |               function name
|     |   function parameter types inside parentheses
|     variable name followed by *, everything enclosed inside parentheses
function return type

Once declared, the variable name (pf here) can be used as a function:

#include <stdio.h>
int multiply(short a, short b)
{
return (int)a * (int)b;
}
int main()
{
int (*pf)(short, short) = multiply;
printf("Multiplied through function pointer: %d\n", pf(10, 9));
return 0;
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

What's the point of having a function pointer where we can directly call the function? Sometimes there are multiple similar (same prototype) functions to call and a decision must be made at runtime. Here is a complete example.

Sorting Student Database

Suppose we want to create a database of students. For simplicity, each student will have a unique id, name and passing year.

#define MAX_NAME_SIZE 1000

typedef struct
{
	uint32_t id;
	char name[MAX_NAME_SIZE];
	uint32_t passing_year;
} Student;

We declare a database (simply an array will work for us) of students and initialize everything to 0 (good practice to avoid undefined behaviour).

#define MAX_STD_SIZE 30

Student std_db[MAX_STD_SIZE];
memset(std_db, 0, sizeof(std_db));

We populate this array somehow (in this example, students will be randomly populated). Now we've a collection of students. Looking them one by one for specific information may not be efficient. For example, we may want a list of students sorted by passing year. Or we may want a list of student names sorted by their names for easy lookup.

In this example we're going to sort the student array using qsort function available in stdlib.h library. But simply calling qsort with the array isn't enough because qsort has no idea about how to sort two Students. Instead, qsort takes a function pointer as a parameter to compare two Student objects with one another to determine their position with respect to each other.

This kind of comparison function is also known as comparator. It has the following prototype for qsort:

int (*compar)(const void *, const void *)

This function takes two pointers to objects to compare with each other. Requirements for this comparator is as follows:

  • If the objects are equal, it will return 0.
  • If the first object should be placed before the second object in the sorted array, it will return a negative integer.
  • If the first object should be placed after the second object in the sorted array, it will return a positive integer.

We'll define two comparators in this example to pass to qsort so that the student array can be sorted as we wish.

Sort by passing year

Sort by passing year is easy. The objects pointed to by the void pointers are cast to Student object. Then if the second passing year is subtracted from first passing year the requirement of comparator function is fulfilled.

int sort_by_passing_year(const void* a, const void* b)
{
	const Student *sa = a;
	const Student *sb = b;

	return sa->passing_year - sb->passing_year;
}

Sort by name

The name of the student with less characters can be considered to come first. If the lengths of the names are same, then a character-by-character comparison needs to be performed to determine which name comes first.

int sort_by_name(const void* a, const void* b)
{
	const Student *sa = a;
	const Student *sb = b;

	int lena = strlen(sa->name);
	int lenb = strlen(sb->name);

	if (lena == lenb)
	{
		/* Names having same length */

		for (int i = 0; i < lena; i++)
		{
			if (sa->name[i] != sb->name[i])
			{
				return sa->name[i] - sb->name[i];
			}
		}

		/* Names are same */
		return 0;
	}

	/* Consider the name with less character to be the lesser of two objects */

	return lena - lenb;
}

With the comparator functions defined, the array of student objects can be sorted by name:

qsort(std_db, MAX_STD_SIZE, sizeof(std_db[0]), sort_by_name);

or passing year:

qsort(std_db, MAX_STD_SIZE, sizeof(std_db[0]), sort_by_passing_year);

Full example together:

#include <stdio.h>
#include <stdint.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
/* Constant definitions */
#define MAX_NAME_SIZE 1000
#define MAX_STD_SIZE 30
/* Student record */
typedef struct
{
uint32_t id;
char name[MAX_NAME_SIZE];
uint32_t passing_year;
} Student;
int sort_by_name(const void* a, const void* b)
{
const Student *sa = a;
const Student *sb = b;
int lena = strlen(sa->name);
int lenb = strlen(sb->name);
if (lena == lenb)
{
/* Names having same length */
for (int i = 0; i < lena; i++)
{
if (sa->name[i] != sb->name[i])
{
return sa->name[i] - sb->name[i];
}
}
/* Names are same */
return 0;
}
/* Consider the name with less character to be the lesser of two objects */
return lena - lenb;
}
int sort_by_passing_year(const void* a, const void* b)
{
const Student *sa = a;
const Student *sb = b;
return sa->passing_year - sb->passing_year;
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Open Source Your Knowledge: become a Contributor and help others learn. Create New Content