Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

1Learning Outcomes

In this chapter, we practice our C memory skills with two more concepts involving pointers.

As mentioned in a previous chapter, normally pointers can only point to one type. Declaring int *p; tells us that p should point to an int value, and it also determines p’s operation with operators like pointer arithmetic (what byte stride to adjust by) and dereferencing (how many bytes to read).

In this section, we discuss the void * pointer, a generic pointers that can point to anything. Generics support general-purpose code by reducing code duplication. Generics are used throughout C to sort an array of any type, search an array of any type, free memory containing data of any type, and more.

By defining one function for each of these use cases, we can call that function across variable types. This helps us make improvements in a single place, instead of multiple very similar places. In doing so, we must further understand memory so that we avoid bugs when writing our own generics.

We have two goals for generics:

  1. Generics should generally regardless of argument type.

  2. Generics should work by accessing blocks of memory, regardless of the data type stored in those blocks.

The first of these is self-evident; the second will become clear later.

2Generic functions in standard C libraries

While we said that we would use generics sparingly to avoid program bugs, you have already encountered your first generics! Check out the stdlib.h heap memory management functions:

These functions are generic functions (or generics[1] for short) because they return or use do not assume anything about the type of the memory being allocated or freed. As described in a previous section, we cast the return values of malloc and realloc calls to the appropriate pointer types and use them in local, typed pointer variables.

3Motivation: swap_int, swap_short, swap_string

3.1swap_int

Suppose we write a function swap_int for swapping integers:

1
2
3
4
5
void swap_int(int *ptr1, int *ptr2) {
  int temp = *ptr1;
  *ptr1 = *ptr2;
  *ptr2 = temp;
}

Because C is pass-by-value, in order to swap integers declared in a different function scope, we pass in arguments as pointers. For example, we could call swap_int as follows:

int x = 2;
int y = 5;
swap_int(&x, &y);

The slidedeck below traces through a toy example that assumes that initially, x stores 2 at address 0x100 and y stores 5 at 0x104. After the swap_int call, x and y are still at the same addresses but have swapped values to 5 and 2, respectively.

Click below to show the explanation of the animation.

3.2swap_short

Next we write a function swap_short for swapping shorts (the integer type, not the fashion statement):

1
2
3
4
5
void swap_short(short *ptr1, short *ptr2) {
  short temp = *ptr1;
  *ptr1 = *ptr2;
  *ptr2 = temp;
}

Aside from the type declarations of ptr1, ptr2, and temp, the logic remains similar.

3.3swap_string

The logic is still similar with swap_string, which “swaps strings”.

1
2
3
4
5
void swap_string(char **ptr1, char **ptr2) {
  char *temp = *ptr1;
  *ptr1 = *ptr2;
  *ptr2 = temp;
}

Instead of making copies of char bytes, this function swaps the addresses of two char * variables (i.e., pointers to C strings). We could call swap_str with the below code.

char *s1 = "CS";
char *s2 = "61C";
swap_string(&s1, &s2);

Figure 1 illustrates a toy example of memory at the start of the call to swap_string; Figure 2 illustrates the memory right before the call returns.

"TODO"

Figure 1:swap_string is called.

"TODO"

Figure 2:Right before swap_string call returns.

Click below to show the explanation of Figure 1 and Figure 2.

3.4Can we write a generic swap?

These three functions demonstrate that at a high level, swapping two values of the same data type follow the same logic. We would like to write a function that can perform a generic swap. Let’s read on!

Footnotes
  1. Java also supports generics to (among other things) support the creation of data structures that can hold any reference type, e.g., DataStructure<T>