Lab 8: Pointers and Dynamic Memory

1. Introduction and set up

The goal of this lab is for you to practice using pointers and dynamic memory.

In this lab you will finish an implementation of a map. Recall that a map (also called a dictionary or associative array) associates keys with values. The most familiar example is Java's HashMap. See the Map in Java's API as an example of the functionality.

This map will be implemented using a linked list. Resist the temptation to refer to your code as a hash map. The "hash" in "hashmap" refers to a specific way to implement a map (or set). We are not doing hashing in this lab, but we will learn about hashing later in the semester.

Your map will associate strings with strings (that is, both the keys and values will be strings), so this will also give you a little practice working with strings in C (although we will be doing no real processing on strings). The driver program makes a map from countries to capitals.

Copy the given code.

mkdir lab8
cd lab8
cp ~tvandrun/Public/cs245/lab8/* .

2. Inspecting the code

First look at the driver program (driver.c) to get a handle on what your map needs to do. The first part of the file contains a big static array with the raw data we are going to use. You can largely skip over that---direct your attention to the main() function.

(If you look at the list of countries, you'll notice some entries like Northern Ireland, Puerto Rico, Transnistria, and Palestine, which for various reasons (status, independence, recognition, etc) raise the question of how we define country. I simply grabbed this list from Wikipedia's list of national capitals, which you can check out yourself if you're interested in the political status of any of the nations included. No political message is intended on my part by including or excluding any entity.)

Notice in the main() function we have a map pointer m which we initialize to the result of create(), which allocates an empty map. We have a loop to populate the map. Then we exercise the functions containsKey() and put().

Then we iterate through all the associations in the map. Recall that with Java's HashMap this is done by getting the keyset from the hash map, an iterator from the keyset, and using the keys (gotten from the keyset) to obtain the value for each key, like this:

     for (Iterator it = map.keySet().iterator(); it.hasNext(); ) {
            String key = it.next();
            System.out.println(key + " " + map.get(key));
    }

Lacking both a good "set" type and iterators in C, what we do here is define a function numKeys() which allocates and returns an array containing all the keys. Notice that the result is stored in a variable keySet that has type char**, which is essentially "array of strings." (Remember that strings are really just character arrays, and arrays are just pointers. So char** is interpreted as "pointer to (array of) pointer(s) to (array of) char(s)." Make sure you understand that. Ask for help if you don't.

Finally we call destroy() which shall deallocate everything in the map.

Now you need to master map.h. The structs for nodes and the map are already written for you but you must understand them completely. Notice that the nodes do not have a single datum but two data: a key and a value.

Then look at the functions in map.c. create() and numKeys() are written for you as examples. I also have provided a functiongetNode(), which is not listed in map.h. It is for convenience---the analogue of a private helper method in Java.

3. The functions

Now turn to map.c. Most of the functions are written for you completely (you must read through them and understand them), but four require you to finish them.

A. create()

This function (written for you) makes a new empty map. Notice that it returns a pointer to a map. It allocates a map using malloc and sets the head to null, indicating the map is empty.

B. getNode()

This function actually is not declared in the header file. That's because it is used only internally by the other functions in this library, not by the driver program. It is the equivalent to a private helper method in Java. Given a pointer to a map and a string (as a char*), it searches through the list to find a node that has the given key and return that node (not the value), if any; it returns null otherwise.

C. put()

The point of this function is, given a pointer to a map, to add the association given by the parameters. There are two scenarios: Is this new association overwriting an earlier association for the same key, or is this an association for an entirely new value? We use getNode() to get a pointer to an already-existing node, if any. Finish the code: If there does not already exist a node for this key, then (a) make a new one; (b) set its next, key, and value fields; (c) make it the new head. On the other hand, if a node for this key already exists, update its value field.

D. get()

This function is to retrieve the value for a given key, if any such value exists. If you understand what's going on, writing shouldn't be too bad, especially if you use the getNode() helper function. (it should be about 3 or 4 lines long). If no such key exists, this function should return NULL.

At this point, you can test what you've done by compiling and running the driver. The results should be

Contains Seychelles? yes: Victoria 
Contains Crazyland? no

E. rem()

This is a hard one, and it's done for you, but take some time to read through it. The point of the function is to remove the association for a given key (and return the value of that association that has just been removed). There are three cases: (1) the map is empty, in which case do nothing (and return NULL); (2) the one we want to remove is at the head of the list, in which case do the equivalent to head = head.next in Java---but note carefully how the code returns the value in that association and frees the node; (3) the one we want to remove is somewhere else in the list or nonexistent. Follow the code carefully, since some concepts will come up later in destroy().

F. containsKey()

Easy one, and given to you already. Make sure you understand it.

G. numKeyas()

Also simple; it counts the number of keys/associations. Make sure you understand it.

H. keys()

Here is a the harder one, for you to finish. This function is to allocate an array containing (pointers to) all the keys. You need to allocate an appropriate-lengthed array of strings (that is, an array for holding character pointers; why is toReturn declared as a char**?). Then you need to populate it. (Where is this deallocated? Look in the driver.)

Uncomment the appropriate code in driver.c to test.

I. destroy()

Now finish this function for destroying (deallocating) an entire map. This requires looping through the map and freeing every node as you go. Here is a naive (ie, wrong) way to do this:

for (current = m->head; current != NULL; current = current->next) 
    free(current);

What's wrong with this? When we do current = current->next, we are dereferencing a node that has already been freed. Instead, you are given a loop that keeps track of the previous node we have looked at (and therefore starts the loop with current pointing to the second node in the list.

Your task is the add the calls to free(). There should be three of them, make sure they go in the right place. Don't forget that in addition to freeing the nodes, you need to free the map struct itself.

Then uncomment the appropriate code in the driver and test.


Thomas VanDrunen
Last modified: Thu Mar 6 13:59:22 CST 2014