Lecture 5
Lecture 5
Lecture 5
CO120.3
Lecture 5
Dynamic Data Structures and Function Pointers in C
Dr Maria Valera(m.valera-espina@doc.ic.ac.uk)
Imperial College London
Summer 2015
Summer 2015
1 / 47
Outline
Summer 2015
2 / 47
Summer 2015
3 / 47
Summer 2015
4 / 47
Implementation Choices
Lets say we want a singly linked list data structure. Well consider how we
might store the struct listed below:
struct name {
char first[25];
char last[25];
};
Summer 2015
5 / 47
next = . . .
next = . . .
value =
first = John
value =
first = Jane
last = Smith
struct name
last = Smith
struct name
...
Summer 2015
6 / 47
next = . . .
next = . . .
value = . . .
struct list elem
value = . . .
struct list elem
first = John
first = Jane
last = Smith
struct name
last = Smith
struct name
...
Summer 2015
7 / 47
struct list_elem {
struct list_elem *next;
};
...
first = John
first = Jane
last = Smith
last = Smith
next = . . .
next = . . .
struct name
struct name
...
Summer 2015
8 / 47
struct A;
struct B {
struct A *a;
};
struct A {
};
A Short C Course CO120.3
Summer 2015
9 / 47
struct list_elem {
int value;
struct list_elem *prev;
struct list_elem *next;
};
Summer 2015
10 / 47
first = . . .
last = . . .
struct list
value = 9
prev = NULL
value = 4
prev = . . .
value = 3
prev = . . .
next = . . .
struct list elem
next = . . .
struct list elem
next = NULL
struct list elem
Summer 2015
11 / 47
Design Considerations
This design is workable, but has a couple of issues:
Any function that inserts an element will have to handle special cases
at the beginning and end of the list.
Any function that removes an element will have to handle special
cases at the beginning and end of the list.
The other thing wed like to be able to do is support iterators. Ideally,
these should allow us to:
Iterate forwards and backwards over the list.
Insert elements.
Delete elements.
Summer 2015
12 / 47
Iterators
You are probably all familiar with Java iterators. Successive calls to the
next() method return successive items from the collection. We will not
be basing our iterators on this model.
Instead, our iterator functionality will be based on C pointers:
They will have separate functions to advance their location (like ++)
and to obtain their value (like *).
To define a range, a start and end iterator will be required iterators
will not know about ranges.
Summer 2015
13 / 47
Pointers as iterators
Here, we iterate over the elements of an array using iter:
double arr[100];
double *begin = arr;
double *end = arr+100;
for(double* iter = begin; iter != end; ++iter) {
*iter = 0.0;
}
We want to iterate over our lists in a similar manner.
Note that dereferencing end is invalid. end points to an element after the
end of the array arr.
FR & MV (Imperial College London)
Summer 2015
14 / 47
value = 4
prev = . . .
value = 7
prev = . . .
value =
prev = . . .
next = . . .
struct list elem
header
next = . . .
struct list elem
elem0
next = . . .
struct list elem
elem1
next = NULL
struct list elem
footer
Summer 2015
15 / 47
Preliminaries
struct list_elem {
int value;
struct list_elem *prev;
struct list_elem *next;
};
These are the same as before except that we have renamed the pointers in
struct list to header and footer from first and last to reflect
their new roles.
Summer 2015
16 / 47
Summer 2015
17 / 47
Summer 2015
18 / 47
value =
prev = . . .
next = . . .
struct list elem
header
next = NULL
struct list elem
footer
Summer 2015
19 / 47
We can now define the methods that return the iterators to the beginning
and end of our linked list:
list_iter list_begin(struct list *l) {
return l->header->next;
}
list_iter list_end(struct list *l) {
return l->footer;
}
Note that for the empty list, both will return footer (the invalid element).
FR & MV (Imperial College London)
Summer 2015
20 / 47
Inserting an item
We will define a method for inserting a value before the supplied iterator.
Inserting before the invalid element footer will place an element at the
end of the list.
void list_insert(struct list *l, list_iter iter, int value) {
struct list_elem *new_elem = list_alloc_elem();
new_elem->value = value;
new_elem->prev = iter->prev;
new_elem->next = iter;
iter->prev->next = new_elem;
iter->prev = new_elem;
}
Summer 2015
21 / 47
By checking prev and next arent NULL, we can check that we arent
dereferencing header or footer:
int list_is_internal(list_iter iter) {
return iter->prev != NULL && iter->next != NULL;
}
FR & MV (Imperial College London)
Summer 2015
22 / 47
With our iterators defined, we can easily define methods that insert
elements at the start or end of our list:
void list_insert_front(struct list *l, int value) {
list_insert(l, list_begin(l), value);
}
void list_insert_back(struct list *l, int value) {
list_insert(l, list_end(l), value);
}
Summer 2015
23 / 47
Destroying a list
Once weve finished with a list, we should reclaim any memory it is using.
We define a method to destroy a list that simply calls list free elem on
all list elements including header and footer:
void list_destroy(struct list *l) {
struct list_elem *elem = l->header;
while (elem != NULL) {
struct list_elem *next = elem->next;
list_free_elem(elem);
elem = next;
}
}
Summer 2015
24 / 47
An example
int main(void) {
struct list l;
list_init(&l);
list_insert_front(&l, 1);
list_insert_front(&l, 2);
list_insert_back(&l, 1);
list_insert_back(&l, 2);
for(list_iter iter = list_begin(&l);
iter != list_end(&l);
iter = list_iter_next(iter)) {
printf("%i\n", list_iter_value(iter));
}
list_destroy(&l);
return 0;
}
FR & MV (Imperial College London)
Summer 2015
25 / 47
Sorted Lists
Using our iterator functionality, we can write a method that constructs
sorted lists:
void list_insert_ascending(struct list *l, int value) {
list_iter iter = list_begin(l);
/* We *must* check we havent hit the end of the list first */
while(iter != list_end(l) && list_iter_value(iter) < value) {
iter = list_iter_next(iter);
}
list_insert(l, iter, value);
}
We iterate though the list until we reach a value larger than the one we
are inserting, or hit the end of the list.
FR & MV (Imperial College London)
Summer 2015
26 / 47
Function Pointers
At this point, you should be pretty familiar with pointers to values.
However, C also supports pointers to functions. Heres how we can take a
pointer to a sum function:
static int sum(int a, int b) {
return a + b;
}
int main(void) {
int (*sum_ptr)(int, int);
sum_ptr = ∑
return 0;
}
Weve written the declaration of sum ptr the same way wed have written
a function declaration except we replaced the function name with
(*sum ptr).
FR & MV (Imperial College London)
Summer 2015
27 / 47
Function Pointers
Its possible to invoke a function pointer in exactly the same way as
normal function.
#include <stdio.h>
static int sum(int a, int b) {
return a + b;
}
int main(void) {
int (*sum_ptr)(int, int) = ∑
printf("The sum of 39 and 73 is %i.\n", sum_ptr(39, 73));
return 0;
}
Summer 2015
28 / 47
Function Pointers
We can pass them to other functions as well.
#include <stdio.h>
static int sum(int a, int b) { return a + b; }
static int mul(int a, int b) { return a * b; }
static void print_result(int (*func)(int, int), int a, int b) {
printf("func(%i, %i) = %i\n", a, b, func(a, b));
}
int main(void) {
int a = 42;
int b = 37;
print_result(&sum, a, b);
print_result(&mul, a, b);
return 0;
}
FR & MV (Imperial College London)
Summer 2015
29 / 47
Summer 2015
30 / 47
Summer 2015
31 / 47
Summer 2015
32 / 47
Inserting Elements
Our insert function will defer to a recursive implementation that returns
the updated tree structure.
void bst_insert(struct bst *handle, void *value) {
handle->tree =
bst_insert_elem(handle->tree, handle->compare, value);
}
Summer 2015
33 / 47
Inserting Elements
struct bst tree;
bst_init(&tree, &string_compare);
compare = . . .
tree = NULL
struct bst
Summer 2015
34 / 47
Inserting Elements
char s1[] = "Bob";
bst_insert(&tree, s1);
compare = . . .
right = NULL
tree = . . .
struct bst
value = . . .
Bob
left = NULL
struct bst elem
char[]
Summer 2015
34 / 47
Inserting Elements
char s2[] = "Alice";
bst_insert(&tree, s2);
compare = . . .
right = NULL
tree = . . .
struct bst
value = . . .
Bob
left = . . .
struct bst elem
char[]
right = NULL
value = . . .
Alice
left = NULL
struct bst elem
char[]
Summer 2015
34 / 47
Inserting Elements
char s3[] = "Eve";
bst_insert(&tree, s3);
right = NULL
compare = . . .
tree = . . .
struct bst
value = . . .
Eve
left = NULL
struct bst elem
char[]
right = . . .
value = . . .
Bob
left = . . .
struct bst elem
char[]
right = NULL
value = . . .
Alice
left = NULL
struct bst elem
char[]
Summer 2015
34 / 47
Summer 2015
35 / 47
Summer 2015
36 / 47
All our print function needs to do is cast the element value back to a
string, then use printf() to display it.
#include <stdio.h>
void bst_print_string(void *value) {
const char *str = (const char*) value;
printf("%s\n", str);
}
Summer 2015
37 / 47
Summer 2015
38 / 47
Summer 2015
39 / 47
Its likely that the values we place in our binary tree will have been
allocated on the heap. In that case, we might want to call free() on
every value in our tree before destroying it.
Fortunately, free() already has the right function signature for us to be
able to do this:
bst_for_each(&tree, &free); /* Free each heap-allocated string */
bst_destroy(&tree);
/* Deallocate tree itself */
This would be useful, for example, if all the strings we inserted had been
copied to the heap after being read from a text file.
Summer 2015
40 / 47
Next, we will define an updated print method that will have access to both
the value to print and a struct print state.
Summer 2015
41 / 47
Since our tree implementation does not know anything about the type of
context, like value, it must also be passed as a void* and casted
appropriately.
Summer 2015
42 / 47
Summer 2015
43 / 47
Summer 2015
44 / 47
Taking it further
Theres no need to restrict ourselves to passing around just one function.
If we pass around a struct of function pointers, we can emulate an
interface in C.
Each function pointer in the struct needs to take a state parameter as a
void*. This emulates the member variables of a class in an object
oriented setting.
The struct of function pointers corresponds to the methods supported by
the interface being emulated.
(void
(void
(void
(void
Summer 2015
45 / 47
Summary
In this lecture:
Weve seen how to use dynamic memory allocation in C to implement
our own data structures.
Weve seen the syntax for the declaration and use of function pointers.
Weve seen how function pointers can be used to implement a
polymorphic data structure in C.
Weve seen how structs of function pointers can be used to emulate
an interface in C.
The problem of implementing the search and delete operations on the
tree are left as an exercise.
Summer 2015
46 / 47
http://apr.apache.org/
http://developer.gnome.org/glib/stable/
Summer 2015
47 / 47