Top Banner
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
50
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
  • 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