跳转至

Lecture 5: C Generics and Function Pointers

Function Pointers

1) Pointers are used to point to a variable of a particular data type.

Normally a pointer can only point to one type.

2) void * is a type that can point to anything (generic pointer).

You can even have pointers to functions:

C
1
int* fn (void*, void*) = &foo;

fn is a function that accepts two void * pointers and returns an int and is initially pointing to the function foo.

(*fn)(x, y); will then call the function

3) A function pointer is a variable storing the starting address of a function.

Function Pointers allow us to define higher-order functions, such as map, filter, and generic sort.

C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* map a function onto int array */ 
void mutate_map(int arr[], int n, int(*fp)(int)) {
    for (int i = 0; i < n; i++)
        arr[i] = (*fp)(arr[i]);
} 

int multiply2 (int x) { return 2 * x;}
int multiply10(int x) { return 10 * x;}

int main() {
    int arr[] = {3,1,4};
    int n = sizeof(arr)/sizeof(arr[0]);

    /*
    sizeof(arr) = 4 * 3 = 12
    sizeof(arr[0]) sizeof(int) = 4
    => n = 3
    */

    print_array(arr, n); // 1

    mutate_map (arr, n, &multiply2);
    print_array(arr, n); // 2

    mutate_map (arr, n, &multiply10); 
    print_array(arr, n); // 3

     return 0;
} 
Bash
1
2
3
4
$./map_func
3 1 4 
6 2 8
60 20 80

alt text

Generic Functions

Generic code reduces code duplication and means you can make improvements and fix bugs in one place rather than many.

C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
void swap_int(int* ptr1, int* ptr2)
{
    int temp = *ptr1;
    *ptr1 = *ptr2;
    *ptr2 = temp;
}

void swap_float(float* ptr1, float* ptr2)
{
    float temp = *ptr1;
    *ptr1 = *ptr2;
    *ptr2 = temp;
}

Too duplicated!!!

C
1
2
3
4
5
6
7
// What we need:

void swap(void *ptr1, void *ptr2) { 
    // 1. store a copy of data1 in temporary storage 
    // 2. copy data2 to location of data1 
    // 3. copy data in temporary storage to location of data2 
}

Dereferencing void *

C
1
2
3
4
5
6
// Case 1 
void *ptr = ; 
printf("%p\n", *ptr);

// warning: dereferencing ‘void *’ pointer 
// error: invalid use of void expression
C
1
2
3
4
5
// Case 2 
void **doubleptr = ; 
printf("%p\n", *doubleptr);

// Dereference pointer in Line 2

Why Case 1 is wrong while Case 2 is right?

  • In case 1, we wanna dereference a void*, if succeed, we get a type called void, which is of no sense.
  • In case 2, we wanna dereference a void**, if succeed, we get a void*, which is ok as a generic type.
Supplementary

To dereference a pointer, we must know the number of bytes to access from memory at compile time.

Generics employ generic pointers and therefore cannot use the dereference operator!

Generic Memory Copying

To access some number of bytes in memory with a generic-typed void * pointer, we use two generics in the string standard library:

C
1
void *memcpy(void *dest, const void *src, size_t n);

Copy n bytes from memory area src to memory area dest.

C
1
void *memmove(void *dest, const void *src, size_t n);

Also: Copy n bytes from memory area src to memory area dest.

alt text

C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void swap(void *ptr1, void *ptr2, size_t nbytes) { 
    // 1. store a copy of data1 in temporary storage 
    char temp[nbytes]; 
    memcpy(temp, ptr1, nbytes);

    // 2. copy data2 to location of data1 
    memcpy(ptr1, ptr2, nbytes);

    // 3. copy data in temporary storage to location of data2
    memcpy(ptr2, temp, nbytes);
}

alt text

Pointer Arithmetic in Generics

Use the swap function to swap the first and last elements in an array:

C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
void swap(void* ptr1, void* ptr2, size_t nbytes) {
    // ......
}

void swap_ends(void* arr, size_t nelems, size_t nbytes) {
    // TBD
}

int main() {

    int arr[] = {1, 2, 3, 4, 5},
    int n = sizeof(arr)/sizeof(arr[0]);

    swap_ends(arr, n, sizeof(arr[0]); // to be implemented

    ......
}

What we pursue:

alt text

C
1
2
3
4
5
6
void swap_ends(void* arr, size_t nelems, size_t nbytes) {
    swap (arr,
          ???,
          nbytes
    );
}

The problem equals to: How to present a pointer towards the last element in this 1-D array?

C
1
(char*) arr + (nelems - 1) * nbytes

We must write (char*) explicitly!!!

Why

Pointer arithmetic in generics must be bytewise!

  1. Cast void * pointer to char *.
  2. Pointer arithmetic is then effectively byte-wise!