Introduction

qsort, heapsort, mergesort, bsearch, and many more search and sort functions all take a compar argument to determine the sorting order of elements in an array. The compar function takes the form int (*compar)(const void *, const void *). The compar function’s parameters are the address of two of the elements in the array. The parameters will need to be dereferenced before they can be compared. Any type of value can have a compar function written for it and it can be used to sort an array of any type.

Dereferencing

The comparison function will depend on how we construct the array of element. This is the most important part of the comparison function prototype. Since it’s a pointer to an element in the array, how the array holds element determines how we need to dereference everything.

Array of objects

type_t array[NUM_ELEMENTS];

Since the address of the element in the array is the object, we can access it directly.

int obj_cmp(const void *a, const void *b)
{
    const c_obj_t *o1 = (const c_obj_t *)a;
    const c_obj_t *o2 = (const c_obj_t *)b;
    ...
}

Array of pointers to objects

type_t **array;

The array is an array of pointers so the comparison function receives the address of the array index. We need to dereference the array address to get the object.

int obj_cmp(const void *a, const void *b)
{
    const c_obj_t *o1 = *(const c_obj_t **)a;
    const c_obj_t *o2 = *(const c_obj_t **)b;
    ...

Common Compared Types

Strings and integers are two of the most common types that need to be sorted. Since the dereference requirement tends to trip people up, here are sort functions for these two types.

static int string_cmp(const void *a, const void *b)
{
	const char *sa = *(const char **)a;
	const char *sb = *(const char **)b;

	return strcmp(sa, sb);
}
static int int_cmp(const void *a, const void *b)
{
	int ia = *(int *)a;
	int ib = *(int *)b;

	if (ia == ib)
		return 0;
	if (ia > ib)
		return 1;
	return -1;
}

Example

Using the above comparison functions is really easy. We just need to pass the function as the compar argument to the sort or search function.

int main(int argc, char **argv)
{
	size_t i;
	int    aints[]  = { 9, 7, 8, 1, 2, 4, 10, 9 };
	char  *mystrs[] = { "abc", "1", "a", "c", "dog", "cat", "bat", "xyz" };

	qsort(aints, sizeof(aints)/sizeof(*aints), sizeof(*aints), int_cmp);
	for (i=0; i<sizeof(aints)/sizeof(*aints); i++) {
		printf("%dn", aints[i]);
	}

	printf("---n");

	qsort(mystrs, sizeof(mystrs)/sizeof(*mystrs), sizeof(*mystrs), string_cmp);
	for (i=0; i<sizeof(mystrs)/sizeof(*mystrs); i++) {
		printf("%sn", mystrs[i]);
	}
	return 0;
}

In this example aints is an array of objects (of type int) and mystrs is an array of pointers to each object (strings). That said, since the int_cmp receives a pointer to the array index, we still need to dereference to get the integer values.

Complex Types

Now let’s think about complex types (structs). They’re no different than the previous int and string examples. Let’s use this example type and use some more complete code examples to illustrate.

typedef struct {
    char  *key;
    size_t cnt;
} c_obj_t;

Array of objects

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

typedef struct {
    char  *key;
    size_t cnt;
} c_obj_t;

int obj_cmp(const void *a, const void *b)
{
    const c_obj_t *o1 = (const c_obj_t *)a;
    const c_obj_t *o2 = (const c_obj_t *)b;

    printf("Comparing obj1: %p, key='%s', cnt=%zu and obj1: %p, key='%s', cnt=%zu\n", o1, o1->key, o1->cnt, o2, o2->key, o2->cnt);
    return strcmp(o1->key, o2->key);
}

#define NUM_ELEMENTS 10
int main(int argc, char **argv)
{
    c_obj_t array[NUM_ELEMENTS];
    char tempa[128];
    size_t i;

    for (i=0; i<NUM_ELEMENTS; i++) {
        size_t cnt = NUM_ELEMENTS-i-1;
        snprintf(tempa, sizeof(tempa), "Object #%zu", cnt);
        array[i].key = strdup(tempa);
        array[i].cnt = cnt;

        printf("Added obj: key='%s', cnt=%zu\n", array[i].key, array[i].cnt);
    } 

    qsort(array, NUM_ELEMENTS, sizeof(*array), obj_cmp);
    for (i=0; i<NUM_ELEMENTS; i++) {
        printf("Validating obj: key='%s', cnt=%zu\n", array[i].key, array[i].cnt);
    }

    return 0;
}

This is similar to the array of ints where each element in the array is the type. The obj_cmp function does not need to dereference the arguments because the array index is the object.

Array of pointers to objects

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

typedef struct {
    char  *key;
    size_t cnt;
} c_obj_t;

int obj_cmp(const void *a, const void *b)
{
    const c_obj_t *o1 = *(const c_obj_t **)a;
    const c_obj_t *o2 = *(const c_obj_t **)b;

    if (o1 && !o2)
        return 1;
    if (!o1 && o2)
        return -1;
    if (!o1 && !o2)
        return 0;

    printf("Comparing obj1: %p, key='%s', cnt=%zu and obj1: %p, key='%s', cnt=%zu\n", o1, o1->key, o1->cnt, o2, o2->key, o2->cnt);
    return strcmp(o1->key, o2->key);
}

int main(int argc, char **argv)
{
    c_obj_t *obj;
    c_obj_t **array;
    size_t num_elements = 10;
    char tempa[128];
    size_t i;

    array = calloc(num_elements, sizeof(*array));
    for (i=num_elements; i-->0; ) {
        snprintf(tempa, sizeof(tempa), "Object #%zu", i);

        obj      = malloc(sizeof(*obj));
        obj->key = strdup(tempa);
        obj->cnt = i;
        array[i] = obj;

        printf("Added obj: %p, key='%s', cnt=%zu\n", obj, obj->key, obj->cnt);
    } 

    qsort(array, num_elements, sizeof(*array), obj_cmp);
    for (i=0; i<num_elements; i++) {
        obj = array[i];
        printf("Validating obj: %p, key='%s', cnt=%zu\n", obj, obj->key, obj->cnt);
    }

    for (i=0; i<num_elements; i++) {
        free(array[i]);
    }
    free(array);

    return 0;
}

This one is similar to the array of strings where each element is a pointer to an object. The obj_cmp function needs to dereference the arguments because they’re pointers to pointers in this example.

Also, in this obj_cmp function we check the pointers are valid. Unlike the array of objects example, some of the entries in the array could be NULL. We need to have these extra checks to ensure we don’t access an element in a NULL pointer.