A Short C Course CO120.3 Lecture 5 Dynamic Data Structures and Function Pointers in C Dr Maria Valera([email protected]) Imperial College London Summer 2015 FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 1 / 47
A Short C CourseCO120.3Lecture 5
Dynamic Data Structures and Function Pointers in C
Dr Maria Valera([email protected])
Imperial College London
Summer 2015
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 1 / 47
Outline
Dynamic Data Structures.
Double Link Lists.
Function Pointers.
A Binary Tree Data Type.
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 2 / 47
Dynamic Data Structures
The C standard library doesnt contain any form of dynamic datastructures (sets, hash tables, etc.)
We can use dynamic memory to create our own data structures thatgrow and shrink as required during execution.
In this set of slides, well look at an implementation of a doubly linkedlist and a binary tree.
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 3 / 47
Why are there no containers in the C standard library?
Data structures in C tend to be extremely specialised to application. Itsnot easy to write a one size fits all implementation.
Things to consider:
How much of the implementation does the container API expose?
Are elements held by value or via pointers?
Is the container responsible for performing its own dynamic memoryallocation?
How are error conditions handled and signalled?
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 4 / 47
Implementation Choices
Lets say we want a singly linked list data structure. Well consider how wemight store the struct listed below:
struct name {
char first[25];
char last[25];
};
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 5 / 47
Element storage by value
How about a list of nodes containing each value?
struct list_elem {struct list_elem *next;struct name value;
};
next = . . .
value =
first = John
last = Smith
struct name
struct list elem
next = . . .
value =
first = Jane
last = Smith
struct name
struct list elem
. . . . . .
We can only store elements of type struct name in this list.
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 6 / 47
Element storage by pointer
How about a list of nodes containing pointers?
struct list_elem {struct list_elem *next;struct name *value;
};
next = . . .
value = . . .
struct list elem
first = John
last = Smith
struct name
next = . . .
value = . . .
struct list elem
first = Jane
last = Smith
struct name
. . . . . .
We can now store elements of arbitrary types. However, memorymanagement is more complex for any values we wish to reference from thelist.FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 7 / 47
Intrusive Data Structures
Put the pointers inside the struct to be stored?
struct list_elem {struct list_elem *next;
};
struct name {char first[25];char last[25];struct list_elem elem;
};
first = John
last = Smith
next = . . .
struct list elem
struct name
first = Jane
last = Smith
next = . . .
struct list elem
struct name
. . . . . .
Our data structure now has to do no dynamic memory allocation.
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 8 / 47
A note on struct dependencies
Well be making extensive use of structs in this lecture. If one struct isnested in another, it is necessary for it to be declared before the structthat contains it.
If a struct only contains a pointer to another struct, it is possible to use aforward declaration since the compiler doesnt need to know the structssize or members.
Cannot be reorderedstruct A {};
struct B {struct A a;
};
Forward declaration of struct Astruct A;
struct B {struct A *a;
};
struct A {};
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 9 / 47
Dynamic Data Structures
We will look at the implementation of a doubly linked list that holdsintegers.
Each value in the list will be held in a different heap allocated listelement and have pointers to the previous and following elements.
We will have two struct types: struct list and structlist elem.
struct list will be the handle type held by the programmer.
struct list elem will be used to hold the elements of the list.
struct list {struct list_elem *first;struct list_elem *last;
};
struct list_elem {int value;struct list_elem *prev;struct list_elem *next;
};
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 10 / 47
Doubly Linked Lists
Using these structures, we could draw the resulting data structure whenholding the integers 9, 4 and 3 as follows:
first = . . .
last = . . .
struct list
value = 9
prev = NULL
next = . . .
struct list elem
value = 4
prev = . . .
next = . . .
struct list elem
value = 3
prev = . . .
next = NULL
struct list elem
FR & MV (Imperial College London) A Short C Course CO120.3 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 casesat the beginning and end of the list.
Any function that removes an element will have to handle specialcases 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.
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 12 / 47
Iterators
You are probably all familiar with Java iterators. Successive calls to thenext() method return successive items from the collection. We will notbe 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 iteratorswill not know about ranges.
FR & MV (Imperial College London) A Short C Course CO120.3 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 theend of the array arr.
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 14 / 47
Our Revised Design
To avoid special cases, we place an additional element at the startand the end of the linked items.
Our iterator should only ever point to elem0, elem1 or footer.
header = . . .
footer = . . .
struct list
value = prev = NULL
next = . . .
struct list elem
header
value = 4
prev = . . .
next = . . .
struct list elem
elem0
value = 7
prev = . . .
next = . . .
struct list elem
elem1
value = prev = . . .
next = NULL
struct list elem
footer
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 15 / 47
Preliminaries
We define our two struct types:
struct list {struct list_elem *header;struct list_elem *footer;
};
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 instruct list to header and footer from first and last to reflecttheir new roles.
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 16 / 47
List memory allocation
We also define the routines that will allocate and free our list elements:
#include
#include
struct list_elem *list_alloc_elem(void) {struct list_elem *elem = malloc(sizeof(struct list_elem));if (elem == NULL) {
perror("list_alloc_elem");
exit(EXIT_FAILURE);
}
return elem;}
void list_free_elem(struct list_elem *elem) {free(elem);
}
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 17 / 47
Creating a new list
We will define a routine that takes a struct list* and initialises it:
void list_init(struct list *l) {l->header = list_alloc_elem();
l->footer = list_alloc_elem();
l->header->prev = NULL;
l->footer->next = NULL;
l->header->next = l->footer;
l->footer->prev = l->header;
}
We can then initialise a new list as follows:
struct list example;list_init(&example);
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 18 / 47
The Empty List
After construction, our list looks like the following:
header = . . .
footer = . . .
struct list
value = prev = NULL
next = . . .
struct list elem
header
value = prev = . . .
next = NULL
struct list elem
footer
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 19 / 47
The begin and end iterators
We will define a typedef for our iterator:
typedef struct list_elem* list_iter;
We can now define the methods that return the iterators to the beginningand 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) A Short C Course CO120.3 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 theend 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;
}
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 21 / 47
Incrementing and Dereferencing
We increment an iterator by taking the next pointer of the currentelement:
list_iter list_iter_next(list_iter iter) {
return iter->next;}
To dereference, we just look at the value member of the element:
int list_iter_value(list_iter iter) {assert(list_is_internal(iter));
return iter->value;}
By checking prev and next arent NULL, we can check that we arentdereferencing header or footer:
int list_is_internal(list_iter iter) {return iter->prev != NULL && iter->next != NULL;
}
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 22 / 47
Adding new elements
With our iterators defined, we can easily define methods that insertelements 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);
}
FR & MV (Imperial College London) A Short C Course CO120.3 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 onall 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;
}
}
Note that we record elem->next since calling free makes it invalid toaccess *elem.
FR & MV (Imperial College London) A Short C Course CO120.3 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) A Short C Course CO120.3 Summer 2015 25 / 47
Sorted Lists
Using our iterator functionality, we can write a method that constructssorted 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 weare inserting, or hit the end of the list.
FR & MV (Imperial College London) A Short C Course CO120.3 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 apointer 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 writtena function declaration except we replaced the function name with(*sum ptr).
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 27 / 47
Function Pointers
Its possible to invoke a function pointer in exactly the same way asnormal function.
#include
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;
}
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 28 / 47
Function Pointers
We can pass them to other functions as well.
#include
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) A Short C Course CO120.3 Summer 2015 29 / 47
A Binary Tree Data Type
As with our linked list data type, we will use two struct types. One is ahandle to our data structure, the other represents internal nodes.
typedef int (*bst_compare_t)(void *val1, void *val2);
struct bst_elem;
struct bst {bst_compare_t compare;struct bst_elem *tree;
};
struct bst_elem {struct bst_elem *left;struct bst_elem *right;void *value;
};
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 30 / 47
A Binary Tree Data Type
As before, well define functions for allocating and deallocating memory.
#include
#include
struct bst_elem *bst_alloc_elem(void) {struct bst_elem *elem = malloc(sizeof(struct bst_elem));if (elem == NULL) {
perror("bst_alloc_elem");
exit(EXIT_FAILURE);
}
return elem;}
void bst_free_elem(struct bst_elem *elem) {free(elem);
}
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 31 / 47
Initialising the Binary Tree
Initialisation is much simpler this time:
void bst_init(struct bst *handle, bst_compare_t compare) {handle->compare = compare;
handle->tree = NULL;
}
Our empty binary tree looks like this:
compare = . . .
tree = NULL
struct bst
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 32 / 47
Inserting Elements
Our insert function will defer to a recursive implementation that returnsthe updated tree structure.
void bst_insert(struct bst *handle, void *value) {handle->tree =
bst_insert_elem(handle->tree, handle->compare, value);
}
Well consider growing a tree of strings, using the following comparisonfunction:
#include
int string_compare(void *val1, void *val2) {return strcmp((const char*) val1, (const char*) val2);
}
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 33 / 47
Inserting Elements
struct bst tree;bst_init(&tree, &string_compare);
compare = . . .
tree = NULL
struct bst
value = . . .
struct bst elem
Bob
char[]
right = NULL
value = . . .
left = NULL
struct bst elem
Alice
char[]
right = NULL
value = . . .
left = NULL
struct bst elem
Eve
char[]
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 34 / 47
Inserting Elements
char s1[] = "Bob";bst_insert(&tree, s1);
compare = . . .
tree = . . .
struct bst
right = NULL
value = . . .
left = NULL
struct bst elem
Bob
char[]
right = NULL
value = . . .
left = NULL
struct bst elem
Alice
char[]
right = NULL
value = . . .
left = NULL
struct bst elem
Eve
char[]
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 34 / 47
Inserting Elements
char s2[] = "Alice";bst_insert(&tree, s2);
compare = . . .
tree = . . .
struct bst
right = NULL
value = . . .
left = . . .
struct bst elem
Bob
char[]
right = NULL
value = . . .
left = NULL
struct bst elem
Alice
char[]
right = NULL
value = . . .
left = NULL
struct bst elem
Eve
char[]
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 34 / 47
Inserting Elements
char s3[] = "Eve";bst_insert(&tree, s3);
compare = . . .
tree = . . .
struct bst
right = . . .
value = . . .
left = . . .
struct bst elem
Bob
char[]
right = NULL
value = . . .
left = NULL
struct bst elem
Alice
char[]
right = NULL
value = . . .
left = NULL
struct bst elem
Eve
char[]
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 34 / 47
Implementation of bst insert elem
struct bst_elem *bst_insert_elem(struct bst_elem *const elem,bst_compare_t compare, void *value) {
if (elem == NULL) {struct bst_elem *new_elem = bst_alloc_elem();new_elem->left = new_elem->right = NULL;
new_elem->value = value;
return new_elem;} else {const int comparison = compare(value, elem->value);if (comparison < 0) {elem->left = bst_insert_elem(elem->left, compare, value);
} else if (comparison > 0) {elem->right = bst_insert_elem(elem->right, compare, value);
}
return elem;}
}
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 35 / 47
Printing the Tree
Now that we have a sorted list of items, we need a way to print them.Lets define a function that applies a given function to each element.
void bst_for_each(struct bst *handle, void (*func)(void*)) {bst_for_each_elem(handle->tree, func);
}
void bst_for_each_elem(struct bst_elem *elem,void (*func)(void*)) {
if (elem == NULL)return;
bst_for_each_elem(elem->left, func);
func(elem->value);
bst_for_each_elem(elem->right, func);
}
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 36 / 47
The Print Function
All our print function needs to do is cast the element value back to astring, then use printf() to display it.
#include
void bst_print_string(void *value) {const char *str = (const char*) value;printf("%s\n", str);
}
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 37 / 47
The Destroy Function
Our destroy function merely recurses the tree, making sure to destroyparent nodes after their children.
void bst_destroy(struct bst *handle) {bst_destroy_elem(handle->tree);
}
void bst_destroy_elem(struct bst_elem *elem) {if (elem == NULL)
return;
bst_destroy_elem(elem->left);
bst_destroy_elem(elem->right);
bst_free_elem(elem);
}
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 38 / 47
Binary Tree Example
int main(void) {char s1[] = "Bob";char s2[] = "Alice";char s3[] = "Eve";
struct bst tree;bst_init(&tree, &string_compare);
bst_insert(&tree, s1);
bst_insert(&tree, s2);
bst_insert(&tree, s3);
bst_for_each(&tree, &bst_print_string);
bst_destroy(&tree);
return 0;}
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 39 / 47
Freeing values in the tree as well
Its likely that the values we place in our binary tree will have beenallocated on the heap. In that case, we might want to call free() onevery value in our tree before destroying it.
Fortunately, free() already has the right function signature for us to beable 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 beencopied to the heap after being read from a text file.
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 40 / 47
What about state?
Instead of printing our strings to a file, suppose we want to write them tosome file we opened? We cant do this with our current interface becauseour callback method has no state.
We will solve this by defining a struct which will contain the state werequire:
struct print_state {FILE *file;
};
Next, we will define an updated print method that will have access to boththe value to print and a struct print state.
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 41 / 47
What about state?
Our updated print function now takes a parameter called context whichis a pointer to a struct print state. It accesses the file descriptor towrite to by dereferencing the context variable.
void bst_print_string_context(void *context, void *value) {struct print_state *state = (struct print_state*) context;const char *str = (const char*) value;fprintf(state->file, "%s\n", str);
}
Since our tree implementation does not know anything about the type ofcontext, like value, it must also be passed as a void* and castedappropriately.
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 42 / 47
What about state?
Lastly, we define two new bst for each functions that accept the stateparameter (context), and a pointer to a function that takes both apointer to a state and a pointer to a value.
void bst_for_each_context(struct bst *handle,void (*func)(void*, void*), void *context) {bst_for_each_elem_context(handle->tree, func, context);
}
void bst_for_each_elem_context(struct bst_elem *elem,void (*func)(void*, void*), void *context) {
if (elem == NULL) return;
bst_for_each_elem_context(elem->left, func, context);
func(context, elem->value);
bst_for_each_elem_context(elem->right, func, context);
}
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 43 / 47
Printing the tree to a file
We can now print a list of strings to a file like this:
int main(void) {struct bst tree;bst_init(&tree, &string_compare);
/* Populate tree with heap-allocated strings */
FILE *out = fopen("output.txt", "w");struct print_state context;context.file = out; /* Set-up the context */
bst_for_each_context(&tree, &bst_print_string, &context);
fclose(out);
bst_for_each(&tree, &free); /* Free strings */
bst_destroy(&tree); /* Free tree memory */
return 0;}
FR & MV (Imperial College London) A Short C Course CO120.3 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 aninterface in C.
Each function pointer in the struct needs to take a state parameter as avoid*. This emulates the member variables of a class in an objectoriented setting.
The struct of function pointers corresponds to the methods supported bythe interface being emulated.
A stream reading interface from the Vorbisfile librarytypedef struct {size_t (*read_func) (void *ptr, size_t size, size_t nmemb, void *datasource);int (*seek_func) (void *datasource, ogg_int64_t offset, int whence);int (*close_func) (void *datasource);long (*tell_func) (void *datasource);
} ov_callbacks;
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 45 / 47
Summary
In this lecture:
Weve seen how to use dynamic memory allocation in C to implementour 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 apolymorphic data structure in C.
Weve seen how structs of function pointers can be used to emulatean interface in C.
The problem of implementing the search and delete operations on thetree are left as an exercise.
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 46 / 47
Libraries you should know
There are two general purpose libraries you should be aware of if you wantto write reasonably portable C that requires access to lower-level operatingsystem functionality or require more functionality than the standard libraryprovides:
APR The Apache Portable Runtime1 provides a consistentinterface to many OS-specific functions (e.g. threads,networking, opening libraries at runtime) and other usefultools (e.g. memory pools to simplify memory managementand hash tables).
GLib GLib2 is similar to APR, but has a stronger focus onuser-level applications. It contains functionality for characterconversion, regular expressions, multiple container types,dynamically sized strings and more.
1http://apr.apache.org/2http://developer.gnome.org/glib/stable/
FR & MV (Imperial College London) A Short C Course CO120.3 Summer 2015 47 / 47