Examples of Functions Using void*

Learn about multiple functions from the C standard library which rely on void pointers.

Introduction

The C standard library offers us a lot of handy functions with void pointers. Since these functions need to work on any possible data type, they can’t be limited to int or char. They need to accept or return any data type, and they use void* to accomplish this.

We’ll look at a few examples of such functions. We already saw most of them, but we didn’t know they used void pointers.

malloc and calloc

The prototypes are as follows:

void* malloc(size_t size)
void* calloc (size_t num, size_t size);

Since malloc and calloc can allocate a block of any data type, by specifying the size, they must return void*. We then implicitly cast the returned pointer to our desired data type when writing constructs like the following:

int* intPtr = malloc(sizeof(int));

Sometimes it may be a good idea to explicitly cast the return value of malloc or calloc to the desired data type.

int* intPtr = (int*)malloc(sizeof(int));

Note: C++ compilers require explicit type casting. They don’t allow the implicit conversion we saw in the first code example.

memcpy and memset

The prototypes are:

void* memset(void* ptr, int value, size_t num);
void* memcpy(void* destination, const void* source, size_t num);

Let us focus on the arguments.

  • memset initialized each byte from a memory block to a particular value. Therefore, it must accept a void*.
  • memcpy copies a number of bytes from a source buffer to a destination buffer. It doesn’t care what the data types are. It just copies them byte by byte. Therefore, it must accept the arguments as void*.

We may ask how memset performs the initialization since we know we can’t dereference void pointers. It’s a good question, and we’ll soon learn how to do it.

We’ll see how memset and memcpy work internally in a future lesson, where we’ll learn about reinterpreting memory. It’s going to be an exciting lesson!

The qsort function

Let’s look again at qsort. We used it twice before to sort some data, and we said to ignore the void* and the typecasting parts of the code.

This time, we’ll understand how qsort works in depth. Let’s build another program, to sort an array of integers. Nothing fancy, as we want to focus on the internals of qsort.

Recall that the prototype of qsort is:

void qsort(void* base, size_t nitems, size_t size, int (*comparatorFunc)(const void *, const void*))
  • It takes the array as a void* to accept any data type.
  • The next argument is the number of elements to sort.
  • After that, we must specify the size of each element.
  • Finally, it accepts a comparator function. Since we must compare different data types using different rules, we need to specify the rule for our data type.
    • qsort allows us to specify this using a comparator function.
    • Notice that the comparator is a pointer to a function.
    • comparatorFunc is a pointer to a function taking two constant void pointers as arguments and returning int.
    • Since the array elements are unknown to qsort, the comparator takes the elements to compare as void*.
    • If the two elements are a and b, the comparator must return:
      • < 0 if a < b
      • > 0 if a > b
      • = 0 if a = b
      • When sorting integers, a common shortcut is to return a - b, which produces a value that respects the above conditions.

We call qsort in line 30 with the comparator defined in line 6. We want to sort NUM_ELEMS elements, where each element has the size of an int.

The interesting aspect is to see how we wrote the comparator.

  • The comparator passes two pointers to two elements from the array.
  • The pointers are void*, so we must convert them back to int* (lines 8–9). We use a typecast (int*).
  • Next, we return the appropriate value using the shortcut *elem1 - *elem2 (line 11). This trick doesn’t work correctly for floating point elements. In this case, compare the elements explicitly and return the correct value.

Get hands-on with 1400+ tech skills courses.