Top Banner
Binary Trees Page: 1 http://cslibrary.stanford.edu/110/ BinaryTrees.html Binary Trees by Nick Parlante This article introduces the basic concepts of binary trees, and then works through a series of practice problems with solution code in C/C++ and Java. Binary trees have an elegant recursive pointer structure, so they are a good way to learn recursive pointer algorithms. Contents Section 1 . Binary Tree Structure -- a quick introduction to binary trees and the code that operates on them Section 2 . Binary Tree Problems -- practice problems in increasing order of difficulty Section 3 . C Solutions -- solution code to the problems for C and C++ programmers Section 4 . Java versions -- how binary trees work in Java, with solution code Stanford CS Education Library -- #110 This is article #110 in the Stanford CS Education Library. This and other free CS materials are available at the library ( http://cslibrary.stanford.edu/ ). That people seeking education should have the opportunity to find it. This article may be used, reproduced, excerpted, or sold so long as this paragraph is clearly reproduced. Copyright 2000-2001, Nick Parlante, [email protected]. Related CSLibrary Articles Linked List Problems ( http://cslibrary.stanford.edu/105/ ) -- a large collection of linked list problems using various pointer techniques (while this binary tree article concentrates on recursion) Pointer and Memory (http://cslibrary.stanford.edu/102/ ) -- basic concepts of pointers and memory The Great Tree-List Problem (http://cslibrary.stanford.edu/109/ ) -- a great pointer recursion problem that uses both trees and lists Section 1 -- Introduction To Binary Trees A binary tree is made of nodes, where each node contains a "left" pointer, a "right" pointer, and a data element. The "root" pointer points to the topmost node in the tree. The left and right pointers recursively point to smaller "subtrees" on either side. A null pointer represents a binary tree with no elements -- the empty tree. The formal recursive definition is: a binary tree is either empty (represented by a null pointer), or is made of a single node, where the left and right pointers (recursive definition ahead) each point to a binary tree.
191
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
Page 1: Data Structures

Binary Trees Page: 1

http://cslibrary.stanford.edu/110/BinaryTrees.html

Binary Treesby Nick Parlante

This article introduces the basic concepts of binary trees, and then works through a series of practice problems withsolution code in C/C++ and Java. Binary trees have an elegant recursive pointer structure, so they are a good way tolearn recursive pointer algorithms.

Contents

Section 1. Binary Tree Structure -- a quick introduction to binary trees and the code that operates on them Section 2. Binary Tree Problems -- practice problems in increasing order of difficulty Section 3. C Solutions -- solution code to the problems for C and C++ programmers Section 4. Java versions -- how binary trees work in Java, with solution code

Stanford CS Education Library -- #110

This is article #110 in the Stanford CS Education Library. This and other free CS materials are available at thelibrary (http://cslibrary.stanford.edu/). That people seeking education should have the opportunity to find it.This article may be used, reproduced, excerpted, or sold so long as this paragraph is clearly reproduced. Copyright2000-2001, Nick Parlante, [email protected].

Related CSLibrary Articles

Linked List Problems (http://cslibrary.stanford.edu/105/) -- a large collection of linked list problemsusing various pointer techniques (while this binary tree article concentrates on recursion) Pointer and Memory (http://cslibrary.stanford.edu/102/) -- basic concepts of pointers and memory The Great Tree-List Problem (http://cslibrary.stanford.edu/109/) -- a great pointer recursion problemthat uses both trees and lists

Section 1 -- Introduction To Binary Trees

A binary tree is made of nodes, where each node contains a "left" pointer, a "right" pointer, and a data element.The "root" pointer points to the topmost node in the tree. The left and right pointers recursively point to smaller"subtrees" on either side. A null pointer represents a binary tree with no elements -- the empty tree. The formalrecursive definition is: a binary tree is either empty (represented by a null pointer), or is made of a single node,where the left and right pointers (recursive definition ahead) each point to a binary tree.

Page 2: Data Structures

Binary Trees Page: 2

http://cslibrary.stanford.edu/110/BinaryTrees.html

A "binary search tree" (BST) or "ordered binary tree" is a type of binary tree where the nodes are arranged in order:for each node, all elements in its left subtree are less-or-equal to the node (<=), and all the elements in its rightsubtree are greater than the node (>). The tree shown above is a binary search tree -- the "root" node is a 5, and itsleft subtree nodes (1, 3, 4) are <= 5, and its right subtree nodes (6, 9) are > 5. Recursively, each of the subtrees mustalso obey the binary search tree constraint: in the (1, 3, 4) subtree, the 3 is the root, the 1 <= 3 and 4 > 3. Watch outfor the exact wording in the problems -- a "binary search tree" is different from a "binary tree".

The nodes at the bottom edge of the tree have empty subtrees and are called "leaf" nodes (1, 4, 6) while the othersare "internal" nodes (3, 5, 9).

Binary Search Tree Niche

Basically, binary search trees are fast at insert and lookup. The next section presents the code for these twoalgorithms. On average, a binary search tree algorithm can locate a node in an N node tree in order lg(N) time (logbase 2). Therefore, binary search trees are good for "dictionary" problems where the code inserts and looks upinformation indexed by some key. The lg(N) behavior is the average case -- it's possible for a particular tree to bemuch slower depending on its shape.

Strategy

Some of the problems in this article use plain binary trees, and some use binary search trees. In any case, theproblems concentrate on the combination of pointers and recursion. (See the articles linked above for pointer articlesthat do not emphasize recursion.)

For each problem, there are two things to understand...

The node/pointer structure that makes up the tree and the code that manipulates it The algorithm, typically recursive, that iterates over the tree

When thinking about a binary tree problem, it's often a good idea to draw a few little trees to think about thevarious cases.

Page 3: Data Structures

Binary Trees Page: 3

http://cslibrary.stanford.edu/110/BinaryTrees.html

Typical Binary Tree Code in C/C++

As an introduction, we'll look at the code for the two most basic binary search tree operations -- lookup() andinsert(). The code here works for C or C++. Java programers can read the discussion here, and then look at the Javaversions in Section 4.

In C or C++, the binary tree is built with a node type like this...

struct node { int data; struct node* left; struct node* right; }

Lookup()

Given a binary search tree and a "target" value, search the tree to see if it contains the target. The basic pattern ofthe lookup() code occurs in many recursive tree algorithms: deal with the base case where the tree is empty, dealwith the current node, and then use recursion to deal with the subtrees. If the tree is a binary search tree, there isoften some sort of less-than test on the node to decide if the recursion should go left or right.

/* Given a binary tree, return true if a node with the target data is found in the tree. Recurs down the tree, chooses the left or right branch by comparing the target to each node. */ static int lookup(struct node* node, int target) { // 1. Base case == empty tree // in that case, the target is not found so return false if (node == NULL) { return(false); } else { // 2. see if found here if (target == node->data) return(true); else { // 3. otherwise recur down the correct subtree if (target < node->data) return(lookup(node->left, target)); else return(lookup(node->right, target)); } } }

The lookup() algorithm could be written as a while-loop that iterates down the tree. Our version uses recursion tohelp prepare you for the problems below that require recursion.

Pointer Changing Code

There is a common problem with pointer intensive code: what if a function needs to change one of the pointerparameters passed to it? For example, the insert() function below may want to change the root pointer. In C andC++, one solution uses pointers-to-pointers (aka "reference parameters"). That's a fine technique, but here we willuse the simpler technique that a function that wishes to change a pointer passed to it will return the new value ofthe pointer to the caller. The caller is responsible for using the new value. Suppose we have a change() function

Page 4: Data Structures

Binary Trees Page: 4

http://cslibrary.stanford.edu/110/BinaryTrees.html

that may change the the root, then a call to change() will look like this...

// suppose the variable "root" points to the tree root = change(root);

We take the value returned by change(), and use it as the new value for root. This construct is a little awkward, butit avoids using reference parameters which confuse some C and C++ programmers, and Java does not have referenceparameters at all. This allows us to focus on the recursion instead of the pointer mechanics. (For lots of problemsthat use reference parameters, see CSLibrary #105, Linked List Problems, http://cslibrary.stanford.edu/105/).

Insert()

Insert() -- given a binary search tree and a number, insert a new node with the given number into the tree in thecorrect place. The insert() code is similar to lookup(), but with the complication that it modifies the tree structure.As described above, insert() returns the new tree pointer to use to its caller. Calling insert() with the number 5 onthis tree...

2 / \ 1 10

returns the tree...

2 / \ 1 10 / 5

The solution shown here introduces a newNode() helper function that builds a single node. The base-case/recursionstructure is similar to the structure in lookup() -- each call checks for the NULL case, looks at the node at hand, andthen recurs down the left or right subtree if needed.

/* Helper function that allocates a new node with the given data and NULL left and right pointers. */ struct node* NewNode(int data) { struct node* node = new(struct node); // "new" is like "malloc" node->data = data; node->left = NULL; node->right = NULL;

return(node); }

/* Give a binary search tree and a number, inserts a new node with the given number in the correct place in the tree. Returns the new root pointer which the caller should then use (the standard trick to avoid using reference parameters). */ struct node* insert(struct node* node, int data) {

Page 5: Data Structures

Binary Trees Page: 5

http://cslibrary.stanford.edu/110/BinaryTrees.html

// 1. If the tree is empty, return a new, single node if (node == NULL) { return(newNode(data)); } else { // 2. Otherwise, recur down the tree if (data <= node->data) node->left = insert(node->left, data); else node->right = insert(node->right, data);

return(node); // return the (unchanged) node pointer } }

The shape of a binary tree depends very much on the order that the nodes are inserted. In particular, if the nodesare inserted in increasing order (1, 2, 3, 4), the tree nodes just grow to the right leading to a linked list shape whereall the left pointers are NULL. A similar thing happens if the nodes are inserted in decreasing order (4, 3, 2, 1). Thelinked list shape defeats the lg(N) performance. We will not address that issue here, instead focusing on pointersand recursion.

Section 2 -- Binary Tree Problems

Here are 14 binary tree problems in increasing order of difficulty. Some of the problems operate on binary searchtrees (aka "ordered binary trees") while others work on plain binary trees with no special ordering. The nextsection, Section 3, shows the solution code in C/C++. Section 4 gives the background and solution code in Java. Thebasic structure and recursion of the solution code is the same in both languages -- the differences are superficial.

Reading about a data structure is a fine introduction, but at some point the only way to learn is to actually try tosolve some problems starting with a blank sheet of paper. To get the most out of these problems, you should at leastattempt to solve them before looking at the solution. Even if your solution is not quite right, you will be building upthe right skills. With any pointer-based code, it's a good idea to make memory drawings of a a few simple cases tosee how the algorithm should work.

1. build123()

This is a very basic problem with a little pointer manipulation. (You can skip this problem if you are alreadycomfortable with pointers.) Write code that builds the following little 1-2-3 binary search tree...

2 / \ 1 3

Write the code in three different ways...

a: by calling newNode() three times, and using three pointer variables b: by calling newNode() three times, and using only one pointer variable c: by calling insert() three times passing it the root pointer to build up the tree

(In Java, write a build123() method that operates on the receiver to change it to be the 1-2-3 tree with the givencoding constraints. See Section 4.)

struct node* build123() {

2. size()

Page 6: Data Structures

Binary Trees Page: 6

http://cslibrary.stanford.edu/110/BinaryTrees.html

This problem demonstrates simple binary tree traversal. Given a binary tree, count the number of nodes in the tree.

int size(struct node* node) {

3. maxDepth()

Given a binary tree, compute its "maxDepth" -- the number of nodes along the longest path from the root node downto the farthest leaf node. The maxDepth of the empty tree is 0, the maxDepth of the tree on the first page is 3.

int maxDepth(struct node* node) {

4. minValue()

Given a non-empty binary search tree (an ordered binary tree), return the minimum data value found in that tree.Note that it is not necessary to search the entire tree. A maxValue() function is structurally very similar to thisfunction. This can be solved with recursion or with a simple while loop.

int minValue(struct node* node) {

5. printTree()

Given a binary search tree (aka an "ordered binary tree"), iterate over the nodes to print them out in increasingorder. So the tree...

4 / \ 2 5 / \ 1 3

Produces the output "1 2 3 4 5". This is known as an "inorder" traversal of the tree.

Hint: For each node, the strategy is: recur left, print the node data, recur right.

void printTree(struct node* node) {

6. printPostorder()

Given a binary tree, print out the nodes of the tree according to a bottom-up "postorder" traversal -- both subtrees ofa node are printed out completely before the node itself is printed, and each left subtree is printed before the rightsubtree. So the tree...

4 / \ 2 5 / \ 1 3

Produces the output "1 3 2 5 4". The description is complex, but the code is simple. This is the sort of bottom-uptraversal that would be used, for example, to evaluate an expression tree where a node is an operation like '+' andits subtrees are, recursively, the two subexpressions for the '+'.

Page 7: Data Structures

Binary Trees Page: 7

http://cslibrary.stanford.edu/110/BinaryTrees.html

void printPostorder(struct node* node) {

7. hasPathSum()

We'll define a "root-to-leaf path" to be a sequence of nodes in a tree starting with the root node and proceedingdownward to a leaf (a node with no children). We'll say that an empty tree contains no root-to-leaf paths. So forexample, the following tree has exactly four root-to-leaf paths:

5 / \ 4 8 / / \ 11 13 4 / \ \ 7 2 1

Root-to-leaf paths: path 1: 5 4 11 7 path 2: 5 4 11 2 path 3: 5 8 13 path 4: 5 8 4 1

For this problem, we will be concerned with the sum of the values of such a path -- for example, the sum of thevalues on the 5-4-11-7 path is 5 + 4 + 11 + 7 = 27.

Given a binary tree and a sum, return true if the tree has a root-to-leaf path such that adding up all the valuesalong the path equals the given sum. Return false if no such path can be found. (Thanks to Owen Astrachan forsuggesting this problem.)

int hasPathSum(struct node* node, int sum) {

8. printPaths()

Given a binary tree, print out all of its root-to-leaf paths as defined above. This problem is a little harder than itlooks, since the "path so far" needs to be communicated between the recursive calls. Hint: In C, C++, and Java,probably the best solution is to create a recursive helper function printPathsRecur(node, int path[], int pathLen),where the path array communicates the sequence of nodes that led up to the current call. Alternately, the problemmay be solved bottom-up, with each node returning its list of paths. This strategy works quite nicely in Lisp, sinceit can exploit the built in list and mapping primitives. (Thanks to Matthias Felleisen for suggesting this problem.)

Given a binary tree, print out all of its root-to-leaf paths, one per line.

void printPaths(struct node* node) {

9. mirror()

Change a tree so that the roles of the left and right pointers are swapped at every node.

So the tree... 4 / \ 2 5 / \

Page 8: Data Structures

Binary Trees Page: 8

http://cslibrary.stanford.edu/110/BinaryTrees.html

1 3

is changed to... 4 / \ 5 2 / \ 3 1

The solution is short, but very recursive. As it happens, this can be accomplished without changing the root nodepointer, so the return-the-new-root construct is not necessary. Alternately, if you do not want to change the treenodes, you may construct and return a new mirror tree based on the original tree.

void mirror(struct node* node) {

10. doubleTree()

For each node in a binary search tree, create a new duplicate node, and insert the duplicate as the left child of theoriginal node. The resulting tree should still be a binary search tree.

So the tree... 2 / \ 1 3

is changed to... 2 / \ 2 3 / / 1 3 / 1

As with the previous problem, this can be accomplished without changing the root node pointer.

void doubleTree(struct node* node) {

11. sameTree()

Given two binary trees, return true if they are structurally identical -- they are made of nodes with the same valuesarranged in the same way. (Thanks to Julie Zelenski for suggesting this problem.)

int sameTree(struct node* a, struct node* b) {

12. countTrees()

This is not a binary tree programming problem in the ordinary sense -- it's more of a math/combinatorics recursionproblem that happens to use binary trees. (Thanks to Jerry Cain for suggesting this problem.)

Suppose you are building an N node binary search tree with the values 1..N. How many structurally different binary search trees are there that store those values? Write a recursive function that, given the number of distinctvalues, computes the number of structurally unique binary search trees that store those values. For example,

Page 9: Data Structures

Binary Trees Page: 9

http://cslibrary.stanford.edu/110/BinaryTrees.html

countTrees(4) should return 14, since there are 14 structurally unique binary search trees that store 1, 2, 3, and 4. Thebase case is easy, and the recursion is short but dense. Your code should not construct any actual trees; it's just acounting problem.

int countTrees(int numKeys) {

Binary Search Tree Checking (for problems 13 and 14)

This background is used by the next two problems: Given a plain binary tree, examine the tree to determine if itmeets the requirement to be a binary search tree. To be a binary search tree, for every node, all of the nodes in itsleft tree must be <= the node, and all of the nodes in its right subtree must be > the node. Consider the following fourexamples...

a. 5 -> TRUE / \ 2 7

b. 5 -> FALSE, because the 6 is not ok to the left of the 5 / \ 6 7

c. 5 -> TRUE / \ 2 7 / 1

d. 5 -> FALSE, the 6 is ok with the 2, but the 6 is not ok with the 5 / \ 2 7 / \ 1 6

For the first two cases, the right answer can be seen just by comparing each node to the two nodes immediatelybelow it. However, the fourth case shows how checking the BST quality may depend on nodes which are severallayers apart -- the 5 and the 6 in that case.

13 isBST() -- version 1

Suppose you have helper functions minValue() and maxValue() that return the min or max int value from anon-empty tree (see problem 3 above). Write an isBST() function that returns true if a tree is a binary search treeand false otherwise. Use the helper functions, and don't forget to check every node in the tree. It's ok if yoursolution is not very efficient. (Thanks to Owen Astrachan for the idea of having this problem, and comparing it toproblem 14)

Returns true if a binary tree is a binary search tree.

int isBST(struct node* node) {

14. isBST() -- version 2

Page 10: Data Structures

Binary Trees Page: 10

http://cslibrary.stanford.edu/110/BinaryTrees.html

Version 1 above runs slowly since it traverses over some parts of the tree many times. A better solution looks at eachnode only once. The trick is to write a utility helper function isBSTRecur(struct node* node, int min, int max) thattraverses down the tree keeping track of the narrowing min and max allowed values as it goes, looking at each nodeonly once. The initial values for min and max should be INT_MIN and INT_MAX -- they narrow from there.

/* Returns true if the given tree is a binary search tree (efficient version). */ int isBST2(struct node* node) { return(isBSTRecur(node, INT_MIN, INT_MAX)); }

/* Returns true if the given tree is a BST and its values are >= min and <= max. */ int isBSTRecur(struct node* node, int min, int max) {

15. Tree-List

The Tree-List problem is one of the greatest recursive pointer problems ever devised, and it happens to use binarytrees as well. CLibarary #109 http://cslibrary.stanford.edu/109/ works through the Tree-List problem in detailand includes solution code in C and Java. The problem requires an understanding of binary trees, linked lists,recursion, and pointers. It's a great problem, but it's complex.

Section 3 -- C/C++ Solutions

Make an attempt to solve each problem before looking at the solution -- it's the best way to learn.

1. Build123() Solution (C/C++)

// call newNode() three times struct node* build123a() { struct node* root = newNode(2); struct node* lChild = newNode(1); struct node* rChild = newNode(3);

root->left = lChild; root->right= rChild;

return(root); }

// call newNode() three times, and use only one local variable struct node* build123b() { struct node* root = newNode(2); root->left = newNode(1); root->right = newNode(3);

return(root); }

Anuj Bansal
Highlight
Anuj Bansal
Highlight
Page 11: Data Structures

Binary Trees Page: 11

http://cslibrary.stanford.edu/110/BinaryTrees.html

/* Build 123 by calling insert() three times. Note that the '2' must be inserted first. */ struct node* build123c() { struct node* root = NULL; root = insert(root, 2); root = insert(root, 1); root = insert(root, 3); return(root); }

2. size() Solution (C/C++)

/* Compute the number of nodes in a tree. */ int size(struct node* node) { if (node==NULL) { return(0); } else { return(size(node->left) + 1 + size(node->right)); } }

3. maxDepth() Solution (C/C++)

/* Compute the "maxDepth" of a tree -- the number of nodes along the longest path from the root node down to the farthest leaf node. */ int maxDepth(struct node* node) { if (node==NULL) { return(0); } else { // compute the depth of each subtree int lDepth = maxDepth(node->left); int rDepth = maxDepth(node->right);

// use the larger one if (lDepth > rDepth) return(lDepth+1); else return(rDepth+1); } }

4. minValue() Solution (C/C++)

/* Given a non-empty binary search tree, return the minimum data value found in that tree.

Anuj Bansal
Highlight
Anuj Bansal
Highlight
Anuj Bansal
Highlight
Page 12: Data Structures

Binary Trees Page: 12

http://cslibrary.stanford.edu/110/BinaryTrees.html

Note that the entire tree does not need to be searched. */ int minValue(struct node* node) { struct node* current = node;

// loop down to find the leftmost leaf while (current->left != NULL) { current = current->left; }

return(current->data); }

5. printTree() Solution (C/C++)

/* Given a binary search tree, print out its data elements in increasing sorted order. */ void printTree(struct node* node) { if (node == NULL) return;

printTree(node->left); printf("%d ", node->data); printTree(node->right); }

6. printPostorder() Solution (C/C++)

/* Given a binary tree, print its nodes according to the "bottom-up" postorder traversal. */ void printPostorder(struct node* node) { if (node == NULL) return;

// first recur on both subtrees printTree(node->left); printTree(node->right);

// then deal with the node printf("%d ", node->data); }

7. hasPathSum() Solution (C/C++)

/* Given a tree and a sum, return true if there is a path from the root down to a leaf, such that adding up all the values along the path equals the given sum.

Anuj Bansal
Highlight
Anuj Bansal
Highlight
Anuj Bansal
Highlight
Page 13: Data Structures

Binary Trees Page: 13

http://cslibrary.stanford.edu/110/BinaryTrees.html

Strategy: subtract the node value from the sum when recurring down, and check to see if the sum is 0 when you run out of tree. */ int hasPathSum(struct node* node, int sum) { // return true if we run out of tree and sum==0 if (node == NULL) { return(sum == 0); } else { // otherwise check both subtrees int subSum = sum - node->data; return(hasPathSum(node->left, subSum) || hasPathSum(node->right, subSum)); } }

8. printPaths() Solution (C/C++)

/* Given a binary tree, print out all of its root-to-leaf paths, one per line. Uses a recursive helper to do the work. */ void printPaths(struct node* node) { int path[1000];

printPathsRecur(node, path, 0); }

/* Recursive helper function -- given a node, and an array containing the path from the root node up to but not including this node, print out all the root-leaf paths. */ void printPathsRecur(struct node* node, int path[], int pathLen) { if (node==NULL) return;

// append this node to the path array path[pathLen] = node->data; pathLen++;

// it's a leaf, so print the path that led to here if (node->left==NULL && node->right==NULL) { printArray(path, pathLen); } else { // otherwise try both subtrees printPathsRecur(node->left, path, pathLen); printPathsRecur(node->right, path, pathLen); } }

// Utility that prints out an array on a line. void printArray(int ints[], int len) { int i; for (i=0; i<len; i++) {

Anuj Bansal
Highlight
Anuj Bansal
Highlight
Anuj Bansal
Highlight
Anuj Bansal
Highlight
Page 14: Data Structures

Binary Trees Page: 14

http://cslibrary.stanford.edu/110/BinaryTrees.html

printf("%d ", ints[i]); } printf("\n"); }

9. mirror() Solution (C/C++)

/* Change a tree so that the roles of the left and right pointers are swapped at every node.

So the tree... 4 / \ 2 5 / \ 1 3

is changed to... 4 / \ 5 2 / \ 3 1 */ void mirror(struct node* node) { if (node==NULL) { return; } else { struct node* temp;

// do the subtrees mirror(node->left); mirror(node->right);

// swap the pointers in this node temp = node->left; node->left = node->right; node->right = temp; } }

10. doubleTree() Solution (C/C++)

/* For each node in a binary search tree, create a new duplicate node, and insert the duplicate as the left child of the original node. The resulting tree should still be a binary search tree.

So the tree... 2 / \

Anuj Bansal
Highlight
Anuj Bansal
Highlight
Page 15: Data Structures

Binary Trees Page: 15

http://cslibrary.stanford.edu/110/BinaryTrees.html

1 3

Is changed to... 2 / \ 2 3 / / 1 3 / 1

*/ void doubleTree(struct node* node) { struct node* oldLeft;

if (node==NULL) return;

// do the subtrees doubleTree(node->left); doubleTree(node->right);

// duplicate this node to its left oldLeft = node->left; node->left = newNode(node->data); node->left->left = oldLeft; }

11. sameTree() Solution (C/C++)

/* Given two trees, return true if they are structurally identical. */ int sameTree(struct node* a, struct node* b) { // 1. both empty -> true if (a==NULL && b==NULL) return(true);

// 2. both non-empty -> compare them else if (a!=NULL && b!=NULL) { return( a->data == b->data && sameTree(a->left, b->left) && sameTree(a->right, b->right) ); } // 3. one empty, one not -> false else return(false); }

12. countTrees() Solution (C/C++)

/* For the key values 1...numKeys, how many structurally unique

Anuj Bansal
Highlight
Anuj Bansal
Highlight
Page 16: Data Structures

Binary Trees Page: 16

http://cslibrary.stanford.edu/110/BinaryTrees.html

binary search trees are possible that store those keys.

Strategy: consider that each value could be the root. Recursively find the size of the left and right subtrees. */ int countTrees(int numKeys) {

if (numKeys <=1) { return(1); } else { // there will be one value at the root, with whatever remains // on the left and right each forming their own subtrees. // Iterate through all the values that could be the root... int sum = 0; int left, right, root;

for (root=1; root<=numKeys; root++) { left = countTrees(root - 1); right = countTrees(numKeys - root);

// number of possible trees with this root == left*right sum += left*right; }

return(sum); } }

13. isBST1() Solution (C/C++)

/* Returns true if a binary tree is a binary search tree. */ int isBST(struct node* node) { if (node==NULL) return(true);

// false if the min of the left is > than us if (node->left!=NULL && minValue(node->left) > node->data) return(false);

// false if the max of the right is <= than us if (node->right!=NULL && maxValue(node->right) <= node->data) return(false);

// false if, recursively, the left or right is not a BST if (!isBST(node->left) || !isBST(node->right)) return(false);

// passing all that, it's a BST return(true); }

Anuj Bansal
Highlight
Anuj Bansal
Highlight
Page 17: Data Structures

Binary Trees Page: 17

http://cslibrary.stanford.edu/110/BinaryTrees.html

14. isBST2() Solution (C/C++)

/* Returns true if the given tree is a binary search tree (efficient version). */ int isBST2(struct node* node) { return(isBSTUtil(node, INT_MIN, INT_MAX)); }

/* Returns true if the given tree is a BST and its values are >= min and <= max. */ int isBSTUtil(struct node* node, int min, int max) { if (node==NULL) return(true);

// false if this node violates the min/max constraint if (node->data<min || node->data>max) return(false);

// otherwise check the subtrees recursively, // tightening the min or max constraint return isBSTUtil(node->left, min, node->data) && isBSTUtil(node->right, node->data+1, max) ); }

15. TreeList Solution (C/C++)

The solution code in C and Java to the great Tree-List recursion problem is in CSLibrary #109 http://cslibrary.stanford.edu/109/

Section 4 -- Java Binary Trees and Solutions

In Java, the key points in the recursion are exactly the same as in C or C++. In fact, I created the Java solutions byjust copying the C solutions, and then making the syntactic changes. The recursion is the same, however the outerstructure is slightly different.

In Java, we will have a BinaryTree object that contains a single root pointer. The root pointer points to an internalNode class that behaves just like the node struct in the C/C++ version. The Node class is private -- it is used onlyfor internal storage inside the BinaryTree and is not exposed to clients. With this OOP structure, almost everyoperation has two methods: a one-line method on the BinaryTree that starts the computation, and a recursivemethod that works on the Node objects. For the lookup() operation, there is a BinaryTree.lookup() method thatthe client uses to start a lookup operation. Internal to the BinaryTree class, there is a private recursivelookup(Node) method that implements the recursion down the Node structure. This second, private recursivemethod is basically the same as the recursive C/C++ functions above -- it takes a Node argument and uses recursionto iterate over the pointer structure.

Java Binary Tree Structure

To get started, here are the basic definitions for the Java BinaryTree class, and the lookup() and insert() methodsas examples...

Page 18: Data Structures

Binary Trees Page: 18

http://cslibrary.stanford.edu/110/BinaryTrees.html

// BinaryTree.java public class BinaryTree { // Root node pointer. Will be null for an empty tree. private Node root;

/* --Node-- The binary tree is built using this nested node class. Each node stores one data element, and has left and right sub-tree pointer which may be null. The node is a "dumb" nested class -- we just use it for storage; it does not have any methods. */ private static class Node { Node left; Node right; int data;

Node(int newData) { left = null; right = null; data = newData; } }

/** Creates an empty binary tree -- a null root pointer. */ public void BinaryTree() { root = null; }

/** Returns true if the given target is in the binary tree. Uses a recursive helper. */ public boolean lookup(int data) { return(lookup(root, data)); }

/** Recursive lookup -- given a node, recur down searching for the given data. */ private boolean lookup(Node node, int data) { if (node==null) { return(false); }

if (data==node.data) { return(true); } else if (data<node.data) {

Page 19: Data Structures

Binary Trees Page: 19

http://cslibrary.stanford.edu/110/BinaryTrees.html

return(lookup(node.left, data)); } else { return(lookup(node.right, data)); } }

/** Inserts the given data into the binary tree. Uses a recursive helper. */ public void insert(int data) { root = insert(root, data); }

/** Recursive insert -- given a node pointer, recur down and insert the given data into the tree. Returns the new node pointer (the standard way to communicate a changed pointer back to the caller). */ private Node insert(Node node, int data) { if (node==null) { node = new Node(data); } else { if (data <= node.data) { node.left = insert(node.left, data); } else { node.right = insert(node.right, data); } }

return(node); // in any case, return the new pointer to the caller }

OOP Style vs. Recursive Style

From the client point of view, the BinaryTree class demonstrates good OOP style -- it encapsulates the binary treestate, and the client sends messages like lookup() and insert() to operate on that state. Internally, the Node classand the recursive methods do not demonstrate OOP style. The recursive methods like insert(Node) and lookup(Node, int) basically look like recursive functions in any language. In particular, they do not operate against a"receiver" in any special way. Instead, the recursive methods operate on the arguments that are passed in which isthe classical way to write recursion. My sense is that the OOP style and the recursive style do not be combinednicely for binary trees, so I have left them separate. Merging the two styles would be especially awkward for the"empty" tree (null) case, since you can't send a message to the null pointer. It's possible to get around that by havinga special object to represent the null tree, but that seems like a distraction to me. I prefer to keep the recursivemethods simple, and use different examples to teach OOP.

Java Solutions

Here are the Java solutions to the 14 binary tree problems. Most of the solutions use two methods:a one-line OOP

Page 20: Data Structures

Binary Trees Page: 20

http://cslibrary.stanford.edu/110/BinaryTrees.html

method that starts the computation, and a recursive method that does the real operation. Make an attempt tosolve each problem before looking at the solution -- it's the best way to learn.

1. Build123() Solution (Java)

/** Build 123 using three pointer variables. */ public void build123a() { root = new Node(2); Node lChild = new Node(1); Node rChild = new Node(3);

root.left = lChild; root.right= rChild; }

/** Build 123 using only one pointer variable. */ public void build123b() { root = new Node(2); root.left = new Node(1); root.right = new Node(3); }

/** Build 123 by calling insert() three times. Note that the '2' must be inserted first. */ public void build123c() { root = null; root = insert(root, 2); root = insert(root, 1); root = insert(root, 3); }

2. size() Solution (Java)

/** Returns the number of nodes in the tree. Uses a recursive helper that recurs down the tree and counts the nodes. */ public int size() { return(size(root)); }

private int size(Node node) { if (node == null) return(0); else { return(size(node.left) + 1 + size(node.right)); } }

Page 21: Data Structures

Binary Trees Page: 21

http://cslibrary.stanford.edu/110/BinaryTrees.html

3. maxDepth() Solution (Java)

/** Returns the max root-to-leaf depth of the tree. Uses a recursive helper that recurs down to find the max depth. */ public int maxDepth() { return(maxDepth(root)); }

private int maxDepth(Node node) { if (node==null) { return(0); } else { int lDepth = maxDepth(node.left); int rDepth = maxDepth(node.right);

// use the larger + 1 return(Math.max(lDepth, rDepth) + 1); } }

4. minValue() Solution (Java)

/** Returns the min value in a non-empty binary search tree. Uses a helper method that iterates to the left to find the min value. */ public int minValue() { return( minValue(root) ); }

/** Finds the min value in a non-empty binary search tree. */ private int minValue(Node node) { Node current = node; while (current.left != null) { current = current.left; }

return(current.data); }

5. printTree() Solution (Java)

/** Prints the node values in the "inorder" order. Uses a recursive helper to do the traversal.

Page 22: Data Structures

Binary Trees Page: 22

http://cslibrary.stanford.edu/110/BinaryTrees.html

*/ public void printTree() { printTree(root); System.out.println(); }

private void printTree(Node node) { if (node == null) return;

// left, node itself, right printTree(node.left); System.out.print(node.data + " "); printTree(node.right); }

6. printPostorder() Solution (Java)

/** Prints the node values in the "postorder" order. Uses a recursive helper to do the traversal. */ public void printPostorder() { printPostorder(root); System.out.println(); }

public void printPostorder(Node node) { if (node == null) return;

// first recur on both subtrees printPostorder(node.left); printPostorder(node.right);

// then deal with the node System.out.print(node.data + " "); }

7. hasPathSum() Solution (Java)

/** Given a tree and a sum, returns true if there is a path from the root down to a leaf, such that adding up all the values along the path equals the given sum.

Strategy: subtract the node value from the sum when recurring down, and check to see if the sum is 0 when you run out of tree. */ public boolean hasPathSum(int sum) { return( hasPathSum(root, sum) ); }

boolean hasPathSum(Node node, int sum) { // return true if we run out of tree and sum==0 if (node == null) {

Page 23: Data Structures

Binary Trees Page: 23

http://cslibrary.stanford.edu/110/BinaryTrees.html

return(sum == 0); } else { // otherwise check both subtrees int subSum = sum - node.data; return(hasPathSum(node.left, subSum) || hasPathSum(node.right, subSum)); } }

8. printPaths() Solution (Java)

/** Given a binary tree, prints out all of its root-to-leaf paths, one per line. Uses a recursive helper to do the work. */ public void printPaths() { int[] path = new int[1000]; printPaths(root, path, 0); }

/** Recursive printPaths helper -- given a node, and an array containing the path from the root node up to but not including this node, prints out all the root-leaf paths. */ private void printPaths(Node node, int[] path, int pathLen) { if (node==null) return;

// append this node to the path array path[pathLen] = node.data; pathLen++;

// it's a leaf, so print the path that led to here if (node.left==null && node.right==null) { printArray(path, pathLen); } else { // otherwise try both subtrees printPaths(node.left, path, pathLen); printPaths(node.right, path, pathLen); } }

/** Utility that prints ints from an array on one line. */ private void printArray(int[] ints, int len) { int i; for (i=0; i<len; i++) { System.out.print(ints[i] + " "); } System.out.println(); }

Page 24: Data Structures

Binary Trees Page: 24

http://cslibrary.stanford.edu/110/BinaryTrees.html

9. mirror() Solution (Java)

/** Changes the tree into its mirror image.

So the tree... 4 / \ 2 5 / \ 1 3

is changed to... 4 / \ 5 2 / \ 3 1

Uses a recursive helper that recurs over the tree, swapping the left/right pointers. */ public void mirror() { mirror(root); }

private void mirror(Node node) { if (node != null) { // do the sub-trees mirror(node.left); mirror(node.right);

// swap the left/right pointers Node temp = node.left; node.left = node.right; node.right = temp; } }

10. doubleTree() Solution (Java)

/** Changes the tree by inserting a duplicate node on each nodes's .left.

So the tree... 2 / \ 1 3

Is changed to... 2

Page 25: Data Structures

Binary Trees Page: 25

http://cslibrary.stanford.edu/110/BinaryTrees.html

/ \ 2 3 / / 1 3 / 1

Uses a recursive helper to recur over the tree and insert the duplicates. */ public void doubleTree() { doubleTree(root); }

private void doubleTree(Node node) { Node oldLeft;

if (node == null) return;

// do the subtrees doubleTree(node.left); doubleTree(node.right);

// duplicate this node to its left oldLeft = node.left; node.left = new Node(node.data); node.left.left = oldLeft; }

11. sameTree() Solution (Java)

/* Compares the receiver to another tree to see if they are structurally identical. */ public boolean sameTree(BinaryTree other) { return( sameTree(root, other.root) ); }

/** Recursive helper -- recurs down two trees in parallel, checking to see if they are identical. */ boolean sameTree(Node a, Node b) { // 1. both empty -> true if (a==null && b==null) return(true);

// 2. both non-empty -> compare them else if (a!=null && b!=null) { return( a.data == b.data && sameTree(a.left, b.left) && sameTree(a.right, b.right) ); }

Page 26: Data Structures

Binary Trees Page: 26

http://cslibrary.stanford.edu/110/BinaryTrees.html

// 3. one empty, one not -> false else return(false); }

12. countTrees() Solution (Java)

/** For the key values 1...numKeys, how many structurally unique binary search trees are possible that store those keys?

Strategy: consider that each value could be the root. Recursively find the size of the left and right subtrees. */ public static int countTrees(int numKeys) { if (numKeys <=1) { return(1); } else { // there will be one value at the root, with whatever remains // on the left and right each forming their own subtrees. // Iterate through all the values that could be the root... int sum = 0; int left, right, root;

for (root=1; root<=numKeys; root++) { left = countTrees(root-1); right = countTrees(numKeys - root);

// number of possible trees with this root == left*right sum += left*right; }

return(sum); } }

13. isBST1() Solution (Java)

/** Tests if a tree meets the conditions to be a binary search tree (BST). */ public boolean isBST() { return(isBST(root)); }

/** Recursive helper -- checks if a tree is a BST using minValue() and maxValue() (not efficient). */ private boolean isBST(Node node) { if (node==null) return(true);

// do the subtrees contain values that do not

Page 27: Data Structures

Binary Trees Page: 27

http://cslibrary.stanford.edu/110/BinaryTrees.html

// agree with the node? if (node.left!=null && maxValue(node.left) > node.data) return(false); if (node.right!=null && minValue(node.right) <= node.data) return(false);

// check that the subtrees themselves are ok return( isBST(node.left) && isBST(node.right) ); }

14. isBST2() Solution (Java)

/** Tests if a tree meets the conditions to be a binary search tree (BST). Uses the efficient recursive helper. */ public boolean isBST2() { return( isBST2(root, Integer.MIN_VALUE, Integer.MAX_VALUE) ); }

/** Efficient BST helper -- Given a node, and min and max values, recurs down the tree to verify that it is a BST, and that all its nodes are within the min..max range. Works in O(n) time -- visits each node only once. */ private boolean isBST2(Node node, int min, int max) { if (node==null) { return(true); } else { // left should be in range min...node.data boolean leftOk = isBST2(node.left, min, node.data);

// if the left is not ok, bail out if (!leftOk) return(false);

// right should be in range node.data+1..max boolean rightOk = isBST2(node.right, node.data+1, max);

return(rightOk); } }

Page 28: Data Structures

Essential CBy Nick Parlante Copyright 1996-2003, Nick Parlante

This Stanford CS Education document tries to summarize all the basic features of the Clanguage. The coverage is pretty quick, so it is most appropriate as review or for someonewith some programming background in another language. Topics include variables, inttypes, floating point types, promotion, truncation, operators, control structures (if, while,for), functions, value parameters, reference parameters, structs, pointers, arrays, the pre-processor, and the standard C library functions.

The most recent version is always maintained at its Stanford CS Education Library URLhttp://cslibrary.stanford.edu/101/. Please send your comments [email protected].

I hope you can share and enjoy this document in the spirit of goodwill in which it is givenaway -- Nick Parlante, 4/2003, Stanford California.

Stanford CS Education Library This is document #101, Essential C, in the StanfordCS Education Library. This and other educational materials are available for free athttp://cslibrary.stanford.edu/. This article is free to be used, reproduced, excerpted,retransmitted, or sold so long as this notice is clearly reproduced at its beginning.

Table of ContentsIntroduction .........................................................................................pg. 2

Where C came from, what is it like, what other resources might you look at.

Section 1 Basic Types and Operators ..........................................pg. 3Integer types, floating point types, assignment operator, comparison operators,arithmetic operators, truncation, promotion.

Section 2 Control Structures ........................................................pg. 11If statement, conditional operator, switch, while, for, do-while, break, continue.

Section 3 Complex Data Types .....................................................pg. 15Structs, arrays, pointers, ampersand operator (&), NULL, C strings, typedef.

Section 4 Functions ........................................................................pg. 24Functions, void, value and reference parameters, const.

Section 5 Odds and Ends ..............................................................pg. 29Main(), the .h/.c file convention, pre-processor, assert.

Section 6 Advanced Arrays and Pointers ....................................pg. 33How arrays and pointers interact. The [ ] and + operators with pointers, baseaddress/offset arithmetic, heap memory management, heap arrays.

Section 7 Operators and Standard Library Reference ..............pg. 41A summary reference of the most common operators and library functions.

The C LanguageC is a professional programmer's language. It was designed to get in one's way as little aspossible. Kernighan and Ritchie wrote the original language definition in their book, TheC Programming Language (below), as part of their research at AT&T. Unix and C++emerged from the same labs. For several years I used AT&T as my long distance carrierin appreciation of all that CS research, but hearing "thank you for using AT&T" for themillionth time has used up that good will.

Page 29: Data Structures

2

Some languages are forgiving. The programmer needs only a basic sense of how thingswork. Errors in the code are flagged by the compile-time or run-time system, and theprogrammer can muddle through and eventually fix things up to work correctly. The Clanguage is not like that.

The C programming model is that the programmer knows exactly what they want to doand how to use the language constructs to achieve that goal. The language lets the expertprogrammer express what they want in the minimum time by staying out of their way.

C is "simple" in that the number of components in the language is small-- If two languagefeatures accomplish more-or-less the same thing, C will include only one. C's syntax isterse and the language does not restrict what is "allowed" -- the programmer can prettymuch do whatever they want.

C's type system and error checks exist only at compile-time. The compiled code runs in astripped down run-time model with no safety checks for bad type casts, bad array indices,or bad pointers. There is no garbage collector to manage memory. Instead theprogrammer mangages heap memory manually. All this makes C fast but fragile.

Analysis -- Where C FitsBecause of the above features, C is hard for beginners. A feature can work fine in onecontext, but crash in another. The programmer needs to understand how the features workand use them correctly. On the other hand, the number of features is pretty small.

Like most programmers, I have had some moments of real loathing for the C language. Itcan be irritatingly obedient -- you type something incorrectly, and it has a way ofcompiling fine and just doing something you don't expect at run-time. However, as I havebecome a more experienced C programmer, I have grown to appreciate C's straight-to-thepoint style. I have learned not to fall into its little traps, and I appreciate its simplicity.

Perhaps the best advice is just to be careful. Don't type things in you don't understand.Debugging takes too much time. Have a mental picture (or a real drawing) of how your Ccode is using memory. That's good advice in any language, but in C it's critical.

Perl and Java are more "portable" than C (you can run them on different computerswithout a recompile). Java and C++ are more structured than C. Structure is useful forlarge projects. C works best for small projects where performance is important and theprogammers have the time and skill to make it work in C. In any case, C is a very popularand influential language. This is mainly because of C's clean (if minimal) style, it's lackof annoying or regrettable constructs, and the relative ease of writing a C compiler.

Other Resources• The C Programming Language, 2nd ed., by Kernighan and Ritchie. The thin book

which for years was the bible for all C programmers. Written by the originaldesigners of the language. The explanations are pretty short, so this book is better as areference than for beginners.

• http://cslibrary.stanford.edu/102/ Pointers and Memory -- Much more detailabout local memory, pointers, reference parameters, and heap memory than in thisarticle, and memory is really the hardest part of C and C++.

• http://cslibrary.stanford.edu//103/ Linked List Basics -- Once you understand thebasics of pointers and C, these problems are a good way to get more practice.

Page 30: Data Structures

3

Section 1Basic Types and OperatorsC provides a standard, minimal set of basic data types. Sometimes these are called"primitive" types. More complex data structures can be built up from these basic types.

Integer TypesThe "integral" types in C form a family of integer types. They all behave like integers andcan be mixed together and used in similar ways. The differences are due to the differentnumber of bits ("widths") used to implement each type -- the wider types can store agreater ranges of values.

char ASCII character -- at least 8 bits. Pronounced "car". As a practical matterchar is basically always a byte which is 8 bits which is enough to store a singleASCII character. 8 bits provides a signed range of -128..127 or an unsigned range is0..255. char is also required to be the "smallest addressable unit" for the machine --each byte in memory has its own address.

short Small integer -- at least 16 bits which provides a signed range of-32768..32767. Typical size is 16 bits. Not used so much.

int Default integer -- at least 16 bits, with 32 bits being typical. Defined to bethe "most comfortable" size for the computer. If you do not really care about therange for an integer variable, declare it int since that is likely to be an appropriatesize (16 or 32 bit) which works well for that machine.

long Large integer -- at least 32 bits. Typical size is 32 bits which gives a signedrange of about -2 billion ..+2 billion. Some compilers support "long long" for 64 bitints.

The integer types can be preceded by the qualifier unsigned which disallowsrepresenting negative numbers, but doubles the largest positive number representable. Forexample, a 16 bit implementation of short can store numbers in the range-32768..32767, while unsigned short can store 0..65535. You can think of pointersas being a form of unsigned long on a machine with 4 byte pointers. In my opinion,it's best to avoid using unsigned unless you really need to. It tends to cause moremisunderstandings and problems than it is worth.

Extra: Portability ProblemsInstead of defining the exact sizes of the integer types, C defines lower bounds. Thismakes it easier to implement C compilers on a wide range of hardware. Unfortunately itoccasionally leads to bugs where a program runs differently on a 16-bit-int machine thanit runs on a 32-bit-int machine. In particular, if you are designing a function that will beimplemented on several different machines, it is a good idea to use typedefs to set uptypes like Int32 for 32 bit int and Int16 for 16 bit int. That way you can prototype afunction Foo(Int32) and be confident that the typedefs for each machine will be set sothat the function really takes exactly a 32 bit int. That way the code will behave the sameon all the different machines.

char ConstantsA char constant is written with single quotes (') like 'A' or 'z'. The char constant 'A' isreally just a synonym for the ordinary integer value 65 which is the ASCII value for

Page 31: Data Structures

4

uppercase 'A'. There are special case char constants, such as '\t' for tab, for characterswhich are not convenient to type on a keyboard.

'A' uppercase 'A' character

'\n' newline character

'\t' tab character

'\0' the "null" character -- integer value 0 (different from the char digit '0')

'\012' the character with value 12 in octal, which is decimal 10

int ConstantsNumbers in the source code such as 234 default to type int. They may be followed byan 'L' (upper or lower case) to designate that the constant should be a long such as 42L.An integer constant can be written with a leading 0x to indicate that it is expressed inhexadecimal -- 0x10 is way of expressing the number 16. Similarly, a constant may bewritten in octal by preceding it with "0" -- 012 is a way of expressing the number 10.

Type Combination and PromotionThe integral types may be mixed together in arithmetic expressions since they are allbasically just integers with variation in their width. For example, char and int can becombined in arithmetic expressions such as ('b' + 5). How does the compiler dealwith the different widths present in such an expression? In such a case, the compiler"promotes" the smaller type (char) to be the same size as the larger type (int) beforecombining the values. Promotions are determined at compile time based purely on thetypes of the values in the expressions. Promotions do not lose information -- they alwaysconvert from a type to compatible, larger type to avoid losing information.

Pitfall -- int OverflowI once had a piece of code which tried to compute the number of bytes in a buffer withthe expression (k * 1024) where k was an int representing the number of kilobytesI wanted. Unfortunately this was on a machine where int happened to be 16 bits. Sincek and 1024 were both int, there was no promotion. For values of k >= 32, the productwas too big to fit in the 16 bit int resulting in an overflow. The compiler can dowhatever it wants in overflow situations -- typically the high order bits just vanish. Oneway to fix the code was to rewrite it as (k * 1024L) -- the long constant forced thepromotion of the int. This was not a fun bug to track down -- the expression sure lookedreasonable in the source code. Only stepping past the key line in the debugger showed theoverflow problem. "Professional Programmer's Language." This example alsodemonstrates the way that C only promotes based on the types in an expression. Thecompiler does not consider the values 32 or 1024 to realize that the operation willoverflow (in general, the values don't exist until run time anyway). The compiler justlooks at the compile time types, int and int in this case, and thinks everything is fine.

Floating point Typesfloat Single precision floating point number typical size: 32 bits

double Double precision floating point number typical size: 64 bits

long double Possibly even bigger floating point number (somewhat obscure)

Constants in the source code such as 3.14 default to type double unless the are suffixedwith an 'f' (float) or 'l' (long double). Single precision equates to about 6 digits of

Page 32: Data Structures

5

precision and double is about 15 digits of precision. Most C programs use double fortheir computations. The main reason to use float is to save memory if many numbersneed to be stored. The main thing to remember about floating point numbers is that theyare inexact. For example, what is the value of the following double expression?

(1.0/3.0 + 1.0/3.0 + 1.0/3.0) // is this equal to 1.0 exactly?

The sum may or may not be 1.0 exactly, and it may vary from one type of machine toanother. For this reason, you should never compare floating numbers to eachother forequality (==) -- use inequality (<) comparisons instead. Realize that a correct C programrun on different computers may produce slightly different outputs in the rightmost digitsof its floating point computations.

CommentsComments in C are enclosed by slash/star pairs: /* .. comments .. */ whichmay cross multiple lines. C++ introduced a form of comment started by two slashes andextending to the end of the line: // comment until the line endThe // comment form is so handy that many C compilers now also support it, although itis not technically part of the C language.

Along with well-chosen function names, comments are an important part of well writtencode. Comments should not just repeat what the code says. Comments should describewhat the code accomplishes which is much more interesting than a translation of whateach statement does. Comments should also narrate what is tricky or non-obvious about asection of code.

VariablesAs in most languages, a variable declaration reserves and names an area in memory at runtime to hold a value of particular type. Syntactically, C puts the type first followed by thename of the variable. The following declares an int variable named "num" and the 2ndline stores the value 42 into num.

int num;num = 42;

num 42

A variable corresponds to an area of memory which can store a value of the given type.Making a drawing is an excellent way to think about the variables in a program. Draweach variable as box with the current value inside the box. This may seem like a"beginner" technique, but when I'm buried in some horribly complex programmingproblem, I invariably resort to making a drawing to help think the problem through.

Variables, such as num, do not have their memory cleared or set in any way when theyare allocated at run time. Variables start with random values, and it is up to the programto set them to something sensible before depending on their values.

Names in C are case sensitive so "x" and "X" refer to different variables. Names cancontain digits and underscores (_), but may not begin with a digit. Multiple variables canbe declared after the type by separating them with commas. C is a classical "compiletime" language -- the names of the variables, their types, and their implementations are allflushed out by the compiler at compile time (as opposed to figuring such details out at runtime like an interpreter).

Page 33: Data Structures

6

float x, y, z, X;

Assignment Operator =The assignment operator is the single equals sign (=).

i = 6;i = i + 1;

The assignment operator copies the value from its right hand side to the variable on itsleft hand side. The assignment also acts as an expression which returns the newlyassigned value. Some programmers will use that feature to write things like the following.

y = (x = 2 * x); // double x, and also put x's new value in y

TruncationThe opposite of promotion, truncation moves a value from a type to a smaller type. Inthat case, the compiler just drops the extra bits. It may or may not generate a compiletime warning of the loss of information. Assigning from an integer to a smaller integer(e.g.. long to int, or int to char) drops the most significant bits. Assigning from afloating point type to an integer drops the fractional part of the number.

char ch;int i;

i = 321;ch = i; // truncation of an int value to fit in a char// ch is now 65

The assignment will drop the upper bits of the int 321. The lower 8 bits of the number321 represents the number 65 (321 - 256). So the value of ch will be (char)65 whichhappens to be 'A'.

The assignment of a floating point type to an integer type will drop the fractional part ofthe number. The following code will set i to the value 3. This happens when assigning afloating point number to an integer or passing a floating point number to a function whichtakes an integer.

double pi;int i;

pi = 3.14159;i = pi; // truncation of a double to fit in an int// i is now 3

Pitfall -- int vs. float ArithmeticHere's an example of the sort of code where int vs. float arithmetic can causeproblems. Suppose the following code is supposed to scale a homework score in therange 0..20 to be in the range 0..100.

{int score;

...// suppose score gets set in the range 0..20 somehow

Page 34: Data Structures

7

score = (score / 20) * 100; // NO -- score/20 truncates to 0...

Unfortunately, score will almost always be set to 0 for this code because the integerdivision in the expression (score/20) will be 0 for every value of score less than 20.The fix is to force the quotient to be computed as a floating point number...

score = ((double)score / 20) * 100; // OK -- floating point division from cast

score = (score / 20.0) * 100; // OK -- floating point division from 20.0

score = (int)(score / 20.0) * 100; // NO -- the (int) truncates the floating// quotient back to 0

No Boolean -- Use intC does not have a distinct boolean type-- int is used instead. The language treats integer0 as false and all non-zero values as true. So the statement...

i = 0;while (i - 10) {

...

will execute until the variable i takes on the value 10 at which time the expression (i -10) will become false (i.e. 0). (we'll see the while() statement a bit later)

Mathematical OperatorsC includes the usual binary and unary arithmetic operators. See the appendix for the tableof precedence. Personally, I just use parenthesis liberally to avoid any bugs due to amisunderstanding of precedence. The operators are sensitive to the type of the operands.So division (/) with two integer arguments will do integer division. If either argument isa float, it does floating point division. So (6/4) evaluates to 1 while (6/4.0)evaluates to 1.5 -- the 6 is promoted to 6.0 before the division.

+ Addition

- Subtraction

/ Division

* Multiplication

% Remainder (mod)

Unary Increment Operators: ++ --The unary ++ and -- operators increment or decrement the value in a variable. There are"pre" and "post" variants for both operators which do slightly different things (explainedbelow)

var++ increment "post" variant

++var increment "pre" variant

Page 35: Data Structures

8

var-- decrement "post" variant

--var decrement "pre" variant

int i = 42;i++; // increment on i// i is now 43i--; // decrement on i// i is now 42

Pre and Post VariationsThe Pre/Post variation has to do with nesting a variable with the increment or decrementoperator inside an expression -- should the entire expression represent the value of thevariable before or after the change? I never use the operators in this way (see below), butan example looks like...

int i = 42;int j;

j = (i++ + 10);// i is now 43// j is now 52 (NOT 53)

j = (++i + 10)// i is now 44// j is now 54

C Programming Cleverness and Ego IssuesRelying on the difference between the pre and post variations of these operators is aclassic area of C programmer ego showmanship. The syntax is a little tricky. It makes thecode a little shorter. These qualities drive some C programmers to show off how cleverthey are. C invites this sort of thing since the language has many areas (this is just oneexample) where the programmer can get a complex effect using a code which is short anddense.

If I want j to depend on i's value before the increment, I write...

j = (i + 10);i++;

Or if I want to j to use the value after the increment, I write...

i++;j = (i + 10);

Now then, isn't that nicer? (editorial) Build programs that do something cool rather thanprograms which flex the language's syntax. Syntax -- who cares?

Relational OperatorsThese operate on integer or floating point values and return a 0 or 1 boolean value.

== Equal

Page 36: Data Structures

9

!= Not Equal

> Greater Than

< Less Than

>= Greater or Equal

<= Less or Equal

To see if x equals three, write something like:

if (x == 3) ...

Pitfall = ==An absolutely classic pitfall is to write assignment (=) when you mean comparison (==).This would not be such a problem, except the incorrect assignment version compiles finebecause the compiler assumes you mean to use the value returned by the assignment. Thisis rarely what you want

if (x = 3) ...

This does not test if x is 3. This sets x to the value 3, and then returns the 3 to the if fortesting. 3 is not 0, so it counts as "true" every time. This is probably the single mostcommon error made by beginning C programmers. The problem is that the compiler is nohelp -- it thinks both forms are fine, so the only defense is extreme vigilance whencoding. Or write "= ≠ ==" in big letters on the back of your hand before coding. Thismistake is an absolute classic and it's a bear to debug. Watch Out! And need I say:"Professional Programmer's Language."

Logical OperatorsThe value 0 is false, anything else is true. The operators evaluate left to right and stop assoon as the truth or falsity of the expression can be deduced. (Such operators are called"short circuiting") In ANSI C, these are furthermore guaranteed to use 1 to represent true,and not just some random non-zero bit pattern. However, there are many C programs outthere which use values other than 1 for true (non-zero pointers for example), so whenprogramming, do not assume that a true boolean is necessarily 1 exactly.

! Boolean not (unary)

&& Boolean and

|| Boolean or

Bitwise OperatorsC includes operators to manipulate memory at the bit level. This is useful for writing low-level hardware or operating system code where the ordinary abstractions of numbers,characters, pointers, etc... are insufficient -- an increasingly rare need. Bit manipulationcode tends to be less "portable". Code is "portable" if with no programmer intervention itcompiles and runs correctly on different types of computers. The bitwise operations are

Page 37: Data Structures

10

typically used with unsigned types. In particular, the shift operations are guaranteed toshift 0 bits into the newly vacated positions when used on unsigned values.

~ Bitwise Negation (unary) – flip 0 to 1 and 1 to 0 throughout

& Bitwise And

| Bitwise Or

^ Bitwise Exclusive Or

>> Right Shift by right hand side (RHS) (divide by power of 2)

<< Left Shift by RHS (multiply by power of 2)

Do not confuse the Bitwise operators with the logical operators. The bitwise connectivesare one character wide (&, |) while the boolean connectives are two characters wide (&&,||). The bitwise operators have higher precedence than the boolean operators. Thecompiler will never help you out with a type error if you use & when you meant &&. Asfar as the type checker is concerned, they are identical-- they both take and produceintegers since there is no distinct boolean type.

Other Assignment OperatorsIn addition to the plain = operator, C includes many shorthand operators which representsvariations on the basic =. For example "+=" adds the right hand side to the left hand side.x = x + 10; can be reduced to x += 10;. This is most useful if x is a longexpression such as the following, and in some cases it may run a little faster.

person->relatives.mom.numChildren += 2; // increase children by 2

Here's the list of assignment shorthand operators...

+=, -= Increment or decrement by RHS

*=, /= Multiply or divide by RHS

%= Mod by RHS

>>= Bitwise right shift by RHS (divide by power of 2)

<<= Bitwise left shift RHS (multiply by power of 2)

&=, |=, ^= Bitwise and, or, xor by RHS

Page 38: Data Structures

11

Section 2Control StructuresCurly Braces {}C uses curly braces ({}) to group multiple statements together. The statements execute inorder. Some languages let you declare variables on any line (C++). Other languages insistthat variables are declared only at the beginning of functions (Pascal). C takes the middleroad -- variables may be declared within the body of a function, but they must follow a'{'. More modern languages like Java and C++ allow you to declare variables on any line,which is handy.

If StatementBoth an if and an if-else are available in C. The <expression> can be any validexpression. The parentheses around the expression are required, even if it is just a singlevariable.

if (<expression>) <statement> // simple form with no {}'s or else clause

if (<expression>) { // simple form with {}'s to group statements<statement><statement>

}

if (<expression>) { // full then/else form<statement>

}else {

<statement>}

Conditional Expression -or- The Ternary OperatorThe conditional expression can be used as a shorthand for some if-else statements. Thegeneral syntax of the conditional operator is:

<expression1> ? <expression2> : <expression3>

This is an expression, not a statement, so it represents a value. The operator works byevaluating expression1. If it is true (non-zero), it evaluates and returns expression2 .Otherwise, it evaluates and returns expression3.

The classic example of the ternary operator is to return the smaller of two variables.Every once in a while, the following form is just what you needed. Instead of...

if (x < y) {min = x;

}else {

min = y;}

Page 39: Data Structures

12

You just say...

min = (x < y) ? x : y;

Switch StatementThe switch statement is a sort of specialized form of if used to efficiently separatedifferent blocks of code based on the value of an integer. The switch expression isevaluated, and then the flow of control jumps to the matching const-expression case. Thecase expressions are typically int or char constants. The switch statement is probablythe single most syntactically awkward and error-prone features of the C language.

switch (<expression>) {case <const-expression-1>:

<statement>break;

case <const-expression-2>:<statement>break;

case <const-expression-3>: // here we combine case 3 and 4case <const-expression-4>:

<statement>break;

default: // optional<statement>

}

Each constant needs its own case keyword and a trailing colon (:). Once execution hasjumped to a particular case, the program will keep running through all the cases from thatpoint down -- this so called "fall through" operation is used in the above example so thatexpression-3 and expression-4 run the same statements. The explicit break statementsare necessary to exit the switch. Omitting the break statements is a common error -- itcompiles, but leads to inadvertent fall-through behavior.

Why does the switch statement fall-through behavior work the way it does? The bestexplanation I can think of is that originally C was developed for an audience of assemblylanguage programmers. The assembly language programmers were used to the idea of ajump table with fall-through behavior, so that's the way C does it (it's also relatively easyto implement it this way.) Unfortunately, the audience for C is now quite different, andthe fall-through behavior is widely regarded as a terrible part of the language.

While LoopThe while loop evaluates the test expression before every loop, so it can execute zerotimes if the condition is initially false. It requires the parenthesis like the if.

while (<expression>) {<statement>

}

Page 40: Data Structures

13

Do-While LoopLike a while, but with the test condition at the bottom of the loop. The loop body willalways execute at least once. The do-while is an unpopular area of the language, mosteveryone tries to use the straight while if at all possible.

do {<statement>

} while (<expression>)

For LoopThe for loop in C is the most general looping construct. The loop header contains threeparts: an initialization, a continuation condition, and an action.

for (<initialization>; <continuation>; <action>) {<statement>

}

The initialization is executed once before the body of the loop is entered. The loopcontinues to run as long as the continuation condition remains true (like a while). Afterevery execution of the loop, the action is executed. The following example executes 10times by counting 0..9. Many loops look very much like the following...

for (i = 0; i < 10; i++) {<statement>

}

C programs often have series of the form 0..(some_number-1). It's idiomatic in C for theabove type loop to start at 0 and use < in the test so the series runs up to but not equal tothe upper bound. In other languages you might start at 1 and use <= in the test.

Each of the three parts of the for loop can be made up of multiple expressions separatedby commas. Expressions separated by commas are executed in order, left to right, andrepresent the value of the last expression. (See the string-reverse example below for ademonstration of a complex for loop.)

BreakThe break statement will move control outside a loop or switch statement. Stylisticallyspeaking, break has the potential to be a bit vulgar. It's preferable to use a straightwhile with a single test at the top if possible. Sometimes you are forced to use a breakbecause the test can occur only somewhere in the midst of the statements in the loopbody. To keep the code readable, be sure to make the break obvious -- forgetting toaccount for the action of a break is a traditional source of bugs in loop behavior.

while (<expression>) {<statement><statement>

if (<condition which can only be evaluated here>)break;

<statement><statement>

}// control jumps down here on the break

Page 41: Data Structures

14

The break does not work with if. It only works in loops and switches. Thinking that abreak refers to an if when it really refers to the enclosing while has created some highquality bugs. When using a break, it's nice to write the enclosing loop to iterate in themost straightforward, obvious, normal way, and then use the break to explicitly catchthe exceptional, weird cases.

ContinueThe continue statement causes control to jump to the bottom of the loop, effectivelyskipping over any code below the continue. As with break, this has a reputation asbeing vulgar, so use it sparingly. You can almost always get the effect more clearly usingan if inside your loop.

while (<expression>) {...if (<condition>)

continue;......// control jumps here on the continue

}

Page 42: Data Structures

15

Section 3Complex Data TypesC has the usual facilities for grouping things together to form composite types-- arraysand records (which are called "structures"). The following definition declares a typecalled "struct fraction" that has two integer sub fields named "numerator" and"denominator". If you forget the semicolon it tends to produce a syntax error in whateverthing follows the struct declaration.

struct fraction {int numerator;int denominator;

}; // Don't forget the semicolon!

This declaration introduces the type struct fraction (both words are required) as anew type. C uses the period (.) to access the fields in a record. You can copy two recordsof the same type using a single assignment statement, however == does not work onstructs.

struct fraction f1, f2; // declare two fractions

f1.numerator = 22;f1.denominator = 7;

f2 = f1; // this copies over the whole struct

ArraysThe simplest type of array in C is one which is declared and used in one place. There aremore complex uses of arrays which I will address later along with pointers. The followingdeclares an array called scores to hold 100 integers and sets the first and last elements.C arrays are always indexed from 0. So the first int in scores array is scores[0]and the last is scores[99].

int scores[100];

scores[0] = 13; // set first elementscores[99] = 42; // set last element

Page 43: Data Structures

16

0

scores

Index 1 2 99

13

Someone else’s memory off either end of the array — do not read or write this memory.

There is space for each int element in the scores array — this element is referred to as scores[0].

-5673 22541 42

These elements have random values because the code has not yet initialized them to anything.

The name of the array refers to the whole array. (implementation) it works by representing a pointer to the start of the array.

It's a very common error to try to refer to non-existent scores[100] element. C doesnot do any run time or compile time bounds checking in arrays. At run time the code willjust access or mangle whatever memory it happens to hit and crash or misbehave in someunpredictable way thereafter. "Professional programmer's language." The convention ofnumbering things 0..(number of things - 1) pervades the language. To bestintegrate with C and other C programmers, you should use that sort of numbering in yourown data structures as well.

Multidimensional ArraysThe following declares a two-dimensional 10 by 10 array of integers and sets the first andlast elements to be 13.

int board [10][10];

board[0][0] = 13;board[9][9] = 13;

The implementation of the array stores all the elements in a single contiguous block ofmemory. The other possible implementation would be a combination of several distinctone dimensional arrays -- that's not how C does it. In memory, the array is arranged withthe elements of the rightmost index next to each other. In other words, board[1][8]comes right before board[1][9] in memory.

(highly optional efficiency point) It's typically efficient to access memory which is nearother recently accessed memory. This means that the most efficient way to read through achunk of the array is to vary the rightmost index the most frequently since that will accesselements that are near each other in memory.

Page 44: Data Structures

17

Array of StructsThe following declares an array named "numbers" which holds 1000 structfraction's.

struct fraction numbers[1000];

numbers[0].numerator = 22; /* set the 0th struct fraction */numbers[0].denominator = 7;

Here's a general trick for unraveling C variable declarations: look at the right hand sideand imagine that it is an expression. The type of that expression is the left hand side. Forthe above declarations, an expression which looks like the right hand side(numbers[1000], or really anything of the form numbers[...]) will be the typeon the left hand side (struct fraction).

PointersA pointer is a value which represents a reference to another value sometimes known asthe pointer's "pointee". Hopefully you have learned about pointers somewhere else, sincethe preceding sentence is probably inadequate explanation. This discussion willconcentrate on the syntax of pointers in C -- for a much more complete discussion ofpointers and their use see http://cslibrary.stanford.edu/102/, Pointers and Memory.

SyntaxSyntactically C uses the asterisk or "star" (*) to indicate a pointer. C defines pointer typesbased on the type pointee. A char* is type of pointer which refers to a single char. astruct fraction* is type of pointer which refers to a struct fraction.

int* intPtr; // declare an integer pointer variable intPtr

char* charPtr; // declares a character pointer --// a very common type of pointer

// Declare two struct fraction pointers// (when declaring multiple variables on one line, the *// should go on the right with the variable)struct fraction *f1, *f2;

The Floating "*"In the syntax, the star is allowed to be anywhere between the base type and the variablename. Programmer's have their own conventions-- I generally stick the * on the left withthe type. So the above declaration of intPtr could be written equivalently...

int *intPtr; // these are all the sameint * intPtr;int* intPtr;

Pointer DereferencingWe'll see shortly how a pointer is set to point to something -- for now just assume thepointer points to memory of the appropriate type. In an expression, the unary * to the leftof a pointer dereferences it to retrieve the value it points to. The following drawing showsthe types involved with a single pointer pointing to a struct fraction.

Page 45: Data Structures

18

struct fraction* f1;

f17

22

denominator

numerator

struct fraction*struct fraction(the whole block of memory)

int(within block of memory)

Expression Typef1 struct fraction**f1 struct fraction(*f1).numerator int

There's an alternate, more readable syntax available for dereferencing a pointer to astruct. A "->" at the right of the pointer can access any of the fields in the struct. So thereference to the numerator field could be written f1->numerator.

Here are some more complex declarations...

struct fraction** fp; // a pointer to a pointer to a struct fraction

struct fraction fract_array[20]; // an array of 20 struct fractions

struct fraction* fract_ptr_array[20]; // an array of 20 pointers to// struct fractions

One nice thing about the C type syntax is that it avoids the circular definition problemswhich come up when a pointer structure needs to refer to itself. The following definitiondefines a node in a linked list. Note that no preparatory declaration of the node pointertype is necessary.

struct node {int data;struct node* next;

};

The & OperatorThe & operator is one of the ways that pointers are set to point to things. The & operatorcomputes a pointer to the argument to its right. The argument can be any variable whichtakes up space in the stack or heap (known as an "LValue" technically). So &i and&(f1->numerator) are ok, but &6 is not. Use & when you have some memory, andyou want a pointer to that memory.

Page 46: Data Structures

19

void foo() {int* p; // p is a pointer to an integerint i; // i is an integer

p = &i; // Set p to point to i*p = 13; // Change what p points to -- in this case i -- to 13

// At this point i is 13. So is *p. In fact *p is i.}

p

13i

When using a pointer to an object created with &, it is important to only use the pointer solong as the object exists. A local variable exists only as long as the function where it isdeclared is still executing (we'll see functions shortly). In the above example, i existsonly as long as foo() is executing. Therefore any pointers which were initialized with&i are valid only as long as foo() is executing. This "lifetime" constraint of localmemory is standard in many languages, and is something you need to take into accountwhen using the & operator.

NULLA pointer can be assigned the value 0 to explicitly represent that it does not currentlyhave a pointee. Having a standard representation for "no current pointee" turns out to bevery handy when using pointers. The constant NULL is defined to be 0 and is typicallyused when setting a pointer to NULL. Since it is just 0, a NULL pointer will behave likea boolean false when used in a boolean context. Dereferencing a NULL pointer is an errorwhich, if you are lucky, the computer will detect at runtime -- whether the computerdetects this depends on the operating system.

Pitfall -- Uninitialized PointersWhen using pointers, there are two entities to keep track of. The pointer and the memoryit is pointing to, sometimes called the "pointee". There are three things which must bedone for a pointer/pointee relationship to work...

(1) The pointer must be declared and allocated

(2) The pointee must be declared and allocated

(3) The pointer (1) must be initialized so that it points to the pointee (2)

The most common pointer related error of all time is the following: Declare and allocatethe pointer (step 1). Forget step 2 and/or 3. Start using the pointer as if it has been setupto point to something. Code with this error frequently compiles fine, but the runtimeresults are disastrous. Unfortunately the pointer does not point anywhere good unless (2)and (3) are done, so the run time dereference operations on the pointer with * will misuseand trample memory leading to a random crash at some point.

Page 47: Data Structures

20

{int* p;

*p = 13; // NO NO NO p does not point to an int yet// this just overwrites a random area in memory

}

-14346

p

i

Of course your code won't be so trivial, but the bug has the same basic form: declare apointer, but forget to set it up to point to a particular pointee.

Using PointersDeclaring a pointer allocates space for the pointer itself, but it does not allocate spacefor the pointee. The pointer must be set to point to something before you can dereferenceit.

Here's some code which doesn't do anything useful, but which does demonstrate (1) (2)(3) for pointer use correctly...

int* p; // (1) allocate the pointerint i; // (2) allocate pointeestruct fraction f1; // (2) allocate pointee

p = &i; // (3) setup p to point to i*p = 42; // ok to use p since it's setup

p = &(f1.numerator); // (3) setup p to point to a different int*p = 22;

p = &(f1.denominator); // (3)*p = 7;

So far we have just used the & operator to create pointers to simple variables such as i.Later, we'll see other ways of getting pointers with arrays and other techniques.

C StringsC has minimal support of character strings. For the most part, strings operate as ordinaryarrays of characters. Their maintenance is up to the programmer using the standardfacilities available for arrays and pointers. C does include a standard library of functionswhich perform common string operations, but the programmer is responsible for themanaging the string memory and calling the right functions. Unfortunately computationsinvolving strings are very common, so becoming a good C programmer often requiresbecoming adept at writing code which manages strings which means managing pointersand arrays.

Page 48: Data Structures

21

A C string is just an array of char with the one additional convention that a "null"character ('\0') is stored after the last real character in the array to mark the end of thestring. The compiler represents string constants in the source code such as "binky" asarrays which follow this convention. The string library functions (see the appendix for apartial list) operate on strings stored in this way. The most useful library function isstrcpy(char dest[], const char source[]); which copies the bytes ofone string over to another. The order of the arguments to strcpy() mimics the argumentsin of '=' -- the right is assigned to the left. Another useful string function isstrlen(const char string[]); which returns the number of characters in Cstring not counting the trailing '\0'.

Note that the regular assignment operator (=) does not do string copying which is whystrcpy() is necessary. See Section 6, Advanced Pointers and Arrays, for more detail onhow arrays and pointers work.

The following code allocates a 10 char array and uses strcpy() to copy the bytes of thestring constant "binky" into that local array.

{char localString[10];

strcpy(localString, "binky");}

b i n k y 0 x x x x

0 1 2 ...

localString

The memory drawing shows the local variable localString with the string "binky"copied into it. The letters take up the first 5 characters and the '\0' char marks the end ofthe string after the 'y'. The x's represent characters which have not been set to anyparticular value.

If the code instead tried to store the string "I enjoy languages which have good stringsupport" into localString, the code would just crash at run time since the 10 characterarray can contain at most a 9 character string. The large string will be written passed theright hand side of localString, overwriting whatever was stored there.

String Code ExampleHere's a moderately complex for loop which reverses a string stored in a local array. Itdemonstrates calling the standard library functions strcpy() and strlen() and demonstratesthat a string really is just an array of characters with a '\0' to mark the effective end of thestring. Test your C knowledge of arrays and for loops by making a drawing of thememory for this code and tracing through its execution to see how it works.

Page 49: Data Structures

22

{

char string[1000]; // string is a local 1000 char arrayint len;

strcpy(string, "binky");len = strlen(string);

/* Reverse the chars in the string: i starts at the beginning and goes up j starts at the end and goes down i/j exchange their chars as they go until they meet*/int i, j;char temp;for (i = 0, j = len - 1; i < j; i++, j--) {

temp = string[i];string[i] = string[j];string[j] = temp;

}

// at this point the local string should be "yknib"

}

"Large Enough" StringsThe convention with C strings is that the owner of the string is responsible for allocatingarray space which is "large enough" to store whatever the string will need to store. Mostroutines do not check that size of the string memory they operate on, they just assume itsbig enough and blast away. Many, many programs contain declarations like thefollowing...

{char localString[1000];...

}

The program works fine so long as the strings stored are 999 characters or shorter.Someday when the program needs to store a string which is 1000 characters or longer,then it crashes. Such array-not-quite-big-enough problems are a common source of bugs,and are also the source of so called "buffer overflow" security problems. This scheme hasthe additional disadvantage that most of the time when the array is storing short strings,95% of the memory reserved is actually being wasted. A better solution allocates thestring dynamically in the heap, so it has just the right size.

To avoid buffer overflow attacks, production code should check the size of the data first,to make sure it fits in the destination string. See the strlcpy() function in Appendix A.

char*Because of the way C handles the types of arrays, the type of the variablelocalString above is essentially char*. C programs very often manipulate stringsusing variables of type char* which point to arrays of characters. Manipulating theactual chars in a string requires code which manipulates the underlying array, or the use

Page 50: Data Structures

23

of library functions such as strcpy() which manipulate the array for you. See Section 6 formore detail on pointers and arrays.

TypeDefA typedef statement introduces a shorthand name for a type. The syntax is...

typedef <type> <name>;

The following defines Fraction type to be the type (struct fraction). C is casesensitive, so fraction is different from Fraction. It's convenient to use typedef tocreate types with upper case names and use the lower-case version of the same word as avariable.

typedef struct fraction Fraction;

Fraction fraction; // Declare the variable "fraction" of type "Fraction"// which is really just a synonym for "struct fraction".

The following typedef defines the name Tree as a standard pointer to a binary tree nodewhere each node contains some data and "smaller" and "larger" subtree pointers.

typedef struct treenode* Tree;struct treenode {

int data;Tree smaller, larger; // equivalently, this line could say

}; // "struct treenode *smaller, *larger"

Page 51: Data Structures

24

Section 4FunctionsAll languages have a construct to separate and package blocks of code. C uses the"function" to package blocks of code. This article concentrates on the syntax andpeculiarities of C functions. The motivation and design for dividing a computation intoseparate blocks is an entire discipline in its own.

A function has a name, a list of arguments which it takes when called, and the block ofcode it executes when called. C functions are defined in a text file and the names of allthe functions in a C program are lumped together in a single, flat namespace. The specialfunction called "main" is where program execution begins. Some programmers like tobegin their function names with Upper case, using lower case for variables andparameters, Here is a simple C function declaration. This declares a function namedTwice which takes a single int argument named num. The body of the functioncomputes the value which is twice the num argument and returns that value to the caller.

/* Computes double of a number. Works by tripling the number, and then subtracting to get back to double.*/static int Twice(int num) {

int result = num * 3;result = result - num;return(result);

}

SyntaxThe keyword "static" defines that the function will only be available to callers in thefile where it is declared. If a function needs to be called from another file, the functioncannot be static and will require a prototype -- see prototypes below. The static formis convenient for utility functions which will only be used in the file where they aredeclared. Next , the "int" in the function above is the type of its return value. Nextcomes name of the function and its list of parameters. When referring to a function byname in documentation or other prose, it's a convention to keep the parenthesis () suffix,so in this case I refer to the function as "Twice()". The parameters are listed with theirtypes and names, just like variables.

Inside the function, the parameter num and the local variable result are "local" to thefunction -- they get their own memory and exist only so long as the function is executing.This independence of "local" memory is a standard feature of most languages (SeeCSLibrary/102 for the detailed discussion of local memory).

The "caller" code which calls Twice() looks like...

int num = 13;int a = 1;int b = 2;a = Twice(a); // call Twice() passing the value of ab = Twice(b + num); // call Twice() passing the value b+num// a == 2// b == 30// num == 13 (this num is totally independent of the "num" local to Twice()

Page 52: Data Structures

25

Things to notice...

(vocabulary) The expression passed to a function by its caller is called the "actualparameter" -- such as "a" and "b + num" above. The parameter storage local to thefunction is called the "formal parameter" such as the "num" in "static int Twice(intnum)".

Parameters are passed "by value" that means there is a single copying assignmentoperation (=) from each actual parameter to set each formal parameter. The actualparameter is evaluated in the caller's context, and then the value is copied into thefunction's formal parameter just before the function begins executing. The alternativeparameter mechanism is "by reference" which C does not implement directly, butwhich the programmer can implement manually when needed (see below). When aparameter is a struct, it is copied.

The variables local to Twice(), num and result, only exist temporarily whileTwice() is executing. This is the standard definition for "local" storage forfunctions.

The return at the end of Twice() computes the return value and exits the function.Execution resumes with the caller. There can be multiple return statements within afunction, but it's good style to at least have one at the end if a return value needs to bespecified. Forgetting to account of a return somewhere in the middle of a functionis a traditional source of bugs.

C-ing and Nothingness -- voidvoid is a type formalized in ANSI C which means "nothing". To indicate that a functiondoes not return anything, use void as the return type. Also, by convention, a pointerwhich does not point to any particular type is declared as void*. Sometimes void* isused to force two bodies of code to not depend on each other where void* translatesroughly to "this points to something, but I'm not telling you (the client) the type of thepointee exactly because you do not really need to know." If a function does not take anyparameters, its parameter list is empty, or it can contain the keyword void but that styleis now out of favor.

void TakesAnIntAndReturnsNothing(int anInt);

int TakesNothingAndReturnsAnInt();int TakesNothingAndReturnsAnInt(void); // equivalent syntax for above

Call by Value vs. Call by ReferenceC passes parameters "by value" which means that the actual parameter values are copiedinto local storage. The caller and callee functions do not share any memory -- they eachhave their own copy. This scheme is fine for many purposes, but it has twodisadvantages.

1) Because the callee has its own copy, modifications to that memory are notcommunicated back to the caller. Therefore, value parameters do not allow the calleeto communicate back to the caller. The function's return value can communicate someinformation back to the caller, but not all problems can be solved with the singlereturn value.

Page 53: Data Structures

26

2) Sometimes it is undesirable to copy the value from the caller to the callee because thevalue is large and so copying it is expensive, or because at a conceptual level copyingthe value is undesirable.

The alternative is to pass the arguments "by reference". Instead of passing a copy of avalue from the caller to the callee, pass a pointer to the value. In this way there is onlyone copy of the value at any time, and the caller and callee both access that one valuethrough pointers.

Some languages support reference parameters automatically. C does not do this -- theprogrammer must implement reference parameters manually using the existing pointerconstructs in the language.

Swap ExampleThe classic example of wanting to modify the caller's memory is a swap() functionwhich exchanges two values. Because C uses call by value, the following version ofSwap will not work...

void Swap(int x, int y) { // NO does not workint temp;

temp = x;x = y; // these operations just change the local x,y,tempy = temp; // -- nothing connects them back to the caller's a,b

}

// Some caller code which calls Swap()...int a = 1;int b = 2;Swap(a, b);

Swap() does not affect the arguments a and b in the caller. The function above onlyoperates on the copies of a and b local to Swap() itself. This is a good example of how"local" memory such as ( x, y, temp) behaves -- it exists independent of everything elseonly while its owning function is running. When the owning function exits, its localmemory disappears.

Reference Parameter TechniqueTo pass an object X as a reference parameter, the programmer must pass a pointer to Xinstead of X itself. The formal parameter will be a pointer to the value of interest. Thecaller will need to use & or other operators to compute the correct pointer actualparameter. The callee will need to dereference the pointer with * where appropriate toaccess the value of interest. Here is an example of a correct Swap() function.

static void Swap(int* x, int* y) { // params are int* instead of intint temp;

temp = *x; // use * to follow the pointer back to the caller's memory*x = *y;*y = temp;

}

Page 54: Data Structures

27

// Some caller code which calls Swap()...int a = 1;int b = 2;

Swap(&a, &b);

Things to notice...

• The formal parameters are int* instead of int.

• The caller uses & to compute pointers to its local memory (a,b).

• The callee uses * to dereference the formal parameter pointers back to get the caller'smemory.

Since the operator & produces the address of a variable -- &a is a pointer to a. InSwap() itself, the formal parameters are declared to be pointers, and the values ofinterest (a,b) are accessed through them. There is no special relationship between thenames used for the actual and formal parameters. The function call matches up the actualand formal parameters by their order -- the first actual parameter is assigned to the firstformal parameter, and so on. I deliberately used different names (a,b vs x,y) to emphasizethat the names do not matter.

constThe qualifier const can be added to the left of a variable or parameter type to declare thatthe code using the variable will not change the variable. As a practical matter, use ofconst is very sporadic in the C programming community. It does have one very handyuse, which is to clarify the role of a parameter in a function prototype...

void foo(const struct fraction* fract);

In the foo() prototype, the const declares that foo() does not intend to change the structfraction pointee which is passed to it. Since the fraction is passed by pointer, we couldnot know otherwise if foo() intended to change our memory or not. Using the const,foo() makes its intentions clear. Declaring this extra bit of information helps to clarify therole of the function to its implementor and caller.

Page 55: Data Structures

28

Bigger Pointer ExampleThe following code is a large example of using reference parameters. There are severalcommon features of C programs in this example...Reference parameters are used to allowthe functions Swap() and IncrementAndSwap() to affect the memory of their callers.There's a tricky case inside of IncrementAndSwap() where it calls Swap() -- no additionaluse of & is necessary in this case since the parameters x, y inside InrementAndSwap() arealready pointers to the values of interest. The names of the variables through theprogram(a, b, x, y, alice, bob) do not need to match up in any particular way for theparameters to work. The parameter mechanism only depends on the types of theparameters and their order in the parameter list -- not their names. Finally this is anexample of what multiple functions look like in a file and how they are called from themain() function.

static void Swap(int* a, int* b) {int temp;temp = *a;*a = *b;*b = temp;

}

static void IncrementAndSwap(int* x, int* y) {(*x)++;(*y)++;Swap(x, y); // don't need & here since a and b are already

// int*'s.}

int main(){

int alice = 10;int bob = 20;

Swap(&alice, &bob);// at this point alice==20 and bob==10

IncrementAndSwap(&alice, &bob);// at this point alice==11 and bob==21

return 0;}

Page 56: Data Structures

29

Section 5Odds and Endsmain()The execution of a C program begins with function named main(). All of the files andlibraries for the C program are compiled together to build a single program file. That filemust contain exactly one main() function which the operating system uses as the startingpoint for the program. Main() returns an int which, by convention, is 0 if the programcompleted successfully and non-zero if the program exited due to some error condition.This is just a convention which makes sense in shell oriented environments such as Unixor DOS.

Multiple FilesFor a program of any size, it's convenient to separate the functions into several separatefiles. To allow the functions in separate files to cooperate, and yet allow the compiler towork on the files independently, C programs typically depend on two features...

PrototypesA "prototype" for a function gives its name and arguments but not its body. In order for acaller, in any file, to use a function, the caller must have seen the prototype for thatfunction. For example, here's what the prototypes would look like for Twice() andSwap(). The function body is absent and there's a semicolon (;) to terminate theprototype...

int Twice(int num);void Swap(int* a, int* b);

In pre-ANSI C, the rules for prototypes where very sloppy -- callers were not required tosee prototypes before calling functions, and as a result it was possible to get in situationswhere the compiler generated code which would crash horribly.

In ANSI C, I'll oversimplify a little to say that...

1) a function may be declared static in which case it can only be used in the same filewhere it is used below the point of its declaration. Static functions do not require aseparate prototype so long as they are defined before or above where they are calledwhich saves some work.

2) A non-static function needs a prototype. When the compiler compiles a functiondefinition, it must have previously seen a prototype so that it can verify that the twoare in agreement ("prototype before definition" rule). The prototype must also be seenby any client code which wants to call the function ("clients must see prototypes"rule).(The require-prototypes behavior is actually somewhat of a compiler option, butit's smart to leave it on.)

PreprocessorThe preprocessing step happens to the C source before it is fed to the compiler. The twomost common preprocessor directives are #define and #include...

Page 57: Data Structures

30

#defineThe #define directive can be used to set up symbolic replacements in the source. As withall preprocessor operations, #define is extremely unintelligent -- it just does textualreplacement without understanding. #define statements are used as a crude way ofestablishing symbolic constants.

#define MAX 100#define SEVEN_WORDS that_symbol_expands_to_all_these_words

Later code can use the symbols MAX or SEVEN_WORDS which will be replaced by thetext to the right of each symbol in its #define.

#includeThe "#include" directive brings in text from different files during compilation. #include isa very unintelligent and unstructured -- it just pastes in the text from the given file andcontinues compiling. The #include directive is used in the .h/.c file convention belowwhich is used to satisfy the various constraints necessary to get prototypes correct.

#include "foo.h" // refers to a "user" foo.h file --// in the originating directory for the compile

#include <foo.h> // refers to a "system" foo.h file --// in the compiler's directory somewhere

foo.h vs foo.cThe universally followed convention for C is that for a file named "foo.c" containing abunch of functions...

• A separate file named foo.h will contain the prototypes for the functions in foo.cwhich clients may want to call. Functions in foo.c which are for "internal useonly" and should never be called by clients should be declared static.

• Near the top of foo.c will be the following line which ensures that the functiondefinitions in foo.c see the prototypes in foo.h which ensures the "prototypebefore definition" rule above.#include "foo.h" // show the contents of "foo.h"

// to the compiler at this point

• Any xxx.c file which wishes to call a function defined in foo.c must include thefollowing line to see the prototypes, ensuring the "clients must see prototypes" ruleabove.#include "foo.h"

Page 58: Data Structures

31

#ifAt compile time, there is some space of names defined by the #defines. The #if test canbe used at compile-time to look at those symbols and turn on and off which lines thecompiler uses. The following example depends on the value of the FOO #define symbol.If it is true, then the "aaa" lines (whatever they are) are compiled, and the "bbb" lines areignored. If FOO were 0, then the reverse would be true.

#define FOO 1

...

#if FOOaaaaaa

#elsebbbbbb

#endif

You can use #if 0 ...#endif to effectively comment out areas of code you don'twant to compile, but which you want to keeep in the source file.

Multiple #includes -- #pragma onceThere's a problem sometimes where a .h file is #included into a file more than one timeresulting in compile errors. This can be a serious problem. Because of this, you want toavoid #including .h files in other .h files if at all possible. On the other hand, #including.h files in .c files is fine. If you are lucky, your compiler will support the #pragma oncefeature which automatically prevents a single file from being #included more than once inany one file. This largely solves multiple #include problems.

// foo.h// The following line prevents problems in files which #include "foo.h"#pragma once

<rest of foo.h ...>

AssertArray out of bounds references are an extremely common form of C run-time error. Youcan use the assert() function to sprinkle your code with your own bounds checks. A fewseconds putting in assert statements can save you hours of debugging.

Getting out all the bugs is the hardest and scariest part of writing a large piece ofsoftware. Assert statements are one of the easiest and most effective helpers for thatdifficult phase.

#include <assert.h>#define MAX_INTS 100{

int ints[MAX_INTS];i = foo(<something complicated>); // i should be in bounds,

// but is it really?assert(i>=0); // safety assertionsassert(i<MAX_INTS);

ints[i] = 0;

Page 59: Data Structures

32

Depending on the options specified at compile time, the assert() expressions will be leftin the code for testing, or may be ignored. For that reason, it is important to only putexpressions in assert() tests which do not need to be evaluated for the proper functioningof the program...

int errCode = foo(); // yesassert(errCode == 0);

assert(foo() == 0); // NO, foo() will not be called if// the compiler removes the assert()

Page 60: Data Structures

33

Section 6Advanced Arrays and PointersAdvanced C ArraysIn C, an array is formed by laying out all the elements contiguously in memory. Thesquare bracket syntax can be used to refer to the elements in the array. The array as awhole is referred to by the address of the first element which is also known as the "baseaddress" of the whole array.

{int array[6];

int sum = 0;sum += array[0] + array[1]; // refer to elements using []

}

0 1 2 3 4 5

array

Index

array[0] array[1] array[2] . . .

The array name acts like a pointer to the first element- in this case an (int*).

The programmer can refer to elements in the array with the simple [ ] syntax such asarray[1]. This scheme works by combining the base address of the whole array withthe index to compute the base address of the desired element in the array. It just requiresa little arithmetic. Each element takes up a fixed number of bytes which is known atcompile-time. So the address of element n in the array using 0 based indexing will be atan offset of (n * element_size) bytes from the base address of the whole array.

address of nth element = address_of_0th_element + (n * element_size_in_bytes)

The square bracket syntax [ ] deals with this address arithmetic for you, but it's useful toknow what it's doing. The [ ] takes the integer index, multiplies by the element size, addsthe resulting offset to the array base address, and finally dereferences the resulting pointerto get to the desired element.

{int intArray[6];

intArray[3] = 13;}

Page 61: Data Structures

34

0 1 2 3 4 5

intArray (intArray+3)

Index

Offsetin bytes = n * elem_size

0 4 8 12 16 20

Assume sizeof(int) = 4i.e. Each array element takes up 4 bytes.

13

12 bytes of offset

'+' SyntaxIn a closely related piece of syntax, a + between a pointer and an integer does the sameoffset computation, but leaves the result as a pointer. The square bracket syntax gives thenth element while the + syntax gives a pointer to the nth element.

So the expression (intArray + 3) is a pointer to the integer intArray[3].(intArray + 3) is of type (int*) while intArray[3] is of type int. The twoexpressions only differ by whether the pointer is dereferenced or not. So the expression(intArray + 3) is exactly equivalent to the expression (&(intArray[3])). Infact those two probably compile to exactly the same code. They both represent a pointerto the element at index 3.

Any [] expression can be written with the + syntax instead. We just need to add in thepointer dereference. So intArray[3] is exactly equivalent to *(intArray + 3).For most purposes, it's easiest and most readable to use the [] syntax. Every once in awhile the + is convenient if you needed a pointer to the element instead of the elementitself.

Pointer++ Style -- strcpy()If p is a pointer to an element in an array, then (p+1) points to the next element in thearray. Code can exploit this using the construct p++ to step a pointer over the elements inan array. It doesn't help readability any, so I can't recommend the technique, but you maysee it in code written by others.

(This example was originally inspired by Mike Cleron) There's a library function calledstrcpy(char* destination, char* source) which copies the bytes of a Cstring from one place to another. Below are four different implementations of strcpy()written in order: from most verbose to most cryptic. In the first one, the normallystraightforward while loop is actually sortof tricky to ensure that the terminating nullcharacter is copied over. The second removes that trickiness by moving assignment intothe test. The last two are cute (and they demonstrate using ++ on pointers), but not reallythe sort of code you want to maintain. Among the four, I think strcpy2() is the beststylistically. With a smart compiler, all four will compile to basically the same code withthe same efficiency.

Page 62: Data Structures

35

// Unfortunately, a straight while or for loop won't work.// The best we can do is use a while (1) with the test// in the middle of the loop.void strcpy1(char dest[], const char source[]) {

int i = 0;

while (1) {dest[i] = source[i];if (dest[i] == '\0') break; // we're donei++;

}}

// Move the assignment into the testvoid strcpy2(char dest[], const char source[]) {

int i = 0;

while ((dest[i] = source[i]) != '\0') {i++;

}}

// Get rid of i and just move the pointers.// Relies on the precedence of * and ++.void strcpy3(char dest[], const char source[]){

while ((*dest++ = *source++) != '\0') ;}

// Rely on the fact that '\0' is equivalent to FALSEvoid strcpy4(char dest[], const char source[]){

while (*dest++ = *source++) ;}

Pointer Type EffectsBoth [ ] and + implicitly use the compile time type of the pointer to compute theelement_size which affects the offset arithmetic. When looking at code, it's easy toassume that everything is in the units of bytes.

int *p;

p = p + 12; // at run-time, what does this add to p? 12?

The above code does not add the number 12 to the address in p-- that would increment pby 12 bytes. The code above increments p by 12 ints. Each int probably takes 4 bytes, soat run time the code will effectively increment the address in p by 48. The compilerfigures all this out based on the type of the pointer.

Using casts, the following code really does just add 12 to the address in the pointer p. Itworks by telling the compiler that the pointer points to char instead of int. The size ofchar is defined to be exactly 1 byte (or whatever the smallest addressable unit is on thecomputer). In other words, sizeof(char) is always 1. We then cast the resulting

Page 63: Data Structures

36

(char*) back to an (int*). The programmer is allowed to cast any pointer type toany other pointer type like this to change the code the compiler generates.

p = (int*) ( ((char*)p) + 12);

Arrays and PointersOne effect of the C array scheme is that the compiler does not distinguish meaningfullybetween arrays and pointers-- they both just look like pointers. In the following example,the value of intArray is a pointer to the first element in the array so it's an (int*).The value of the variable intPtr is also (int*) and it is set to point to a single integeri. So what's the difference between intArray and intPtr? Not much as far as thecompiler is concerned. They are both just (int*) pointers, and the compiler is perfectlyhappy to apply the [] or + syntax to either. It's the programmer's responsibility to ensurethat the elements referred to by a [] or + operation really are there. Really its' just thesame old rule that C doesn't do any bounds checking. C thinks of the single integer i asjust a sort of degenerate array of size 1.

{int intArray[6];int *intPtr;int i;

intPtr = &i;

intArray[3] = 13; // okintPtr[0] = 12; // odd, but ok. Changes i.intPtr[3] = 13; // BAD! There is no integer reserved here!

}

Page 64: Data Structures

37

0 1 2 3 4 5

intArray

i

intPtr

(intArray+3)

(intPtr+3)

These bytes exist, but they have not been explicitly reserved. They are the bytes which happen to be adjacent to the memory for i. They are probably being used to store something already, such as a smashed looking smiley face. The 13 just gets blindly written over the smiley face. This error will only be apparent later when the program tries to read the smiley face data.

Index

13

1312

Array Names Are ConstOne subtle distinction between an array and a pointer, is that the pointer which representsthe base address of an array cannot be changed in the code. The array base addressbehaves like a const pointer. The constraint applies to the name of the array where it isdeclared in the code-- the variable ints in the example below.

{int ints[100]int *p;int i;

ints = NULL; // NO, cannot change the base addr ptrints = &i; // NOints = ints + 1; // NOints++; // NO

p = ints; // OK, p is a regular pointer which can be changed// here it is getting a copy of the ints pointer

p++; // OK, p can still be changed (and ints cannot)p = NULL; // OKp = &i; // OK

foo(ints); // OK (possible foo definitions are below)}

Page 65: Data Structures

38

Array parameters are passed as pointers. The following two definitions of foo lookdifferent, but to the compiler they mean exactly the same thing. It's preferable to usewhichever syntax is more accurate for readability. If the pointer coming in really is thebase address of a whole array, then use [ ].

void foo(int arrayParam[]) {arrayParam = NULL; // Silly but valid. Just changes the local pointer

}

void foo(int *arrayParam) {arrayParam = NULL; // ditto

}

Heap MemoryC gives programmers the standard sort of facilities to allocate and deallocate dynamicheap memory. A word of warning: writing programs which manage their heap memory isnotoriously difficult. This partly explains the great popularity of languages such as Javaand Perl which handle heap management automatically. These languages take over a taskwhich has proven to be extremely difficult for the programmer. As a result Perl and Javaprograms run a little more slowly, but they contain far fewer bugs. (For a detaileddiscussion of heap memory see http://cslibrary.stanford.edu/102/, Pointers and Memory.)

C provides access to the heap features through library functions which any C code cancall. The prototypes for these functions are in the file <stdlib.h>, so any code whichwants to call these must #include that header file. The three functions of interest are...

void* malloc(size_t size) Request a contiguous block of memoryof the given size in the heap. malloc() returns a pointer to the heap block or NULL ifthe request could not be satisfied. The type size_t is essentially an unsignedlong which indicates how large a block the caller would like measured in bytes.Because the block pointer returned by malloc() is a void* (i.e. it makes no claimabout the type of its pointee), a cast will probably be required when storing the void*pointer into a regular typed pointer.

void free(void* block) The mirror image of malloc() -- free takes apointer to a heap block earlier allocated by malloc() and returns that block to the heapfor re-use. After the free(), the client should not access any part of the block orassume that the block is valid memory. The block should not be freed a second time.

void* realloc(void* block, size_t size); Take an existing heapblock and try to relocate it to a heap block of the given size which may be larger orsmaller than the original size of the block. Returns a pointer to the new block, orNULL if the relocation was unsuccessful. Remember to catch and examine the returnvalue of realloc() -- it is a common error to continue to use the old block pointer.Realloc() takes care of moving the bytes from the old block to the new block.Realloc() exists because it can be implemented using low-level features which makeit more efficient than C code the client could write.

Memory ManagementAll of a program's memory is deallocated automatically when the it exits, so a programonly needs to use free() during execution if it is important for the program to recycle itsmemory while it runs -- typically because it uses a lot of memory or because it runs for a

Page 66: Data Structures

39

long time. The pointer passed to free() must be exactly the pointer which was originallyreturned by malloc() or realloc(), not just a pointer into somewhere within the heap block.

Dynamic ArraysSince arrays are just contiguous areas of bytes, you can allocate your own arrays in theheap using malloc(). The following code allocates two arrays of 1000 ints-- one in thestack the usual "local" way, and one in the heap using malloc(). Other than the differentallocations, the two are syntactically similar in use.

{int a[1000];

int *b;b = (int*) malloc( sizeof(int) * 1000);assert(b != NULL); // check that the allocation succeeded

a[123] = 13; // Just use good ol' [] to access elementsb[123] = 13; // in both arrays.

free(b);}

Although both arrays can be accessed with [ ], the rules for their maintenance are verydifferent....

Advantages of being in the heap• Size (in this case 1000) can be defined at run time. Not so for an array like "a".

• The array will exist until it is explicitly deallocated with a call to free().

• You can change the size of the array at will at run time using realloc(). The followingchanges the size of the array to 2000. Realloc() takes care of copying over the oldelements.

...b = realloc(b, sizeof(int) * 2000);assert(b != NULL);

Disadvantages of being in the heap• You have to remember to allocate the array, and you have to get it right.

• You have to remember to deallocate it exactly once when you are done with it, and youhave to get that right.

• The above two disadvantages have the same basic profile: if you get them wrong, yourcode still looks right. It compiles fine. It even runs for small cases, but for some inputcases it just crashes unexpectedly because random memory is getting overwrittensomewhere like the smiley face. This sort of "random memory smasher" bug can be areal ordeal to track down.

Page 67: Data Structures

40

Dynamic StringsThe dynamic allocation of arrays works very well for allocating strings in the heap. Theadvantage of heap allocating a string is that the heap block can be just big enough to storethe actual number of characters in the string. The common local variable technique suchas char string[1000]; allocates way too much space most of the time, wastingthe unused bytes, and yet fails if the string ever gets bigger than the variable's fixed size.

#include <string.h>

/* Takes a c string as input, and makes a copy of that string in the heap. The caller takes over ownership of the new string and is responsible for freeing it.*/char* MakeStringInHeap(const char* source) {

char* newString;

newString = (char*) malloc(strlen(source) + 1); // +1 for the '\0'assert(newString != NULL);strcpy(newString, source);return(newString);

}

Page 68: Data Structures

41

Section 7Details and Library FunctionsPrecedence and Associativity

function-call() [] -> . L to R

! ~ ++ -- + - *(ptr deref) sizeof &(addr of) R to L(all unary ops are the same)

* / % L to R(the top tier arithmetic binary ops)

+ - L to R(second tier arithmetic binary ops)

< <= > >= L to R

== != L to R

in order: & ^ | && || L to R(note that bitwise comes before boolean)

= and all its variants R to L

, (comma) . L to R

A combinations which never works right without parens: *structptr.fieldYou have to write it as (*structptr).field or structptr->field

Standard Library FunctionsMany basic housekeeping funcions are available to a C program in form of standardlibrary functions. To call these, a program must #include the appropriate .h file. Mostcompilers link in the standard library code by default. The functions listed in the nextsection are the most commonly used ones, but there are many more which are not listedhere.

stdio.h file input and output

ctype.h character tests

string.h string operations

math.h mathematical functions such as sin() and cos()

stdlib.h utility functions such as malloc() and rand()

assert.h the assert() debugging macro

stdarg.h support for functions with variable numbers of arguments

setjmp.h support for non-local flow control jumps

signal.h support for exceptional condition signals

time.h date and time

Page 69: Data Structures

42

limits.h, float.h constants which define type range values such as INT_MAX

stdio.hStdio.h is a very common file to #include -- it includes functions to print and read stringsfrom files and to open and close files in the file system.

FILE* fopen(const char* fname, const char* mode);Open a file named in the filesystem and return a FILE* for it. Mode = "r" read,"w"write,"a"append, returns NULL on error. The standard files stdout, stdin,stderr are automatically opened and closed for you by the system.

int fclose(FILE* file);Close a previously opened file. Returns EOF on error. The operating system closes allof a program's files when it exits, but it's tidy to do it beforehand. Also, there istypically a limit to the number of files which a program may have opensimultaneously.

int fgetc(FILE* in);Read and return the next unsigned char out of a file, or EOF if the file has beenexhausted. (detail) This and other file functions return ints instead of a chars becausethe EOF constant they potentially is not a char, but is an int. getc() is an alternate,faster version implemented as a macro which may evaluate the FILE* expressionmore than once.

char* fgets(char* dest, int n, FILE* in)Reads the next line of text into a string supplied by the caller. Reads at most n-1characters from the file, stopping at the first '\n' character. In any case, the string is '\0'terminated. The '\n' is included in the string. Returns NULL on EOF or error.

int fputc(int ch, FILE* out);Write the char to the file as an unsigned char. Returns ch, or EOF on err. putc() is analternate, faster version implemented as a macro which may evaluate the FILE*expression more than once.

int ungetc(int ch, FILE* in);Push the most recent fgetc() char back onto the file. EOF may not be pushed back.Returns ch or EOF on error.

int printf(const char* format_string, ...);Prints a string with values possibly inserted into it to standard output. Takes a variablenumber of arguments -- first a format string followed by a number of matchingarguments. The format string contains text mixed with % directives which markthings to be inserted in the output. %d = int, %Ld=long int, %s=string, %f=double,%c=char. Every % directive must have a matching argument of the correct type afterthe format string. Returns the number of characters written, or negative on error. Ifthe percent directives do not match the number and type of arguments, printf() tendsto crash or otherwise do the wrong thing at run time. fprintf() is a variant which takesan additional FILE* argument which specifies the file to print to. Examples...printf("hello\n"); prints: helloprintf("hello %d there %d\n", 13, 1+1); prints: hello 13 there 2printf("hello %c there %d %s\n", 'A', 42, "ok"); prints: hello A there 42 ok

Page 70: Data Structures

43

int scanf(const char* format, ...)Opposite of printf() -- reads characters from standard input trying to match elementsin the format string. Each percent directive in the format string must have a matchingpointer in the argument list which scanf() uses to store the values it finds. scanf()skips whitespace as it tries to read in each percent directive. Returns the number ofpercent directives processed successfully, or EOF on error. scanf() is famouslysensitive to programmer errors. If scanf() is called with anything but the correctpointers after the format string, it tends to crash or otherwise do the wrong thing atrun time. sscanf() is a variant which takes an additional initial string from which itdoes its reading. fscanf() is a variant which takes an additional initial FILE* fromwhich it does its reading. Example...{

int num;char s1[1000];char s2[1000];

scanf("hello %d %s %s", &num, s1, s2);}Looks for the word "hello" followed by a number and two words (all separated bywhitespace). scanf() uses the pointers &num, s1, and s2 to store what it finds into thelocal variables.

ctype.hctype.h includes macros for doing simple tests and operations on characters

isalpha(ch) // ch is an upper or lower case letter

islower(ch), isupper(ch) // same as above, but upper/lower specific

isspace(ch) // ch is a whitepace character such as tab, space, newline, etc.

isdigit(ch) // digit such as '0'..'9'

toupper(ch), tolower(ch) // Return the lower or upper case version of aalphabetic character, otherwise pass it through unchanged.

Page 71: Data Structures

44

string.hNone of these string routines allocate memory or check that the passed in memory is theright size. The caller is responsible for making sure there is "enough" memory for theoperation. The type size_t is an unsigned integer wide enough for the computer'saddress space -- most likely an unsigned long.

size_t strlen(const char* string);Return the number of chars in a C string. EG strlen("abc")==3

char* strcpy(char* dest, const char* source);Copy the characters from the source string to the destination string.

size_t strlcpy(char* dest, const char* source,size_t dest_size);

Like strcpy(), but knows the size of the dest. Truncates if necessary. Use this to avoidmemory errors and buffer-overflow security problems. This function is not asstandard as strcpy(), but most sytems have it. Do not use the old strncpy() function --it is difficult to use correctly.

char *strcat(char* dest, const char* source);Append the characters from the source string to the end of destination string. (There isa non-standard strlcat() variant that takes the size of the dest as third argument.)

int strcmp(const char* a, const char* b);Compare two strings and return an int which encodes their ordering. zero:a==b,negative:a<b, positive:a>b. It is a common error to think of the result of strcmp() asbeing boolean true if the strings are equal which is, unfortunately, exactly backwards.

char* strchr(const char* searchIn, char ch);Search the given string for the first occurence of the given character. Returns apointer to the character, or NULL if none is found.

char* strstr(const char* searchIn, const char* searchFor);Similar to strchr(), but searches for an entire string instead of a single character. Thesearch is case sensitive.

void* memcpy(void* dest, const void* source, size_t n);Copy the given number of bytes from the source to the destination. The source anddestination must not overlap. This may be implemented in a specialized but highlyoptimized way for a particular computer.

void* memmove(void* dest, const void* source, size_t n);Similar to memcpy() but allows the areas to overlap. This probably runs slightlyslower than memcpy().

Page 72: Data Structures

45

stdlib.hint rand();

Returns a pseudo random integer in the range 0..RAND_MAX (limits.h) which is atleast 32767.

void srand(unsigned int seed);The sequence of random numbers returned by rand() is initially controlled by a global"seed" variable. srand() sets this seed which, by default, starts with the value 1. Passthe expression time(NULL) (time.h) to set the seed to a value based on the currenttime to ensure that the random sequence is different from one run to the next.

void* malloc(size_t size);Allocate a heap block of the given size in bytes. Returns a pointer to the block orNULL on failure. A cast may be required to store the void* pointer into a regulartyped pointer. [ed: see the Heap Allocation section above for the longer discussion ofmalloc(), free(), and realloc()]

void free(void* block);Opposite of malloc(). Returns a previous malloc block to the system for reuse

void* realloc(void* block, size_t size);Resize an existing heap block to the new size. Takes care of copying bytes from theold block to the new. Returns the new base address of the heap block. It is a commonerror to forget to catch the return value from realloc(). Returns NULL if the resizeoperation was not possible.

void exit(int status);Halt and exit the program and pass a condition int back to the operating sytem. Pass 0to signal normal program termination, non-zero otherwise.

void* bsearch(const void* key, const void* base, size_t len,size_t elem_size, <compare_function>);Do a binary search in an array of elements. The last argument is a function whichtakes pointers to the two elements to compare. Its prototype should be:int compare(const void* a, const void* b);, and it should return 0, -1, or 1 as strcmp()does. Returns a pointer to a found element, or NULL otherwise. Note that strcmp()itself cannot be used directly as a compare function for bsearch() on an array of char*strings because strcmp() takes char* arguments and bsearch() will need a comparatorthat takes pointers to the array elements -- char**.

void qsort(void* base, size_t len, size_t elem_size,<compare_function>);Sort an array of elements. Takes a function pointer just like besearch().

Revision History11/1998 -- original major version. Based on my old C handout for CS107. Thanks to JonBecker for proofreading and Mike Cleron for the original inspiration.

Revised 4/2003 with many helpful typo and other suggestions from Negar Shamma andA. P. Garcia

Page 73: Data Structures

Linked ListBasics

By Nick Parlante Copyright © 1998-2001, Nick Parlante

AbstractThis document introduces the basic structures and techniques for building linked listswith a mixture of explanations, drawings, sample code, and exercises. The material isuseful if you want to understand linked lists or if you want to see a realistic, appliedexample of pointer-intensive code. A separate document, Linked List Problems(http://cslibrary.stanford.edu/105/), presents 18 practice problems covering a wide rangeof difficulty.

Linked lists are useful to study for two reasons. Most obviously, linked lists are a datastructure which you may want to use in real programs. Seeing the strengths andweaknesses of linked lists will give you an appreciation of the some of the time, space,and code issues which are useful to thinking about any data structures in general.

Somewhat less obviously, linked lists are great way to learn about pointers. In fact, youmay never use a linked list in a real program, but you are certain to use lots of pointers.Linked list problems are a nice combination of algorithms and pointer manipulation.Traditionally, linked lists have been the domain where beginning programmers get thepractice to really understand pointers.

AudienceThe article assumes a basic understanding of programming and pointers. The article usesC syntax for its examples where necessary, but the explanations avoid C specifics asmuch as possible — really the discussion is oriented towards the important concepts ofpointer manipulation and linked list algorithms.

Other Resources• Link List Problems (http://cslibrary.stanford.edu/105/) Lots of linked

list problems, with explanations, answers, and drawings. The "problems"article is a companion to this "explanation" article.

• Pointers and Memory (http://cslibrary.stanford.edu/102/) Explains allabout how pointers and memory work. You need some understanding ofpointers and memory before you can understand linked lists.

• Essential C (http://cslibrary.stanford.edu/101/) Explains all the basicfeatures of the C programming language.

This is document #103, Linked List Basics, in the Stanford CS Education Library. Thisand other free educational materials are available at http://cslibrary.stanford.edu/. Thisdocument is free to be used, reproduced, or sold so long as this notice is clearlyreproduced at its beginning.

Page 74: Data Structures

2

ContentsSection 1 — Basic List Structures and Code 2Section 2 — Basic List Building 11Section 3 — Linked List Code Techniques 17Section 3 — Code Examples 22

EditionOriginally 1998 there was just one "Linked List" document that included a basicexplanation and practice problems. In 1999, it got split into two documents: #103 (thisdocument) focuses on the basic introduction, while #105 is mainly practice problems.This 4-12-2001 edition represents minor edits on the 1999 edition.

DedicationThis document is distributed for free for the benefit and education of all. That a personseeking knowledge should have the opportunity to find it. Thanks to Stanford and myboss Eric Roberts for supporing me in this project. Best regards, Nick [email protected]

Section 1 —Linked List BasicsWhy Linked Lists?Linked lists and arrays are similar since they both store collections of data. Theterminology is that arrays and linked lists store "elements" on behalf of "client" code. Thespecific type of element is not important since essentially the same structure works tostore elements of any type. One way to think about linked lists is to look at how arrayswork and think about alternate approaches.

Array ReviewArrays are probably the most common data structure used to store collections ofelements. In most languages, arrays are convenient to declare and the provide the handy[ ] syntax to access any element by its index number. The following example shows sometypical array code and a drawing of how the array might look in memory. The codeallocates an array int scores[100], sets the first three elements set to contain thenumbers 1, 2, 3 and leaves the rest of the array uninitialized...

void ArrayTest() {int scores[100];

// operate on the elements of the scores array...scores[0] = 1;scores[1] = 2;scores[2] = 3;

}

Page 75: Data Structures

3

Here is a drawing of how the scores array might look like in memory. The key point isthat the entire array is allocated as one block of memory. Each element in the array getsits own space in the array. Any element can be accessed directly using the [ ] syntax.

0

scores

index 1 2 99

1 2 3 23142

3

-3451

Once the array is set up, access to any element is convenient and fast with the [ ]operator. (Extra for experts) Array access with expressions such as scores[i] isalmost always implemented using fast address arithmetic: the address of an element iscomputed as an offset from the start of the array which only requires one multiplicationand one addition.

The disadvantages of arrays are...

1) The size of the array is fixed — 100 elements in this case. Most often thissize is specified at compile time with a simple declaration such as in theexample above . With a little extra effort, the size of the array can bedeferred until the array is created at runtime, but after that it remains fixed.(extra for experts) You can go to the trouble of dynamically allocating anarray in the heap and then dynamically resizing it with realloc(), but thatrequires some real programmer effort.

2) Because of (1), the most convenient thing for programmers to do is toallocate arrays which seem "large enough" (e.g. the 100 in the scoresexample). Although convenient, this strategy has two disadvantages: (a)most of the time there are just 20 or 30 elements in the array and 70% ofthe space in the array really is wasted. (b) If the program ever needs toprocess more than 100 scores, the code breaks. A surprising amount ofcommercial code has this sort of naive array allocation which wastes spacemost of the time and crashes for special occasions. (Extra for experts) Forrelatively large arrays (larger than 8k bytes), the virtual memory systemmay partially compensate for this problem, since the "wasted" elementsare never touched.

3) (minor) Inserting new elements at the front is potentially expensivebecause existing elements need to be shifted over to make room.

Linked lists have their own strengths and weaknesses, but they happen to be strong wherearrays are weak. The array's features all follow from its strategy of allocating the memoryfor all its elements in one block of memory. Linked lists use an entirely different strategy.As we will see, linked lists allocate memory for each element separately and only whennecessary.

Pointer RefresherHere is a quick review of the terminology and rules for pointers. The linked list code tofollow will depend on these rules. (For much more detailed coverage of pointers andmemory, see Pointers and Memory, http://cslibrary.stanford.edu/102/).

Page 76: Data Structures

4

• Pointer/Pointee A "pointer" stores a reference to another variablesometimes known as its "pointee". Alternately, a pointer may be set to thevalue NULL which encodes that it does not currently refer to a pointee. (InC and C++ the value NULL can be used as a boolean false).

• Dereference The dereference operation on a pointer accesses its pointee.A pointer may only be dereferenced after it has been set to refer to aspecific pointee. A pointer which does not have a pointee is "bad" (below)and should not be dereferenced.

• Bad Pointer A pointer which does not have an assigned a pointee is"bad" and should not be dereferenced. In C and C++, a dereference on abad sometimes crashes immediately at the dereference and sometimesrandomly corrupts the memory of the running program, causing a crash orincorrect computation later. That sort of random bug is difficult to trackdown. In C and C++, all pointers start out with bad values, so it is easyto use bad pointer accidentally. Correct code sets each pointer to have agood value before using it. Accidentally using a pointer when it is bad isthe most common bug in pointer code. In Java and other runtime orientedlanguages, pointers automatically start out with the NULL value, sodereferencing one is detected immediately. Java programs are much easierto debug for this reason.

• Pointer assignment An assignment operation between two pointers likep=q; makes the two pointers point to the same pointee. It does not copythe pointee memory. After the assignment both pointers will point to thesame pointee memory which is known as a "sharing" situation.

• malloc() malloc() is a system function which allocates a block ofmemory in the "heap" and returns a pointer to the new block. Theprototype for malloc() and other heap functions are in stdlib.h. Theargument to malloc() is the integer size of the block in bytes. Unlike local("stack") variables, heap memory is not automatically deallocated whenthe creating function exits. malloc() returns NULL if it cannot fulfill therequest. (extra for experts) You may check for the NULL case withassert() if you wish just to be safe. Most modern programming systemswill throw an exception or do some other automatic error handling in theirmemory allocator, so it is becoming less common that source code needsto explicitly check for allocation failures.

• free() free() is the opposite of malloc(). Call free() on a block of heapmemory to indicate to the system that you are done with it. The argumentto free() is a pointer to a block of memory in the heap — a pointer whichsome time earlier was obtained via a call to malloc().

What Linked Lists Look LikeAn array allocates memory for all its elements lumped together as one block of memory.In contrast, a linked list allocates space for each element separately in its own block ofmemory called a "linked list element" or "node". The list gets is overall structure by usingpointers to connect all its nodes together like the links in a chain.

Each node contains two fields: a "data" field to store whatever element type the list holdsfor its client, and a "next" field which is a pointer used to link one node to the next node.Each node is allocated in the heap with a call to malloc(), so the node memory continuesto exist until it is explicitly deallocated with a call to free(). The front of the list is a

Page 77: Data Structures

5

pointer to the first node. Here is what a list containing the numbers 1, 2, and 3 might looklike...

The Drawing Of List {1, 2, 3}

Stack Heap

1 2 3

A “head” pointer local to BuildOneTwoThree() keeps the whole list by storing a pointer to the first node.

Each node stores one data element (int in this example).

Each node stores one next pointer.

The overall list is built by connecting the nodes together by their next pointers. The nodes are all allocated in the heap.

The next field of the last node is NULL.

head

BuildOneTwoThree()

This drawing shows the list built in memory by the function BuildOneTwoThree() (thefull source code for this function is below). The beginning of the linked list is stored in a"head" pointer which points to the first node. The first node contains a pointer to thesecond node. The second node contains a pointer to the third node, ... and so on. The lastnode in the list has its .next field set to NULL to mark the end of the list. Code can accessany node in the list by starting at the head and following the .next pointers. Operationstowards the front of the list are fast while operations which access node farther down thelist take longer the further they are from the front. This "linear" cost to access a node isfundamentally more costly then the constant time [ ] access provided by arrays. In thisrespect, linked lists are definitely less efficient than arrays.

Drawings such as above are important for thinking about pointer code, so most of theexamples in this article will associate code with its memory drawing to emphasize thehabit. In this case the head pointer is an ordinary local pointer variable, so it is drawnseparately on the left to show that it is in the stack. The list nodes are drawn on the rightto show that they are allocated in the heap.

The Empty List — NULLThe above is a list pointed to by head is described as being of "length three" since it ismade of three nodes with the .next field of the last node set to NULL. There needs to besome representation of the empty list — the list with zero nodes. The most commonrepresentation chosen for the empty list is a NULL head pointer. The empty list case isthe one common weird "boundary case" for linked list code. All of the code presented inthis article works correctly for the empty list case, but that was not without some effort.When working on linked list code, it's a good habit to remember to check the empty listcase to verify that it works too. Sometimes the empty list case works the same as all thecases, but sometimes it requires some special case code. No matter what, it's a good caseto at least think about.

Page 78: Data Structures

6

Linked List Types: Node and PointerBefore writing the code to build the above list, we need two data types...

• Node The type for the nodes which will make up the body of the list.These are allocated in the heap. Each node contains a single client dataelement and a pointer to the next node in the list. Type: struct node

struct node {int data;struct node* next;

};

• Node Pointer The type for pointers to nodes. This will be the type of thehead pointer and the .next fields inside each node. In C and C++, noseparate type declaration is required since the pointer type is just the nodetype followed by a '*'. Type: struct node*

BuildOneTwoThree() FunctionHere is simple function which uses pointer operations to build the list {1, 2, 3}. Thememory drawing above corresponds to the state of memory at the end of this function.This function demonstrates how calls to malloc() and pointer assignments (=) work tobuild a pointer structure in the heap.

/* Build the list {1, 2, 3} in the heap and store its head pointer in a local stack variable. Returns the head pointer to the caller.*/struct node* BuildOneTwoThree() {

struct node* head = NULL;struct node* second = NULL;struct node* third = NULL;

head = malloc(sizeof(struct node)); // allocate 3 nodes in the heapsecond = malloc(sizeof(struct node));third = malloc(sizeof(struct node));

head->data = 1; // setup first nodehead->next = second; // note: pointer assignment rule

second->data = 2; // setup second nodesecond->next = third;

third->data = 3; // setup third linkthird->next = NULL;

// At this point, the linked list referenced by "head"// matches the list in the drawing.return head;

}

ExerciseQ: Write the code with the smallest number of assignments (=) which will build theabove memory structure. A: It requires 3 calls to malloc(). 3 int assignments (=) to setupthe ints. 4 pointer assignments to setup head and the 3 next fields. With a little clevernessand knowledge of the C language, this can all be done with 7 assignment operations (=).

Page 79: Data Structures

7

Length() FunctionThe Length() function takes a linked list and computes the number of elements in the list.Length() is a simple list function, but it demonstrates several concepts which will be usedin later, more complex list functions...

/* Given a linked list head pointer, compute and return the number of nodes in the list.*/int Length(struct node* head) {

struct node* current = head;int count = 0;

while (current != NULL) {count++;current = current->next;

}

return count;}

There are two common features of linked lists demonstrated in Length()...

1) Pass The List By Passing The Head PointerThe linked list is passed in to Length() via a single head pointer. The pointer is copiedfrom the caller into the "head" variable local to Length(). Copying this pointer does notduplicate the whole list. It only copies the pointer so that the caller and Length() bothhave pointers to the same list structure. This is the classic "sharing" feature of pointercode. Both the caller and length have copies of the head pointer, but they share thepointee node structure.

2) Iterate Over The List With A Local PointerThe code to iterate over all the elements is a very common idiom in linked list code....

struct node* current = head;while (current != NULL) {

// do something with *current node

current = current->next;}

The hallmarks of this code are...

1) The local pointer, current in this case, starts by pointing to the samenode as the head pointer with current = head;. When the functionexits, current is automatically deallocated since it is just an ordinarylocal, but the nodes in the heap remain.

2) The while loop tests for the end of the list with (current != NULL).This test smoothly catches the empty list case — current will be NULLon the first iteration and the while loop will just exit before the firstiteration.

3) At the bottom of the while loop, current = current->next;advances the local pointer to the next node in the list. When there are nomore links, this sets the pointer to NULL. If you have some linked list

Page 80: Data Structures

8

code which goes into an infinite loop, often the problem is that step (3) hasbeen forgotten.

Calling Length()Here's some typical code which calls Length(). It first calls BuildOneTwoThree() to makea list and store the head pointer in a local variable. It then calls Length() on the list andcatches the int result in a local variable.

void LengthTest() {struct node* myList = BuildOneTwoThree();

int len = Length(myList); // results in len == 3}

Memory DrawingsThe best way to design and think about linked list code is to use a drawing to see how thepointer operations are setting up memory. There are drawings below of the state ofmemory before and during the call to Length() — take this opportunity to practicelooking at memory drawings and using them to think about pointer intensive code. Youwill be able to understand many of the later, more complex functions only by makingmemory drawings like this on your own.

Start with the Length() and LengthTest() code and a blank sheet of paper. Trace throughthe execution of the code and update your drawing to show the state of memory at eachstep. Memory drawings should distinguish heap memory from local stack memory.Reminder: malloc() allocates memory in the heap which is only be deallocated bydeliberate calls to free(). In contrast, local stack variables for each function areautomatically allocated when the function starts and deallocated when it exits. Ourmemory drawings show the caller local stack variables above the callee, but anyconvention is fine so long as you realize that the caller and callee are separate. (Seecslibrary.stanford.edu/102/, Pointers and Memory, for an explanation of how localmemory works.)

Page 81: Data Structures

9

Drawing 1 : Before Length()Below is the state of memory just before the call to Length() in LengthTest() above.BuildOneTwoThree() has built the {1, 2, 3} list in the heap and returned the head pointer.The head pointer has been caught by the caller and stored in its local variable myList.The local variable len has a random value — it will only be given the value 3 when thencall to Length() returns.

len -14231

1 2 3

myList

The head pointer for the list is stored in the local variable myList.

Stack Heap

LengthTest()

Nodes allocated in the heap via calls to malloc() in BuildOneTwoThree().

len has a random value until it is assigned.

Page 82: Data Structures

10

Drawing 2: Mid LengthHere is the state of memory midway through the execution of Length(). Length()'s localvariables head and current have been automatically allocated. The current pointerstarted out pointing to the first node, and then the first iteration of the while loopadvanced it to point to the second node.

Stack Heap

1 2 3

myList

LengthTest()

head

Length()

current

len -14231

Notice how the local variables in Length() (head and current) are separate from thelocal variables in LengthTest() (myList and len). The local variables head andcurrent will be deallocated (deleted) automatically when Length() exits. This is fine— the heap allocated links will remain even though stack allocated pointers which werepointing to them have been deleted.

ExerciseQ: What if we said head = NULL; at the end of Length() — would that mess up themyList variable in the caller? A: No. head is a local which was initialized with a copyof the actual parameter, but changes do not automatically trace back to the actualparameter. Changes to the local variables in one function do not affect the locals ofanother function.

ExerciseQ: What if the passed in list contains no elements, does Length() handle that caseproperly? A: Yes. The representation of the empty list is a NULL head pointer. TraceLength() on that case to see how it handles it.

Page 83: Data Structures

11

Section 2 —List BuildingBuildOneTwoThree() is a fine as example of pointer manipulation code, but it's not ageneral mechanism to build lists. The best solution will be an independent function whichadds a single new node to any list. We can then call that function as many times as wewant to build up any list. Before getting into the specific code, we can identify the classic3-Step Link In operation which adds a single node to the front of a linked list. The 3 stepsare...

1) Allocate Allocate the new node in the heap and set its .data towhatever needs to be stored.struct node* newNode;newNode = malloc(sizeof(struct node));newNode->data = data_client_wants_stored;

2) Link Next Set the .next pointer of the new node to point to the currentfirst node of the list. This is actually just a pointer assignment —remember: "assigning one pointer to another makes them point to the samething."newNode->next = head;

3) Link Head Change the head pointer to point to the new node, so it isnow the first node in the list.head = newNode;

3-Step Link In CodeThe simple LinkTest() function demonstrates the 3-Step Link In...

void LinkTest() {struct node* head = BuildTwoThree(); // suppose this builds the {2, 3} liststruct node* newNode;

newNode= malloc(sizeof(struct node)); // allocatenewNode->data = 1;

newNode->next = head; // link next

head = newNode; // link head

// now head points to the list {1, 2, 3}}

Page 84: Data Structures

12

3-Step Link In DrawingThe drawing of the above 3-Step Link like (overwritten pointer values are in gray)...

1

2 3

head

Insert this node with the 3-Step Link In:1) Allocate the new node2) Set its .next to the old head3) Set head to point to the new nodeBefore: list = {2, 3}After: list = {1, 2, 3}

Stack Heap

LinkTest()

newNode

Push() FunctionWith the 3-Step Link In in mind, the problem is to write a general function which adds asingle node to head end of any list. Historically, this function is called "Push()" sincewe're adding the link to the head end which makes the list look a bit like a stack.Alternately it could be called InsertAtFront(), but we'll use the name Push().

WrongPush()Unfortunately Push() written in C suffers from a basic problem: what should be theparameters to Push()? This is, unfortunately, a sticky area in C. There's a nice, obviousway to write Push() which looks right but is wrong. Seeing exactly how it doesn't workwill provide an excuse for more practice with memory drawings, motivate the correctsolution, and just generally make you a better programmer....

void WrongPush(struct node* head, int data) {struct node* newNode = malloc(sizeof(struct node));

newNode->data = data;newNode->next = head;head = newNode; // NO this line does not work!

}

void WrongPushTest() {List head = BuildTwoThree();

WrongPush(head, 1); // try to push a 1 on front -- doesn't work}

Page 85: Data Structures

13

WrongPush() is very close to being correct. It takes the correct 3-Step Link In and puts itan almost correct context. The problem is all in the very last line where the 3-Step LinkIn dictates that we change the head pointer to refer to the new node. What does the linehead = newNode; do in WrongPush()? It sets a head pointer, but not the right one. Itsets the variable named head local to WrongPush(). It does not in any way change thevariable named head we really cared about which is back in the caller WrontPushTest().

ExerciseMake the memory drawing tracing WrongPushTest() to see how it does not work. Thekey is that the line head = newElem; changes the head local to WrongPush() notthe head back in WrongPushTest(). Remember that the local variables for WrongPush()and WrongPushTest() are separate (just like the locals for LengthTest() and Length() inthe Length() example above).

Reference Parameters In CWe are bumping into a basic "feature" of the C language that changes to local parametersare never reflected back in the caller's memory. This is a traditional tricky area of Cprogramming. We will present the traditional "reference parameter" solution to thisproblem, but you may want to consult another C resource for further information. (SeePointers and Memory (http://cslibrary.stanford.edu/102/) for a detailed explanation ofreference parameters in C and C++.)

We need Push() to be able to change some of the caller's memory — namely the headvariable. The traditional method to allow a function to change its caller's memory is topass a pointer to the caller's memory instead of a copy. So in C, to change an int in thecaller, pass a int* instead. To change a struct fraction, pass a structfraction* intead. To change an X, pass an X*. So in this case, the value we want tochange is struct node*, so we pass a struct node** instead. The two stars(**) are a little scary, but really it's just a straight application of the rule. It just happensthat the value we want to change already has one star (*), so the parameter to change ithas two (**). Or put another way: the type of the head pointer is "pointer to a structnode." In order to change that pointer, we need to pass a pointer to it, which will be a"pointer to a pointer to a struct node".

Instead of defining WrongPush(struct node* head, int data); we definePush(struct node** headRef, int data);. The first form passes a copy ofthe head pointer. The second, correct form passes a pointer to the head pointer. The ruleis: to modify caller memory, pass a pointer to that memory. The parameter has the word"ref" in it as a reminder that this is a "reference" (struct node**) pointer to thehead pointer instead of an ordinary (struct node*) copy of the head pointer.

Page 86: Data Structures

14

Correct Push() CodeHere are Push() and PushTest() written correctly. The list is passed via a pointer to thehead pointer. In the code, this amounts to use of '&' on the parameter in the caller and useof '*' on the parameter in the callee. Inside Push(), the pointer to the head pointer isnamed "headRef" instead of just "head" as a reminder that it is not just a simple headpointer..

/* Takes a list and a data value. Creates a new link with the given data and pushes it onto the front of the list. The list is not passed in by its head pointer. Instead the list is passed in as a "reference" pointer to the head pointer -- this allows us to modify the caller's memory.*/void Push(struct node** headRef, int data) {

struct node* newNode = malloc(sizeof(struct node));

newNode->data = data;newNode->next = *headRef; // The '*' to dereferences back to the real head*headRef = newNode; // ditto

}

void PushTest() {struct node* head = BuildTwoThree();// suppose this returns the list {2, 3}

Push(&head, 1); // note the &Push(&head, 13);

// head is now the list {13, 1, 2, 3}}

Page 87: Data Structures

15

Correct Push() DrawingHere is a drawing of memory just before the first call to Push() exits. The original valueof the head pointer is in gray. Notice how the headRef parameter inside Push() pointsback to the real head pointer back in PushTest(). Push() uses *headRef to access andchange the real head pointer.

1

2 3

head

PushTest()

This node inserted by the call to Push(). Push follows its headRef to modify the real head.

Stack Heap

headRef

Push()

data 1

The key point: the headRef parameter to Push() is not the real head of the list. It is a pointer to the real head of the list back in the caller’s memory space.

ExerciseThe above drawing shows the state of memory at the end of the first call to Push() inPushTest(). Extend the drawing to trace through the second call to Push(). The resultshould be that the list is left with elements {13, 1, 2, 3}.

ExerciseThe following function correctly builds a three element list using nothing but Push().Make the memory drawing to trace its execution and show the final state of its list. Thiswill also demonstrate that Push() works correctly for the empty list case.

void PushTest2() {struct node* head = NULL; // make a list with no elements

Push(&head, 1);Push(&head, 2);Push(&head, 3);

// head now points to the list {3, 2, 1}}

What About C++?(Just in case you were curious) C++ has its built in "& argument" feature to implementreference parameters for the programmer. The short story is, append an '&' to the type ofa parameter, and the compiler will automatically make the parameter operate by referencefor you. The type of the argument is not disturbed by this — the types continue to act as

Page 88: Data Structures

16

they appear in the source, which is the most convenient for the programmer. So In C++,Push() and PushTest() look like...

/* Push() in C++ -- we just add a '&' to the right hand side of the head parameter type, and the compiler makes that parameter work by reference. So this code changes the caller's memory, but no extra uses of '*' are necessary -- we just access "head" directly, and the compiler makes that change reference back to the caller.*/void Push(struct node*& head, int data) {

struct node* newNode = malloc(sizeof(struct node));

newNode->data = data;newNode->next = head; // No extra use of * necessary on head -- the compilerhead = newNode; // just takes care of it behind the scenes.

}

void PushTest() {struct node* head = BuildTwoThree();// suppose this returns the list {2, 3}

Push(head, 1); // No extra use & necessary -- the compiler takesPush(head, 13); // care of it here too. Head is being changed by

// these calls.

// head is now the list {13, 1, 2, 3}}

The memory drawing for the C++ case looks the same as for the C case. The difference isthat the C case, the *'s need to be taken care of in the code. In the C++ case, it's handledinvisibly in the code.

Page 89: Data Structures

17

Section 3 —Code TechniquesThis section summarizes, in list form, the main techniques for linked list code. Thesetechniques are all demonstrated in the examples in the next section.

1) Iterate Down a ListA very frequent technique in linked list code is to iterate a pointer over all the nodes in alist. Traditionally, this is written as a while loop. The head pointer is copied into a localvariable current which then iterates down the list. Test for the end of the list withcurrent!=NULL. Advance the pointer with current=current->next.

// Return the number of nodes in a list (while-loop version)int Length(struct node* head) {

int count = 0;struct node* current = head;

while (current != NULL) {count++;current = current->next

}

return(count);}

Alternately, some people prefer to write the loop as a for which makes the initialization,test, and pointer advance more centralized, and so harder to omit...

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

2) Changing a Pointer With A Reference PointerMany list functions need to change the caller's head pointer. To do this in the C language,pass a pointer to the head pointer. Such a pointer to a pointer is sometimes called a"reference pointer". The main steps for this technique are...

• Design the function to take a pointer to the head pointer. This is thestandard technique in C — pass a pointer to the "value of interest" thatneeds to be changed. To change a struct node*, pass a structnode**.

• Use '&' in the caller to compute and pass a pointer to the value of interest.

• Use '*' on the parameter in the callee function to access and change thevalue of interest.

The following simple function sets a head pointer to NULL by using a referenceparameter....

// Change the passed in head pointer to be NULL// Uses a reference pointer to access the caller's memoryvoid ChangeToNull(struct node** headRef) { // Takes a pointer to

// the value of interest

Page 90: Data Structures

18

*headRef = NULL; // use '*' to access the value of interest}

void ChangeCaller() {struct node* head1;struct node* head2;

ChangeToNull(&head1); // use '&' to compute and pass a pointer toChangeToNull(&head2); // the value of interest// head1 and head2 are NULL at this point

}

Here is a drawing showing how the headRef pointer in ChangeToNull() points back tothe variable in the caller...

Stack

head1

headRef

ChangToNull()

ChangeCaller()

See the use of Push() above and its implementation for another example of referencepointers.

3) Build — At Head With Push()The easiest way to build up a list is by adding nodes at its "head end" with Push(). Thecode is short and it runs fast — lists naturally support operations at their head end. Thedisadvantage is that the elements will appear in the list in the reverse order that they areadded. If you don't care about order, then the head end is the best.

struct node* AddAtHead() {struct node* head = NULL;int i;

for (i=1; i<6; i++) {Push(&head, i);

}

// head == {5, 4, 3, 2, 1};return(head);

}

4) Build — With Tail PointerWhat about adding nodes at the "tail end" of the list? Adding a node at the tail of a listmost often involves locating the last node in the list, and then changing its .next field

Page 91: Data Structures

19

from NULL to point to the new node, such as the tail variable in the followingexample of adding a "3" node to the end of the list {1, 2}...

Stack Heap

1 2head

tail 3

newNode

This is just a special case of the general rule: to insert or delete a node inside a list, youneed a pointer to the node just before that position, so you can change its .next field.Many list problems include the sub-problem of advancing a pointer to the node before thepoint of insertion or deletion. The one exception is if the node is the first in the list — inthat case the head pointer itself must be changed. The following examples show thevarious ways code can handle the single head case and all the interior cases...

5) Build — Special Case + Tail PointerConsider the problem of building up the list {1, 2, 3, 4, 5} by appending the nodes to thetail end. The difficulty is that the very first node must be added at the head pointer, but allthe other nodes are inserted after the last node using a tail pointer. The simplest way todeal with both cases is to just have two separate cases in the code. Special case code firstadds the head node {1}. Then there is a separate loop that uses a tail pointer to add all theother nodes. The tail pointer is kept pointing at the last node, and each new node is addedat tail->next. The only "problem" with this solution is that writing separate specialcase code for the first node is a little unsatisfying. Nonetheless, this approach is a solidone for production code — it is simple and runs fast.

struct node* BuildWithSpecialCase() {struct node* head = NULL;struct node* tail;int i;

// Deal with the head node here, and set the tail pointerPush(&head, 1);tail = head;

// Do all the other nodes using 'tail'for (i=2; i<6; i++) {

Push(&(tail->next), i); // add node at tail->nexttail = tail->next; // advance tail to point to last node

}

return(head); // head == {1, 2, 3, 4, 5};}

Page 92: Data Structures

20

6) Build — Dummy NodeAnother solution is to use a temporary dummy node at the head of the list during thecomputation. The trick is that with the dummy, every node appear to be added after the.next field of a node. That way the code for the first node is the same as for the othernodes. The tail pointer plays the same role as in the previous example. The difference isthat it now also handles the first node.

struct node* BuildWithDummyNode() {struct node dummy; // Dummy node is temporarily the first nodestruct node* tail = &dummy; // Start the tail at the dummy.

// Build the list on dummy.next (aka tail->next)int i;

dummy.next = NULL;

for (i=1; i<6; i++) {Push(&(tail->next), i);tail = tail->next;

}

// The real result list is now in dummy.next// dummy.next == {1, 2, 3, 4, 5};return(dummy.next);

}

Some linked list implementations keep the dummy node as a permanent part of the list.For this "permanent dummy" strategy, the empty list is not represented by a NULLpointer. Instead, every list has a dummy node at its head. Algorithms skip over thedummy node for all operations. That way the heap allocated dummy node is alwayspresent to provide the above sort of convenience in the code.

Our dummy-in-the stack strategy is a little unusual, but it avoids making the dummy apermanent part of the list. Some of the solutions presented in this document will use thetemporary dummy strategy. The code for the permanent dummy strategy is extremelysimilar, but is not shown.7) Build — Local ReferencesFinally, here is a tricky way to unifying all the node cases without using a dummy node.The trick is to use a local "reference pointer" which always points to the last pointer inthe list instead of to the last node. All additions to the list are made by following thereference pointer. The reference pointer starts off pointing to the head pointer. Later, itpoints to the .next field inside the last node in the list. (A detailed explanation follows.)

struct node* BuildWithLocalRef() {struct node* head = NULL;struct node** lastPtrRef= &head; // Start out pointing to the head pointerint i;

for (i=1; i<6; i++) {Push(lastPtrRef, i); // Add node at the last pointer in the listlastPtrRef= &((*lastPtrRef)->next); // Advance to point to the

// new last pointer}

// head == {1, 2, 3, 4, 5};return(head);

}

Page 93: Data Structures

21

This technique is short, but the inside of the loop is scary. This technique is rarely used.(Actually, I'm the only person I've known to promote it. I think it has a sort of compactcharm.) Here's how it works...

1) At the top of the loop, lastPtrRef points to the last pointer in the list.Initially it points to the head pointer itself. Later it points to the .nextfield inside the last node in the list.

2) Push(lastPtrRef, i); adds a new node at the last pointer. Thenew node becaomes the last node in the list.

3) lastPtrRef= &((*lastPtrRef)->next); Advance thelastPtrRef to now point to the .next field inside the new last node— that .next field is now the last pointer in the list.

Here is a drawing showing the state of memory for the above code just before the thirdnode is added. The previous values of lastPtrRef are shown in gray...

Stack Heap

1 2head

LocalRef()

lastPtrRef

This technique is never required to solve a linked list problem, but it will be one of thealternative solutions presented for some of the advanced problems.

Both the temporary-dummy strategy and the reference-pointer strategy are a littleunusual. They are good ways to make sure that you really understand pointers, since theyuse pointers in unusual ways.

Page 94: Data Structures

22

Section 4 — ExamplesThis section presents some complete list code to demonstrate all of the techniques above.For many more sample problems with solutions, see CS Education Library #105, --Linked List Problems (http://cslibrary.stanford.edu/105/).

AppendNode() ExampleConsider a AppendNode() function which is like Push(), except it adds the new node atthe tail end of the list instead of the head. If the list is empty, it uses the reference pointerto change the head pointer. Otherwise it uses a loop to locate the last node in the list. Thisversion does not use Push(). It builds the new node directly.

struct node* AppendNode(struct node** headRef, int num) {struct node* current = *headRef;struct node* newNode;

newNode = malloc(sizeof(struct node));newNode->data = num;newNode->next = NULL;

// special case for length 0if (current == NULL) {

*headRef = newNode;}else {

// Locate the last nodewhile (current->next != NULL) {

current = current->next;}

current->next = newNode;}

}

AppendNode() With Push()This version is very similar, but relies on Push() to build the new node. Understandingthis version requires a real understanding of reference pointers.

struct node* AppendNode(struct node** headRef, int num) {struct node* current = *headRef;

// special case for the empty listif (current == NULL) {

Push(headRef, num);} else {

// Locate the last nodewhile (current->next != NULL) {

current = current->next;}

// Build the node after the last nodePush(&(current->next), num);

}}

Page 95: Data Structures

23

CopyList() ExampleConsider a CopyList() function that takes a list and returns a complete copy of that list.One pointer can iterate over the original list in the usual way. Two other pointers cankeep track of the new list: one head pointer, and one tail pointer which always points tothe last node in the new list. The first node is done as a special case, and then the tailpointer is used in the standard way for the others...

struct node* CopyList(struct node* head) {struct node* current = head; // used to iterate over the original liststruct node* newList = NULL; // head of the new liststruct node* tail = NULL; // kept pointing to the last node in the new list

while (current != NULL) {if (newList == NULL) { // special case for the first new node

newList = malloc(sizeof(struct node));newList->data = current->data;newList->next = NULL;tail = newList;

}else {

tail->next = malloc(sizeof(struct node));tail = tail->next;tail->data = current->data;tail->next = NULL;

}current = current->next;

}

return(newList);}

CopyList() Memory DrawingHere is the state of memory when CopyList() finishes copying the list {1, 2}...

Stack Heap

1 2head

1 2

CopyList()

current

newList

tail

Page 96: Data Structures

24

CopyList() With Push() ExerciseThe above implementation is a little unsatisfying because the 3-step-link-in is repeated —once for the first node and once for all the other nodes. Write a CopyList2() which usesPush() to take care of allocating and inserting the new nodes, and so avoids repeating thatcode.

CopyList() With Push() Answer// Variant of CopyList() that uses Push()struct node* CopyList2(struct node* head) {

struct node* current = head; // used to iterate over the original liststruct node* newList = NULL; // head of the new liststruct node* tail = NULL; // kept pointing to the last node in the new list

while (current != NULL) {if (newList == NULL) { // special case for the first new node

Push(&newList, current->data);tail = newList;

}else {

Push(&(tail->next), current->data); // add each node at the tailtail = tail->next; // advance the tail to the new last node

}current = current->next;

}

return(newList);}

CopyList() With Dummy NodeAnther strategy for CopyList() uses a temporary dummy node to take care of the firstnode case. The dummy node is temporarily the first node in the list, and the tail pointerstarts off pointing to it. All nodes are added off the tail pointer.

// Dummy node variantstruct node* CopyList(struct node* head) {

struct node* current = head; // used to iterate over the original liststruct node* tail; // kept pointing to the last node in the new liststruct node dummy; // build the new list off this dummy node

dummy.next = NULL;tail = &dummy; // start the tail pointing at the dummy

while (current != NULL) {Push(&(tail->next), current->data); // add each node at the tailtail = tail->next; // advance the tail to the new last node}current = current->next;

}

return(dummy.next);}

CopyList() With Local ReferencesThe final, and most unusual version uses the "local references" strategy instead of a tailpointer. The strategy is to keep a lastPtr that points to the last pointer in the list. Allnode additions are done at the lastPtr, and it always points to the last pointer in the

Page 97: Data Structures

25

list. When the list is empty, it points to the head pointer itself. Later it points to the.next pointer inside the last node in the list.

// Local reference variantstruct node* CopyList(struct node* head) {

struct node* current = head; // used to iterate over the original liststruct node* newList = NULL;struct node** lastPtr;

lastPtr = &newList; // start off pointing to the head itself

while (current != NULL) {Push(lastPtr, current->data); // add each node at the lastPtrlastPtr = &((*lastPtr)->next); // advance lastPtrcurrent = current->next;

}

return(newList);}

CopyList() RecursiveFinally, for completeness, here is the recursive version of CopyList(). It has the pleasingshortness that recursive code often has. However, it is probably not good for productioncode since it uses stack space proportional to the length of its list.

// Recursive variantstruct node* CopyList(struct node* head) {

if (head == NULL) return NULL;else {

struct node* newList = malloc(sizeof(struct node)); // make the one nodenewList->data = current->data;

newList->next = CopyList(current->next); // recur for the rest

return(newList);}

}

Appendix —Other ImplementationsThere are a many variations on the basic linked list which have individual advantagesover the basic linked list. It is probably best to have a firm grasp of the basic linked listand its code before worrying about the variations too much.

• Dummy Header Forbid the case where the head pointer is NULL.Instead, choose as a representation of the empty list a single "dummy"node whose .data field is unused. The advantage of this technique is thatthe pointer-to-pointer (reference parameter) case does not come up foroperations such as Push(). Also, some of the iterations are now a littlesimpler since they can always assume the existence of the dummy headernode. The disadvantage is that allocating an "empty" list now requires

Page 98: Data Structures

26

allocating (and wasting) memory. Some of the algorithms have an uglinessto them since they have to realize that the dummy node "doesn't count."(editorial) Mainly the dummy header is for programmers to avoid the uglyreference parameter issues in functions such as Push(). Languages whichdon't allow reference parameters, such as Java, may require the dummyheader as a workaround. (See the "temporary dummy" variant below.)

• Circular Instead of setting the .next field of the last node to NULL,set it to point back around to the first node. Instead of needing a fixed headend, any pointer into the list will do.

• Tail Pointer The list is not represented by a single head pointer. Insteadthe list is represented by a head pointer which points to the first node and atail pointer which points to the last node. The tail pointer allows operationsat the end of the list such as adding an end element or appending two liststo work efficiently.

• Head struct A variant I like better than the dummy header is to have aspecial "header" struct (a different type from the node type) whichcontains a head pointer, a tail pointer, and possibly a length to make manyoperations more efficient. Many of the reference parameter problems goaway since most functions can deal with pointers to the head struct(whether it is heap allocated or not). This is probably the best approach touse in a language without reference parameters, such as Java.

• Doubly-Linked Instead of just a single .next field, each nodeincudes both .next and .previous pointers. Insertion and deletion nowrequire more operations. but other operations are simplified. Given apointer to a node, insertion and deletion can be performed directly whereasin the singly linked case, the iteration typically needs to locate the pointjust before the point of change in the list so the .next pointers can befollowed downstream.

• Chunk List Instead of storing a single client element in each node, storea little constant size array of client elements in each node. Tuning thenumber of elements per node can provide different performancecharacteristics: many elements per node has performance more like anarray, few elements per node has performance more like a linked list. TheChunk List is a good way to build a linked list with good performance.

• Dynamic Array Instead of using a linked list, elements may bestored in an array block allocated in the heap. It is possible to grow andshrink the size of the block as needed with calls to the system functionrealloc(). Managing a heap block in this way is a fairly complex, but canhave excellent efficiency for storage and iteration., especially becausemodern memory systems are tuned for the access of contiguous areas ofmemory. In contrast, linked list can actually be a little inefficient, sincethey tend to iterate through memory areas that are not adjacent.

Page 99: Data Structures

Linked ListProblems

By Nick Parlante Copyright ©1998-2002, Nick Parlante

AbstractThis document reviews basic linked list code techniques and then works through 18linked list problems covering a wide range of difficulty. Most obviously, these problemsare a way to learn about linked lists. More importantly, these problems are a way todevelop your ability with complex pointer algorithms. Even though modern languagesand tools have made linked lists pretty unimportant for day-to-day programming, theskills for complex pointer algorithms are very important, and linked lists are an excellentway to develop those skills.

The problems use the C language syntax, so they require a basic understanding of C andits pointer syntax. The emphasis is on the important concepts of pointer manipulation andlinked list algorithms rather than the features of the C language.

For some of the problems we present multiple solutions, such as iteration vs. recursion,dummy node vs. local reference. The specific problems are, in rough order of difficulty:Count, GetNth, DeleteList, Pop, InsertNth, SortedInsert, InsertSort, Append,FrontBackSplit, RemoveDuplicates, MoveNode, AlternatingSplit, ShuffleMerge,SortedMerge, SortedIntersect, Reverse, and RecursiveReverse.

ContentsSection 1 — Review of basic linked list code techniques 3Section 2 — 18 list problems in increasing order of difficulty 10Section 3 — Solutions to all the problems 20

This is document #105, Linked List Problems, in the Stanford CS Education Library.This and other free educational materials are available at http://cslibrary.stanford.edu/.This document is free to be used, reproduced, or sold so long as this notice is clearlyreproduced at its beginning.

Related CS Education Library DocumentsRelated Stanford CS Education library documents...

• Linked List Basics (http://cslibrary.stanford.edu/103/)Explains all the basic issues and techniques for building linked lists.

• Pointers and Memory (http://cslibrary.stanford.edu/102/)Explains how pointers and memory work in C and other languages. Startswith the very basics, and extends through advanced topics such asreference parameters and heap management.

• Binary Trees (http://cslibrary.stanford.edu/110/)Introduction to binary trees

• Essential C (http://cslibrary.stanford.edu/101/)Explains the basic features of the C programming language.

Page 100: Data Structures

2

• The Great Tree List Problem (http://cslibrary.stanford.edu/109/)Presents the greatest recursive pointer problem ever devised.

Why Linked Lists Are Great To StudyLinked lists hold a special place in the hearts of many programmers. Linked lists are greatto study because...

• Nice Domain The linked list structure itself is simple. Many linked listoperations such as "reverse a list" or "delete a list" are easy to describe andunderstand since they build on the simple purpose and structure of thelinked list itself.

• Complex Algorithm Even though linked lists are simple, the algorithmsthat operate on them can be as complex and beautiful as you want (Seeproblem #18). It's easy to find linked list algorithms that are complex, andpointer intensive.

• Pointer Intensive Linked list problems are really about pointers. Thelinked list structure itself is obviously pointer intensive. Furthermore,linked list algorithms often break and re-weave the pointers in a linked listas they go. Linked lists really test your understanding of pointers.

• Visualization Visualization is an important skill in programming anddesign. Ideally, a programmer can visualize the state of memory to helpthink through the solution. Even the most abstract languages such as Javaand Perl have layered, reference based data structures that requirevisualization. Linked lists have a natural visual structure for practicing thissort of thinking. It's easy to draw the state of a linked list and use thatdrawing to think through the code.

Not to appeal to your mercenary side, but for all of the above reasons, linked listproblems are often used as interview and exam questions. They are short to state, andhave complex, pointer intensive solutions. No one really cares if you can build linkedlists, but they do want to see if you have programming agility for complex algorithms andpointer manipulation. Linked lists are the perfect source of such problems.

How To Use This DocumentTry not to use these problems passively. Take some time to try to solveeach problem.Even if you do not succeed, you will think through the right issues in the attempt, andlooking at the given solution will make more sense. Use drawings to think about theproblems and work through the solutions. Linked lists are well-suited for memorydrawings, so these problems are an excellent opportunity to develop your visualizationskill. The problems in this document use regular linked lists, without simplifcations likedummy headers.

DedicationThis Jan-2002 revision includes many small edits. The first major release was Jan 17,1999. Thanks to Negar Shamma for her many corrections. This document is distributedfor the benefit and education of all. Thanks to the support of Eric Roberts and StanfordUniversity. That someone seeking education should have the opportunity to find it. Mayyou learn from it in the spirit of goodwill in which it is given.Best Regards, Nick Parlante -- [email protected]

Page 101: Data Structures

3

Section 1 —Linked List ReviewThis section is a quick review of the concepts used in these linked list problems. For moredetailed coverage, see Link List Basics (http://cslibrary.stanford.edu/103/) where all ofthis material is explained in much more detail.

Linked List Ground RulesAll of the linked list code in this document uses the "classic" singly linked list structure:A single head pointer points to the first node in the list. Each node contains a single.next pointer to the next node. The .next pointer of the last node is NULL. Theempty list is represented by a NULL head pointer. All of the nodes are allocated in theheap.

For a few of the problems, the solutions present the temporary "dummy node" variation(see below), but most of the code deals with linked lists in their plain form. In the text,brackets {} are used to describe lists — the list containing the numbers 1, 2, and 3 iswritten as {1, 2, 3}. The node type used is...

struct node {int data;struct node* next;

};

To keep thing ssimple, we will not introduce any intermediate typedefs. All pointers tonodes are declared simply as struct node*. Pointers to pointers to nodes are declaredas struct node**. Such pointers to pointers are often called "reference pointers".

Basic Utility FunctionsIn a few places, the text assumes the existence of the following basic utility functions...

• int Length(struct node* head);Returns the number of nodes in the list.

• struct node* BuildOneTwoThree();Allocates and returns the list {1, 2, 3}. Used by some of the example codeto build lists to work on.

• void Push(struct node** headRef, int newData);Given an int and a reference to the head pointer (i.e. a structnode** pointer to the head pointer), add a new node at the head of thelist with the standard 3-step-link-in: create the new node, set its .next topoint to the current head, and finally change the head to point to the newnode. (If you are not sure of how this function works, the first fewproblems may be helpful warm-ups.)

Page 102: Data Structures

4

Use of the Basic Utility FunctionsThis sample code demonstrates the basic utility functions being used. Theirimplementations are also given in the appendix at the end of the document.

void BasicsCaller() {struct node* head;int len;

head = BuildOneTwoThree(); // Start with {1, 2, 3}

Push(&head, 13); // Push 13 on the front, yielding {13, 1, 2, 3}// (The '&' is because head is passed// as a reference pointer.)

Push(&(head->next), 42); // Push 42 into the second position// yielding {13, 42, 1, 2, 3}// Demonstrates a use of '&' on// the .next field of a node.// (See technique #2 below.)

len = Length(head); // Computes that the length is 5.}

If these basic functions do not make sense to you, you can (a) go see Linked List Basics(http://cslibrary.stanford.edu/103/) which explains the basics of linked lists in detail, or(b) do the first few problems, but avoid the intermediate and advanced ones.

Linked List Code TechniquesThe following list presents the most common techniques you may want to use in solvingthe linked list problems. The first few are basic. The last few are only necessary for themore advanced problems.

1. Iterate Down a ListA very frequent technique in linked list code is to iterate a pointer over all the nodes in alist. Traditionally, this is written as a while loop. The head pointer is copied into a localvariable current which then iterates down the list. Test for the end of the list withcurrent!=NULL. Advance the pointer with current=current->next.

// Return the number of nodes in a list (while-loop version)int Length(struct node* head) {

int count = 0;struct node* current = head;

while (current != NULL) {count++;current = current->next;

}

return(count);}

Alternately, some people prefer to write the loop as a for which makes the initialization,test, and pointer advance more centralized, and so harder to omit...

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

Page 103: Data Structures

5

2. Changing a Pointer Using a Reference PointerMany list functions need to change the caller's head pointer. In C++, you can just declarethe pointer parameter as an & argument, and the compiler takes care of the details. To dothis in the C language, pass a pointer to the head pointer. Such a pointer to a pointer issometimes called a "reference pointer". The main steps for this technique are...

• Design the function to take a pointer to the head pointer. This is thestandard technique in C — pass a pointer to the "value of interest" thatneeds to be changed. To change a struct node*, pass a structnode**.

• Use '&' in the caller to compute and pass a pointer to the value of interest.

• Use '*' on the parameter in the callee function to access and change thevalue of interest.

The following simple function sets a head pointer to NULL by using a referenceparameter....

// Change the passed in head pointer to be NULL// Uses a reference pointer to access the caller's memoryvoid ChangeToNull(struct node** headRef) { // Takes a pointer to

// the value of interest

*headRef = NULL; // use '*' to access the value of interest}

void ChangeCaller() {struct node* head1;struct node* head2;

ChangeToNull(&head1); // use '&' to compute and pass a pointer toChangeToNull(&head2); // the value of interest// head1 and head2 are NULL at this point

}

Here is a drawing showing how the headRef pointer in ChangeToNull() points back tothe variable in the caller...

Stack

head1

headRef

ChangeToNull(&head1)

ChangeCaller()

Page 104: Data Structures

6

Many of the functions in this document use reference pointer parameters. See the use ofPush() above and its implementation in the appendix for another example of referencepointers. See problem #8 and its solution for a complete example with drawings. Formore detailed explanations, see the resources listed on page 1.

3. Build — At Head With Push()The easiest way to build up a list is by adding nodes at its "head end" with Push(). Thecode is short and it runs fast — lists naturally support operations at their head end. Thedisadvantage is that the elements will appear in the list in the reverse order that they areadded. If you don't care about order, then the head end is the best.

struct node* AddAtHead() {struct node* head = NULL;int i;

for (i=1; i<6; i++) {Push(&head, i);

}

// head == {5, 4, 3, 2, 1};return(head);

}

4. Build — With Tail PointerWhat about adding nodes at the "tail end" of the list? Adding a node at the tail of a listmost often involves locating the last node in the list, and then changing its .next fieldfrom NULL to point to the new node, such as the tail variable in the followingexample of adding a "3" node to the end of the list {1, 2}...

Stack Heap

1 2head

tail 3

newNode

This is just a special case of the general rule: to insert or delete a node inside a list, youneed a pointer to the node just before that position, so you can change its .next field.Many list problems include the sub-problem of advancing a pointer to the node before thepoint of insertion or deletion. The one exception is if the operation falls on the first nodein the list — in that case the head pointer itself must be changed. The following examplesshow the various ways code can handle the single head case and all the interior cases...

Page 105: Data Structures

7

5. Build — Special Case + Tail PointerConsider the problem of building up the list {1, 2, 3, 4, 5} by appending the nodes to thetail end. The difficulty is that the very first node must be added at the head pointer, but allthe other nodes are inserted after the last node using a tail pointer. The simplest way todeal with both cases is to just have two separate cases in the code. Special case code firstadds the head node {1}. Then there is a separate loop that uses a tail pointer to add all theother nodes. The tail pointer is kept pointing at the last node, and each new node is addedat tail->next. The only "problem" with this solution is that writing separate specialcase code for the first node is a little unsatisfying. Nonetheless, this approach is a solidone for production code — it is simple and runs fast.

struct node* BuildWithSpecialCase() {struct node* head = NULL;struct node* tail;int i;

// Deal with the head node here, and set the tail pointerPush(&head, 1);tail = head;

// Do all the other nodes using 'tail'for (i=2; i<6; i++) {

Push(&(tail->next), i); // add node at tail->nexttail = tail->next; // advance tail to point to last node

}

return(head); // head == {1, 2, 3, 4, 5};}

6. Build — Temporary Dummy NodeThis is a slightly unusual technique that can be used to shorten the code: Use a temporarydummy node at the head of the list during the computation. The trick is that with thedummy, every node appears to be added after the .next field of some other node. Thatway the code for the first node is the same as for the other nodes. The tail pointer playsthe same role as in the previous example. The difference is that now it also handles thefirst node as well.

struct node* BuildWithDummyNode() {struct node dummy; // Dummy node is temporarily the first nodestruct node* tail = &dummy; // Start the tail at the dummy.

// Build the list on dummy.next (aka tail->next)int i;

dummy.next = NULL;

for (i=1; i<6; i++) {Push(&(tail->next), i);tail = tail->next;

}

// The real result list is now in dummy.next// dummy.next == {1, 2, 3, 4, 5};return(dummy.next);

}

Page 106: Data Structures

8

Some linked list implementations keep the dummy node as a permanent part of the list.For this "permanent dummy" strategy, the empty list is not represented by a NULLpointer. Instead, every list has a heap allocated dummy node at its head. Algorithms skipover the dummy node for all operations. That way the dummy node is always present toprovide the above sort of convenience in the code. I prefer the temporary strategy shownhere, but it is a little peculiar since the temporary dummy node is allocated in the stack,while all the other nodes are allocated in the heap. For production code, I do not useeither type of dummy node. The code should just cope with the head node boundarycases.

7. Build — Local ReferencesFinally, here is a tricky way to unify all the node cases without using a dummy node atall. For this technique, we use a local "reference pointer" which always points to the lastpointer in the list instead of to the last node. All additions to the list are made byfollowing the reference pointer. The reference pointer starts off pointing to the headpointer. Later, it points to the .next field inside the last node in the list. (A detailedexplanation follows.)

struct node* BuildWithLocalRef() {struct node* head = NULL;struct node** lastPtrRef= &head; // Start out pointing to the head pointerint i;

for (i=1; i<6; i++) {Push(lastPtrRef, i); // Add node at the last pointer in the listlastPtrRef= &((*lastPtrRef)->next); // Advance to point to the

// new last pointer}

// head == {1, 2, 3, 4, 5};return(head);

}

This technique is short, but the inside of the loop is scary. This technique is rarely used,but it's a good way to see if you really understand pointers. Here's how it works...

1) At the top of the loop, lastPtrRef points to the last pointer in the list.Initially it points to the head pointer itself. Later it points to the .nextfield inside the last node in the list.

2) Push(lastPtrRef, i); adds a new node at the last pointer. Thenew node becomes the last node in the list.

3) lastPtrRef= &((*lastPtrRef)->next); Advance thelastPtrRef to now point to the .next field inside the new last node— that .next field is now the last pointer in the list.

Here is a drawing showing the state of memory for the above code just before the thirdnode is added. The previous values of lastPtrRef are shown in gray...

Page 107: Data Structures

9

Stack Heap

1 2head

LocalRef()

lastPtrRef

This technique is never required to solve a linked list problem, but it will be one of thealternative solutions presented for some of the advanced problems. The code is shorterthis way, but the performance is probably not any better.

Unusual TechniquesBoth the temporary-stack-dummy and the local-reference-pointer techniques are a littleunusual. They are cute, and they let us play around with yet another variantion in pointerintensive code. They use memory in unusual ways, so they are a nice way to see if youreally understand what's going on. However, I probably would not use them in productioncode.

Page 108: Data Structures

10

Section 2 —Linked List ProblemsHere are 18 linked list problems arranged in order of difficulty. The first few are quitebasic and the last few are quite advanced. Each problem starts with a basic definition ofwhat needs to be accomplished. Many of the problems also include hints or drawings toget you started. The solutions to all the problems are in the next section.

It's easy to just passively sweep your eyes over the solution — verifying its existencewithout lettings its details touch your brain. To get the most benefit from these problems,you need to make an effort to think them through. Whether or not you solve the problem,you will be thinking through the right issues, and the given solution will make moresense.

Great programmers can visualize data structures to see how the code and memory willinteract. Linked lists are well suited to that sort of visual thinking. Use these problems todevelop your visualization skill. Make memory drawings to trace through the executionof code. Use drawings of the pre- and post-conditions of a problem to start thinking abouta solution.

"The will to win means nothing without the will to prepare." - Juma Ikangaa, marathoner(also attributed to Bobby Knight)

1 — Count()Write a Count() function that counts the number of times a given int occurs in a list. Thecode for this has the classic list traversal structure as demonstrated in Length().

void CountTest() {List myList = BuildOneTwoThree(); // build {1, 2, 3}

int count = Count(myList, 2); // returns 1 since there's 1 '2' in the list}

/* Given a list and an int, return the number of times that int occurs in the list.*/int Count(struct node* head, int searchFor) {// Your code

Page 109: Data Structures

11

2 — GetNth()Write a GetNth() function that takes a linked list and an integer index and returns the datavalue stored in the node at that index position. GetNth() uses the C numbering conventionthat the first node is index 0, the second is index 1, ... and so on. So for the list {42, 13,666} GetNth() with index 1 should return 13. The index should be in the range [0..length-1]. If it is not, GetNth() should assert() fail (or you could implement some other errorcase strategy).

void GetNthTest() {struct node* myList = BuildOneTwoThree(); // build {1, 2, 3}int lastNode = GetNth(myList, 2); // returns the value 3

}

Essentially, GetNth() is similar to an array[i] operation — the client can ask forelements by index number. However, GetNth() no a list is much slower than [ ] on anarray. The advantage of the linked list is its much more flexible memory management —we can Push() at any time to add more elements and the memory is allocated as needed.

// Given a list and an index, return the data// in the nth node of the list. The nodes are numbered from 0.// Assert fails if the index is invalid (outside 0..lengh-1).int GetNth(struct node* head, int index) {// Your code

3 — DeleteList()Write a function DeleteList() that takes a list, deallocates all of its memory and sets itshead pointer to NULL (the empty list).

void DeleteListTest() {struct node* myList = BuildOneTwoThree(); // build {1, 2, 3}

DeleteList(&myList); // deletes the three nodes and sets myList to NULL}

Post DeleteList() Memory DrawingThe following drawing shows the state of memory after DeleteList() executes in theabove sample. Overwritten pointers are shown in gray and deallocated heap memory hasan 'X' through it. Essentially DeleteList() just needs to call free() once for each node andset the head pointer to NULL.

Page 110: Data Structures

12

Stack Heap

1 2 3

myList

DeleteListTest()

myList is overwritten with the value NULL.

The three heap blocks are deallocated by calls to free(). Their memory will appear to be intact for a while, but the memory should not be accessed.

DeleteList()The DeleteList() implementation will need to use a reference parameter just like Push()so that it can change the caller's memory (myList in the above sample). Theimplementation also needs to be careful not to access the .next field in each node afterthe node has been deallocated.

void DeleteList(struct node** headRef) {// Your code

4 — Pop()Write a Pop() function that is the inverse of Push(). Pop() takes a non-empty list, deletesthe head node, and returns the head node's data. If all you ever used were Push() andPop(), then our linked list would really look like a stack. However, we provide moregeneral functions like GetNth() which what make our linked list more than just a stack.Pop() should assert() fail if there is not a node to pop. Here's some sample code whichcalls Pop()....

void PopTest() {struct node* head = BuildOneTwoThree(); // build {1, 2, 3}int a = Pop(&head); // deletes "1" node and returns 1int b = Pop(&head); // deletes "2" node and returns 2int c = Pop(&head); // deletes "3" node and returns 3int len = Length(head); // the list is now empty, so len == 0

}

Pop() UnlinkPop() is a bit tricky. Pop() needs to unlink the front node from the list and deallocate itwith a call to free(). Pop() needs to use a reference parameter like Push() so that it canchange the caller's head pointer. A good first step to writing Pop() properly is making thememory drawing for what Pop() should do. Below is a drawing showing a Pop() of thefirst node of a list. The process is basically the reverse of the 3-Step-Link-In used byPush() (would that be "Ni Knil Pets-3"?). The overwritten pointer value is shown in gray,and the deallocated heap memory has a big 'X' drawn on it...

Page 111: Data Structures

13

Stack Heap

1 2 3

head

PopTest()

The head pointer advances to refer to the node after the unlinked one.

The unlinked node is deallocated by a call to free(). Ironically, the unlinked node itself is not changed immediately. It is no longer appears in the list just because the head pointer no longer points to it.

Pop()/* The opposite of Push(). Takes a non-empty list and removes the front node, and returns the data which was in that node.*/int Pop(struct node** headRef) {// your code...

5 — InsertNth()A more difficult problem is to write a function InsertNth() which can insert a new node atany index within a list. Push() is similar, but can only insert a node at the head end of thelist (index 0). The caller may specify any index in the range [0..length], and the new nodeshould be inserted so as to be at that index.

void InsertNthTest() {struct node* head = NULL; // start with the empty list

InsertNth(&head, 0, 13); // build {13)InsertNth(&head, 1, 42); // build {13, 42}InsertNth(&head, 1, 5); // build {13, 5, 42}

DeleteList(&head); // clean up after ourselves}

Page 112: Data Structures

14

InsertNth() is complex — you will want to make some drawings to think about yoursolution and afterwards, to check its correctness.

/* A more general version of Push(). Given a list, an index 'n' in the range 0..length, and a data element, add a new node to the list so that it has the given index.*/void InsertNth(struct node** headRef, int index, int data) {// your code...

6 — SortedInsert()Write a SortedInsert() function which given a list that is sorted in increasing order, and asingle node, inserts the node into the correct sorted position in the list. While Push()allocates a new node to add to the list, SortedInsert() takes an existing node, and justrearranges pointers to insert it into the list. There are many possible solutions to thisproblem.

void SortedInsert(struct node** headRef, struct node* newNode) {// Your code...

7 — InsertSort()Write an InsertSort() function which given a list, rearranges its nodes so they are sorted inincreasing order. It should use SortedInsert().

// Given a list, change it to be in sorted order (using SortedInsert()).void InsertSort(struct node** headRef) { // Your code

8 — Append()Write an Append() function that takes two lists, 'a' and 'b', appends 'b' onto the end of 'a',and then sets 'b' to NULL (since it is now trailing off the end of 'a'). Here is a drawing ofa sample call to Append(a, b) with the start state in gray and the end state in black. At theend of the call, the 'a' list is {1, 2, 3, 4}, and 'b' list is empty.

Stack Heap

1 2a

b

3 4

Page 113: Data Structures

15

It turns out that both of the head pointers passed to Append(a, b) need to be referenceparameters since they both may need to be changed. The second 'b' parameter is alwaysset to NULL. When is 'a' changed? That case occurs when the 'a' list starts out empty. Inthat case, the 'a' head must be changed from NULL to point to the 'b' list. Before the call'b' is {3, 4}. After the call, 'a' is {3, 4}.

Stack Heap

a

b

3 4

// Append 'b' onto the end of 'a', and then set 'b' to NULL.void Append(struct node** aRef, struct node** bRef) {// Your code...

9 — FrontBackSplit()Given a list, split it into two sublists — one for the front half, and one for the back half. Ifthe number of elements is odd, the extra element should go in the front list. SoFrontBackSplit() on the list {2, 3, 5, 7, 11} should yield the two lists {2, 3, 5} and {7,11}. Getting this right for all the cases is harder than it looks. You should check yoursolution against a few cases (length = 2, length = 3, length=4) to make sure that the listgets split correctly near the short-list boundary conditions. If it works right for length=4,it probably works right for length=1000. You will probably need special case code to dealwith the (length <2) cases.

Hint. Probably the simplest strategy is to compute the length of the list, then use a forloop to hop over the right number of nodes to find the last node of the front half, and thencut the list at that point. There is a trick technique that uses two pointers to traverse thelist. A "slow" pointer advances one nodes at a time, while the "fast" pointer goes twonodes at a time. When the fast pointer reaches the end, the slow pointer will be about halfway. For either strategy, care is required to split the list at the right point.

/* Split the nodes of the given list into front and back halves, and return the two lists using the reference parameters. If the length is odd, the extra node should go in the front list.*/void FrontBackSplit(struct node* source,

struct node** frontRef, struct node** backRef) {// Your code...

Page 114: Data Structures

16

10 RemoveDuplicates()Write a RemoveDuplicates() function which takes a list sorted in increasing order anddeletes any duplicate nodes from the list. Ideally, the list should only be traversed once.

/* Remove duplicates from a sorted list.*/void RemoveDuplicates(struct node* head) {// Your code...

11 — MoveNode()This is a variant on Push(). Instead of creating a new node and pushing it onto the givenlist, MoveNode() takes two lists, removes the front node from the second list and pushesit onto the front of the first. This turns out to be a handy utility function to have forseveral later problems. Both Push() and MoveNode() are designed around the feature thatlist operations work most naturally at the head of the list. Here's a simple example ofwhat MoveNode() should do...

void MoveNodeTest() {struct node* a = BuildOneTwoThree(); // the list {1, 2, 3}struct node* b = BuildOneTwoThree();

MoveNode(&a, &b);// a == {1, 1, 2, 3}// b == {2, 3}

}

/* Take the node from the front of the source, and move it to the front of the dest. It is an error to call this with the source list empty.*/void MoveNode(struct node** destRef, struct node** sourceRef) {// Your code

12 — AlternatingSplit()Write a function AlternatingSplit() that takes one list and divides up its nodes to maketwo smaller lists. The sublists should be made from alternating elements in the originallist. So if the original list is {a, b, a, b, a}, then one sublist should be {a, a, a} and theother should be {b, b}. You may want to use MoveNode() as a helper. The elements inthe new lists may be in any order (for some implementations, it turns out to be convenientif they are in the reverse order from the original list.)

/* Given the source list, split its nodes into two shorter lists. If we number the elements 0, 1, 2, ... then all the even elements should go in the first list, and all the odd elements in the second. The elements in the new lists may be in any order.*/void AlternatingSplit(struct node* source,

struct node** aRef, struct node** bRef) {// Your code

Page 115: Data Structures

17

13— ShuffleMerge()Given two lists, merge their nodes together to make one list, taking nodes alternatelybetween the two lists. So ShuffleMerge() with {1, 2, 3} and {7, 13, 1} should yield {1, 7,2, 13, 3, 1}. If either list runs out, all the nodes should be taken from the other list. Thesolution depends on being able to move nodes to the end of a list as discussed in theSection 1 review. You may want to use MoveNode() as a helper. Overall, manytechniques are possible: dummy node, local reference, or recursion. Using this functionand FrontBackSplit(), you could simulate the shuffling of cards.

/* Merge the nodes of the two lists into a single list taking a node alternately from each list, and return the new list.*/struct node* ShuffleMerge(struct node* a, struct node* b) {// Your code

14 — SortedMerge()Write a SortedMerge() function that takes two lists, each of which is sorted in increasingorder, and merges the two together into one list which is in increasing order.SortedMerge() should return the new list. The new list should be made by splicingtogether the nodes of the first two lists (use MoveNode()). Ideally, Merge() should onlymake one pass through each list. Merge() is tricky to get right — it may be solvediteratively or recursively. There are many cases to deal with: either 'a' or 'b' may beempty, during processing either 'a' or 'b' may run out first, and finally there's the problemof starting the result list empty, and building it up while going through 'a' and 'b'.

/* Takes two lists sorted in increasing order, and splices their nodes together to make one big sorted list which is returned.*/struct node* SortedMerge(struct node* a, struct node* b) {// your code...

15 — MergeSort()(This problem requires recursion) Given FrontBackSplit() and SortedMerge(), it's prettyeasy to write a classic recursive MergeSort(): split the list into two smaller lists,recursively sort those lists, and finally merge the two sorted lists together into a singlesorted list. Ironically, this problem is easier than either FrontBackSplit() orSortedMerge().

void MergeSort(struct node* headRef) {// Your code...

Page 116: Data Structures

18

16 — SortedIntersect()Given two lists sorted in increasing order, create and return a new list representing theintersection of the two lists. The new list should be made with its own memory — theoriginal lists should not be changed. In other words, this should be Push() list building,not MoveNode(). Ideally, each list should only be traversed once. This problem alongwith Union() and Difference() form a family of clever algorithms that exploit theconstraint that the lists are sorted to find common nodes efficiently.

/* Compute a new sorted list that represents the intersection of the two given sorted lists.*/struct node* SortedIntersect(struct node* a, struct node* b) {// Your code

17 — Reverse()Write an iterative Reverse() function that reverses a list by rearranging all the .nextpointers and the head pointer. Ideally, Reverse() should only need to make one pass of thelist. The iterative solution is moderately complex. It's not so difficult that it needs to bethis late in the document, but it goes here so it can be next to #18 Recursive Reversewhich is quite tricky. The efficient recursive solution is quite complex (see nextproblem). (A memory drawing and some hints for Reverse() are below.)

void ReverseTest() {struct node* head;

head = BuildOneTwoThree();Reverse(&head);// head now points to the list {3, 2, 1}

DeleteList(&head); // clean up after ourselves}

Stack Heap

1 2 3

List reverse before and after. Before (in gray) the list is {1, 2, 3}. After (in black), the pointers have been rearranged so the list is {3, 2, 1}.head

ReverseTest()

"Push" Reverse HintIterate through the main list. Move each node to the front of the result list as you go. It'slike doing a Push() operation with each node, except you use pointer re-arrangement on

Page 117: Data Structures

19

the existing node instead of allocating a new node. You can use MoveNode() to do mostof the work, or hand code the pointer re-arrangement.

"3 Pointers" HintThis strategy is not as good as the "Push" strategy, but it's the first one I thought of(thanks to Owen Astrachan for pointing out the better solution). Instead of running asingle "current" pointer down the list, run three pointers (front, middle, back) down thelist in order: front is on one node, middle is just behind it, and back in turn is one behindmiddle. Once the code to run the three pointers down the list is clear and tested in adrawing, add code to reverse the .next pointer of the middle node during the iteration.Add code to take care of the empty list and to adjust the head pointer itself.

/* Reverse the given linked list by changing its .next pointers and its head pointer. Takes a pointer (reference) to the head pointer.*/void Reverse(struct node** headRef) {// your code...

18 — RecursiveReverse()(This problem is difficult and is only possible if you are familiar with recursion.) There isa short and efficient recursive solution to this problem. As before, the code should onlymake a single pass over the list. Doing it with multiple passes is easier but very slow, sohere we insist on doing it in one pass.. Solving this problem requires a real understandingof pointer code and recursion.

/* Recursively reverses the given linked list by changing its .next pointers and its head pointer in one pass of the list.*/void RecursiveReverse(struct node** headRef) {// your code...

The Tree-List Recursion ProblemOnce you are done with these problems, see the best and most complex list recursionproblem of all time: The great Tree-List-Recursion problem athttp://cslibrary.stanford.edu/109/

Page 118: Data Structures

20

Section 3 — Solutions

1 — Count() SolutionA straightforward iteration down the list — just like Length().

int Count(struct node* head, int searchFor) {struct node* current = head;int count = 0;

while (current != NULL) {if (current->data == searchFor) count++;current = current->next;

}

return count;}

Alternately, the iteration may be coded with a for loop instead of a while...

int Count2(struct node* head, int searchFor) {struct node* current;int count = 0;

for (current = head; current != NULL; current = current->next) {if (current->data == searchFor) count++;

}

return count;}

2 — GetNth() SolutionCombine standard list iteration with the additional problem of counting over to find theright node. Off-by-one errors are common in this sort of code. Check it carefully against asimple case. If it's right for n=0, n=1, and n=2, it will probably be right for n=1000.

int GetNth(struct node* head, int index) {struct node* current = head;int count = 0; // the index of the node we're currently looking at

while (current != NULL) {if (count == index) return(current->data);count++;current = current->next;

}

assert(0); // if we get to this line, the caller was asking// for a non-existent element so we assert fail.

}

Page 119: Data Structures

21

3 — DeleteList() SolutionDelete the whole list and set the head pointer to NULL. There is a slight complicationinside the loop, since we need extract the .next pointer before we delete the node, sinceafter the delete it will be technically unavailable.

void DeleteList(struct node** headRef) {struct node* current = *headRef; // deref headRef to get the real headstruct node* next;

while (current != NULL) {next = current->next; // note the next pointerfree(current); // delete the nodecurrent = next; // advance to the next node

}

*headRef = NULL; // Again, deref headRef to affect the real head back// in the caller.

}

4 — Pop() SolutionExtract the data from the head node, delete the node, advance the head pointer to point atthe next node in line. Uses a reference parameter since it changes the head pointer.

int Pop(struct node** headRef) {struct node* head;int result;

head = *headRef;assert(head != NULL);

result = head->data; // pull out the data before the node is deleted

*headRef = head->next; // unlink the head node for the caller// Note the * -- uses a reference-pointer// just like Push() and DeleteList().

free(head); // free the head node

return(result); // don't forget to return the data from the link}

5 — InsertNth() SolutionThis code handles inserting at the very front as a special case. Otherwise, it works byrunning a current pointer to the node before where the new node should go. Uses a forloop to march the pointer forward. The exact bounds of the loop (the use of < vs <=, n vs.n-1) are always tricky — the best approach is to get the general structure of the iterationcorrect first, and then make a careful drawing of a couple test cases to adjust the n vs. n-1cases to be correct. (The so called "OBOB" — Off By One Boundary cases.) The OBOBcases are always tricky and not that interesting. Write the correct basic structure and thenuse a test case to get the OBOB cases correct. Once the insertion point has beendetermined, this solution uses Push() to do the link in. Alternately, the 3-Step Link Incode could be pasted here directly.

Page 120: Data Structures

22

void InsertNth(struct node** headRef, int index, int data) {// position 0 is a special case...if (index == 0) Push(headRef, data);else {

struct node* current = *headRef;int i;

for (i=0; i<index-1; i++) {assert(current != NULL); // if this fails, index was too bigcurrent = current->next;

}

assert(current != NULL); // tricky: you have to check one last time

Push(&(current->next), data); // Tricky use of Push() --// The pointer being pushed on is not// in the stack. But actually this works// fine -- Push() works for any node pointer.

}}

6 — SortedInsert() SolutionThe basic strategy is to iterate down the list looking for the place to insert the new node.That could be the end of the list, or a point just before a node which is larger than the newnode. The three solutions presented handle the "head end" case in different ways...

// Uses special case code for the head endvoid SortedInsert(struct node** headRef, struct node* newNode) {

// Special case for the head endif (*headRef == NULL || (*headRef)->data >= newNode->data) {

newNode->next = *headRef;*headRef = newNode;

}else {

// Locate the node before the point of insertionstruct node* current = *headRef;while (current->next!=NULL && current->next->data<newNode->data) {

current = current->next;}newNode->next = current->next;current->next = newNode;

}}

// Dummy node strategy for the head endvoid SortedInsert2(struct node** headRef, struct node* newNode) {

struct node dummy;struct node* current = &dummy;dummy.next = *headRef;

while (current->next!=NULL && current->next->data<newNode->data) {current = current->next;

}

newNode->next = current->next;current->next = newNode;

Page 121: Data Structures

23

*headRef = dummy.next;}

// Local references strategy for the head endvoid SortedInsert3(struct node** headRef, struct node* newNode) {

struct node** currentRef = headRef;

while (*currentRef!=NULL && (*currentRef)->data<newNode->data) {currentRef = &((*currentRef)->next);

}

newNode->next = *currentRef; // Bug: this line used to have// an incorrect (*currRef)->next

*currentRef = newNode;}

7 — InsertSort() SolutionStart with an empty result list. Iterate through the source list and SortedInsert() each of itsnodes into the result list. Be careful to note the .next field in each node before movingit into the result list.

// Given a list, change it to be in sorted order (using SortedInsert()).void InsertSort(struct node** headRef) {

struct node* result = NULL; // build the answer herestruct node* current = *headRef; // iterate over the original liststruct node* next;

while (current!=NULL) {next = current->next; // tricky - note the next pointer before we change itSortedInsert(&result, current);current = next;

}

*headRef = result;}

8 — Append() SolutionThe case where the 'a' list is empty is a special case handled first — in that case the 'a'head pointer needs to be changed directly. Otherwise we iterate down the 'a' list until wefind its last node with the test (current->next != NULL), and then tack on the 'b'list there. Finally, the original 'b' head is set to NULL. This code demonstrates extensiveuse of pointer reference parameters, and the common problem of needing to locate thelast node in a list. (There is also a drawing of how Append() uses memory below.)

void Append(struct node** aRef, struct node** bRef) {struct node* current;

if (*aRef == NULL) { // Special case if a is empty*aRef = *bRef;

}else { // Otherwise, find the end of a, and append b there

current = *aRef;while (current->next != NULL) { // find the last node

current = current->next;}

Page 122: Data Structures

24

current->next = *bRef; // hang the b list off the last node}

*bRef=NULL; // NULL the original b, since it has been appended above}

Append() Test and DrawingThe following AppendTest() code calls Append() to join two lists. What does memorylook like just before the call to Append() exits?

void AppendTest() {struct node* a;struct node* b;

// set a to {1, 2}// set b to {3, 4}

Append(&a, &b);}

As an example of how reference parameters work, note how reference parameters inAppend() point back to the head pointers in AppendTest()...

Stack Heap

1 2a

b

3 4

AppendTest()

aRef

bRef

current

Append(&a, &b)

9 — FrontBackSplit() SolutionTwo solutions are presented...

// Uses the "count the nodes" strategyvoid FrontBackSplit(struct node* source,

struct node** frontRef, struct node** backRef) {

int len = Length(source);int i;struct node* current = source;

Page 123: Data Structures

25

if (len < 2) {*frontRef = source;*backRef = NULL;

}else {

int hopCount = (len-1)/2; //(figured these with a few drawings)for (i = 0; i<hopCount; i++) {

current = current->next;}

// Now cut at current*frontRef = source;*backRef = current->next;current->next = NULL;

}}

// Uses the fast/slow pointer strategyvoid FrontBackSplit2(struct node* source,

struct node** frontRef, struct node** backRef) {struct node* fast;struct node* slow;

if (source==NULL || source->next==NULL) { // length < 2 cases*frontRef = source;*backRef = NULL;

}else {

slow = source;fast = source->next;

// Advance 'fast' two nodes, and advance 'slow' one nodewhile (fast != NULL) {

fast = fast->next;if (fast != NULL) {

slow = slow->next;fast = fast->next;

}}

// 'slow' is before the midpoint in the list, so split it in two// at that point.*frontRef = source;*backRef = slow->next;slow->next = NULL;

}}

10 — RemoveDuplicates() SolutionSince the list is sorted, we can proceed down the list and compare adjacent nodes. Whenadjacent nodes are the same, remove the second one. There's a tricky case where the nodeafter the next node needs to be noted before the deletion.

// Remove duplicates from a sorted listvoid RemoveDuplicates(struct node* head) {

struct node* current = head;

Page 124: Data Structures

26

if (current == NULL) return; // do nothing if the list is empty

// Compare current node with next nodewhile(current->next!=NULL) {

if (current->data == current->next->data) {struct node* nextNext = current->next->next;free(current->next);current->next = nextNext;

}else {

current = current->next; // only advance if no deletion}

}}

11 — MoveNode() SolutionThe MoveNode() code is most similar to the code for Push(). It's short — just changing acouple pointers — but it's complex. Make a drawing.

void MoveNode(struct node** destRef, struct node** sourceRef) {struct node* newNode = *sourceRef; // the front source nodeassert(newNode != NULL);

*sourceRef = newNode->next; // Advance the source pointer

newNode->next = *destRef; // Link the old dest off the new node*destRef = newNode; // Move dest to point to the new node

}

12 — AlternatingSplit() SolutionThe simplest approach iterates over the source list and use MoveNode() to pull nodes offthe source and alternately put them on 'a' and b'. The only strange part is that the nodeswill be in the reverse order that they occurred in the source list.

AlternatingSplit()void AlternatingSplit(struct node* source,

struct node** aRef, struct node** bRef) {struct node* a = NULL; // Split the nodes to these 'a' and 'b' listsstruct node* b = NULL;

struct node* current = source;while (current != NULL) {

MoveNode(&a, &current); // Move a node to 'a'if (current != NULL) {

MoveNode(&b, &current); // Move a node to 'b'}

}*aRef = a;*bRef = b;

}

Page 125: Data Structures

27

AlternatingSplit() Using Dummy NodesHere is an alternative approach which builds the sub-lists in the same order as the sourcelist. The code uses a temporary dummy header nodes for the 'a' and 'b' lists as they arebeing built. Each sublist has a "tail" pointer which points to its current last node — thatway new nodes can be appended to the end of each list easily. The dummy nodes give thetail pointers something to point to initially. The dummy nodes are efficient in this casebecause they are temporary and allocated in the stack. Alternately, the "local references"technique could be used to get rid of the dummy nodes (see Section 1 for more details).

void AlternatingSplit2(struct node* source,struct node** aRef, struct node** bRef) {

struct node aDummy;struct node* aTail = &aDummy; // points to the last node in 'a'struct node bDummy;struct node* bTail = &bDummy; // points to the last node in 'b'struct node* current = source;

aDummy.next = NULL;bDummy.next = NULL;

while (current != NULL) {MoveNode(&(aTail->next), &current); // add at 'a' tailaTail = aTail->next; // advance the 'a' tailif (current != NULL) {

MoveNode(&(bTail->next), &current);bTail = bTail->next;

}}

*aRef = aDummy.next;*bRef = bDummy.next;

}

13 SuffleMerge() SolutionThere are four separate solutions included. See Section 1 for information on the variousdummy node and reference techniques.

SuffleMerge() — Dummy Node Not Using MoveNode()struct node* ShuffleMerge(struct node* a, struct node* b) {

struct node dummy;struct node* tail = &dummy;dummy.next = NULL;

while (1) {if (a==NULL) { // empty list cases

tail->next = b;break;

}else if (b==NULL) {

tail->next = a;break;

}else { // common case: move two nodes to tail

tail->next = a;tail = a;a = a->next;

Page 126: Data Structures

28

tail->next = b;tail = b;b = b->next;

}}

return(dummy.next);}

SuffleMerge() — Dummy Node Using MoveNode()Basically the same as above, but use MoveNode().

struct node* ShuffleMerge(struct node* a, struct node* b) {struct node dummy;struct node* tail = &dummy;dummy.next = NULL;

while (1) {if (a==NULL) {

tail->next = b;break;

}else if (b==NULL) {

tail->next = a;break;

}else {

MoveNode(&(tail->next), &a);tail = tail->next;MoveNode(&(tail->next), &b);tail = tail->next;

}}

return(dummy.next);}

SuffleMerge() — Local ReferencesUses a local reference to get rid of the dummy nodes entirely.

struct node* ShuffleMerge(struct node* a, struct node* b) {struct node* result = NULL;struct node** lastPtrRef = &result;

while (1) {if (a==NULL) {

*lastPtrRef = b;break;

}else if (b==NULL) {

*lastPtrRef = a;break;

}else {

MoveNode(lastPtrRef, &a);lastPtrRef = &((*lastPtrRef)->next);MoveNode(lastPtrRef, &b);lastPtrRef = &((*lastPtrRef)->next);

Page 127: Data Structures

29

}}

return(result);}

SuffleMerge() — RecursiveThe recursive solution is the most compact of all, but is probably not appropriate forproduction code since it uses stack space proportionate to the lengths of the lists.

struct node* ShuffleMerge(struct node* a, struct node* b) {struct node* result;struct node* recur;

if (a==NULL) return(b); // see if either list is emptyelse if (b==NULL) return(a);else {

// it turns out to be convenient to do the recursive call first --// otherwise a->next and b->next need temporary storage.

recur = ShuffleMerge(a->next, b->next);

result = a; // one node from aa->next = b; // one from bb->next = recur; // then the restreturn(result);

}}

14 — SortedMerge() SolutionSortedMerge() Using Dummy NodesThe strategy here uses a temporary dummy node as the start of the result list. The pointertail always points to the last node in the result list, so appending new nodes is easy.The dummy node gives tail something to point to initially when the result list is empty.This dummy node is efficient, since it is only temporary, and it is allocated in the stack.The loop proceeds, removing one node from either 'a' or 'b', and adding it to tail. Whenwe are done, the result is in dummy.next.

struct node* SortedMerge(struct node* a, struct node* b) {struct node dummy; // a dummy first node to hang the result onstruct node* tail = &dummy; // Points to the last result node --

// so tail->next is the place to add// new nodes to the result.

dummy.next = NULL;

while (1) {if (a == NULL) { // if either list runs out, use the other list

tail->next = b;break;

}else if (b == NULL) {

tail->next = a;break;

}

Page 128: Data Structures

30

if (a->data <= b->data) {MoveNode(&(tail->next), &a);

}else {

MoveNode(&(tail->next), &b);}tail = tail->next;

}

return(dummy.next);}

SortedMerge() Using Local ReferencesThis solution is structurally very similar to the above, but it avoids using a dummy node.Instead, it maintains a struct node** pointer, lastPtrRef, that always points to the lastpointer of the result list. This solves the same case that the dummy node did — dealingwith the result list when it is empty. If you are trying to build up a list at its tail, either thedummy node or the struct node** "reference" strategy can be used (see Section 1 fordetails).

struct node* SortedMerge2(struct node* a, struct node* b) {struct node* result = NULL;struct node** lastPtrRef = &result; // point to the last result pointer

while (1) {if (a==NULL) {

*lastPtrRef = b;break;

}else if (b==NULL) {

*lastPtrRef = a;break;

}

if (a->data <= b->data) {MoveNode(lastPtrRef, &a);

}else {

MoveNode(lastPtrRef, &b);}lastPtrRef = &((*lastPtrRef)->next); // tricky: advance to point to

// the next ".next" field

}

return(result);}

SortedMerge() Using RecursionMerge() is one of those nice recursive problems where the recursive solution code ismuch cleaner than the iterative code. You probably wouldn't want to use the recursiveversion for production code however, because it will use stack space which isproportional to the length of the lists.

struct node* SortedMerge3(struct node* a, struct node* b) {struct node* result = NULL;

Page 129: Data Structures

31

// Base casesif (a==NULL) return(b);else if (b==NULL) return(a);

// Pick either a or b, and recurif (a->data <= b->data) {

result = a;result->next = SortedMerge3(a->next, b);

}else {

result = b;result->next = SortedMerge3(a, b->next);

}

return(result);}

15 — MergeSort() SolutionThe MergeSort strategy is: split into sublists, sort the sublists recursively, merge the twosorted lists together to form the answer.

void MergeSort(struct node** headRef) {struct node* head = *headRef;struct node* a;struct node* b;

// Base case -- length 0 or 1if ((head == NULL) || (head->next == NULL)) {

return;}

FrontBackSplit(head, &a, &b); // Split head into 'a' and 'b' sublists// We could just as well use AlternatingSplit()

MergeSort(&a); // Recursively sort the sublistsMergeSort(&b);

*headRef = SortedMerge(a, b); // answer = merge the two sorted lists together}

(Extra for experts) Using recursive stack space proportional to the length of a list is notrecommended. However, the recursion in this case is ok — it uses stack space which isproportional to the log of the length of the list. For a 1000 node list, the recursion willonly go about 10 deep. For a 2000 node list, it will go 11 deep. If you think about it, youcan see that doubling the size of the list only increases the depth by 1.

16 — SortedIntersect() SolutionThe strategy is to advance up both lists and build the result list as we go. When thecurrent point in both lists are the same, add a node to the result. Otherwise, advancewhichever list is smaller. By exploiting the fact that both lists are sorted, we only traverseeach list once. To build up the result list, both the dummy node and local referencestrategy solutions are shown...

// This solution uses the temporary dummy to build up the result liststruct node* SortedIntersect(struct node* a, struct node* b) {

struct node dummy;

Page 130: Data Structures

32

struct node* tail = &dummy;

dummy.next = NULL;

// Once one or the other list runs out -- we're donewhile (a!=NULL && b!=NULL) {

if (a->data == b->data) {Push((&tail->next), a->data);tail = tail->next;a = a->next;b = b->next;

}else if (a->data < b->data) { // advance the smaller list

a = a->next;}else {

b = b->next;}

}

return(dummy.next);}

// This solution uses the local referencestruct node* SortedIntersect2(struct node* a, struct node* b) {

struct node* result = NULL;struct node** lastPtrRef = &result;

// Advance comparing the first nodes in both lists.// When one or the other list runs out, we're done.while (a!=NULL && b!=NULL) {

if (a->data == b->data) { // found a node for the intersectionPush(lastPtrRef, a->data);lastPtrRef = &((*lastPtrRef)->next);a=a->next;b=b->next;

}else if (a->data < b->data) { // advance the smaller list

a=a->next;}else {

b=b->next;}

}

return(result);}

17 — Reverse() SolutionThis first solution uses the "Push" strategy with the pointer re-arrangement hand codedinside the loop. There's a slight trickyness in that it needs to save the value of the"current->next" pointer at the top of the loop since the body of the loop overwrites thatpointer.

/* Iterative list reverse. Iterate through the list left-right.

Page 131: Data Structures

33

Move/insert each node to the front of the result list -- like a Push of the node.*/static void Reverse(struct node** headRef) {

struct node* result = NULL;struct node* current = *headRef;struct node* next;

while (current != NULL) {next = current->next; // tricky: note the next node

current->next = result; // move the node onto the resultresult = current;

current = next;}

*headRef = result;}

Here's the variation on the above that uses MoveNode() to do the work...

static void Reverse2(struct node** headRef) {struct node* result = NULL;struct node* current = *headRef;

while (current != NULL) {MoveNode(&result, &current);

}

*headRef = result;}

Finally, here's the back-middle-front strategy...

// Reverses the given linked list by changing its .next pointers and// its head pointer. Takes a pointer (reference) to the head pointer.void Reverse(struct node** headRef) {

if (*headRef != NULL) { // special case: skip the empty list

/* Plan for this loop: move three pointers: front, middle, back down the list in order. Middle is the main pointer running down the list. Front leads it and Back trails it. For each step, reverse the middle pointer and then advance all three to get the next node.*/

struct node* middle = *headRef; // the main pointer

struct node* front = middle->next; // the two other pointers (NULL ok)struct node* back = NULL;

while (1) {middle->next = back; // fix the middle node

if (front == NULL) break; // test if done

Page 132: Data Structures

34

back = middle; // advance the three pointersmiddle = front;front = front->next;

}

*headRef = middle; // fix the head pointer to point to the new front}

}

18 — RecursiveReverse() SolutionProbably the hardest part is accepting the concept that theRecursiveReverse(&rest) does in fact reverse the rest. Then then there's a trickto getting the one front node all the way to the end of the list. Make a drwaing to see howthe trick works.

void RecursiveReverse(struct node** headRef) {struct node* first;struct node* rest;

if (*headRef == NULL) return; // empty list base case

first = *headRef; // suppose first = {1, 2, 3}rest = first->next; // rest = {2, 3}

if (rest == NULL) return; // empty rest base case

RecursiveReverse(&rest); // Recursively reverse the smaller {2, 3} case// after: rest = {3, 2}

first->next->next = first; // put the first elem on the end of the listfirst->next = NULL; // (tricky step -- make a drawing)

*headRef = rest; // fix the head pointer}

The inefficient soluition is to reverse the last n-1 elements of the list, and then iterate allthe way down to the new tail and put the old head node there. That solution is very slowcompared to the above which gets the head node in the right place without extra iteration.

Page 133: Data Structures

35

AppendixBasic Utility Function ImplementationsHere is the source code for the basic utility functions.

Length()// Return the number of nodes in a listint Length(struct node* head) {

int count = 0;struct node* current = head;

while (current != NULL) {count++;current=current->next;

}

return(count);}

Push()// Given a reference (pointer to pointer) to the head// of a list and an int, push a new node on the front of the list.// Creates a new node with the int, links the list off the .next of the// new node, and finally changes the head to point to the new node.void Push(struct node** headRef, int newData) {

struct node* newNode =(struct node*) malloc(sizeof(struct node)); // allocate node

newNode->data = newData; // put in the datanewNode->next = (*headRef); // link the old list off the new node(*headRef) = newNode; // move the head to point to the new node

}

BuildOneTwoThree()// Build and return the list {1, 2, 3}struct node* BuildOneTwoThree() {

struct node* head = NULL; // Start with the empty listPush(&head, 3); // Use Push() to add all the dataPush(&head, 2);Push(&head, 1);

return(head);}

Page 134: Data Structures

Pointers andMemory

By Nick Parlante Copyright ©1998-2000, Nick Parlante

AbstractThis document explains how pointers and memory work and how to use them—from thebasic concepts through all the major programming techniques. For each topic there is acombination of discussion, sample C code, and drawings.

AudienceThis document can be used as an introduction to pointers for someone with basicprogramming experience. Alternately, it can be used to review and to fill in gaps forsomeone with a partial understanding of pointers and memory. Many advancedprogramming and debugging problems only make sense with a complete understandingof pointers and memory — this document tries to provide that understanding. Thisdocument concentrates on explaining how pointers work. For more advanced pointerapplications and practice problems, see the other resources below.

PaceLike most CS Education Library documents, the coverage here tries to be complete butfast. The document starts with the basics and advances through all the major topics. Thepace is fairly quick — each basic concept is covered once and usually there is someexample code and a memory drawing. Then the text moves on to the next topic. For morepractice, you can take the time to work through the examples and sample problems. Also,see the references below for more practice problems.

TopicsTopics include: pointers, local memory, allocation, deallocation, dereference operations,pointer assignment, deep vs. shallow copies, the ampersand operator (&), bad pointers,the NULL pointer, value parameters, reference parameters, heap allocation anddeallocation, memory ownership models, and memory leaks. The text focuses on pointersand memory in compiled languages like C and C++. At the end of each section, there issome related but optional material, and in particular there are occasional notes on otherlanguages, such as Java.

Pointers and Memory – document #102 in the Stanford CS Education Library. This andother free educational materials are available at http://cslibrary.stanford.edu/102/. Thisdocument is free to be used, reproduced, sold, or retransmitted so long as this notice isclearly reproduced at its beginning.

Other CS Education Library Documents• Point Fun With Binky Video (http://cslibrary.stanford.edu/104/)

A silly video about pointer basics.

• Linked list Basics (http://cslibrary.stanford.edu/103/)Introduces the basic techniques for building linked lists in C.

Page 135: Data Structures

2

• Linked List Problems (http://cslibrary.stanford.edu/105/)18 classic linked list problems with solutions — a great way to practicewith realistic, pointer intensive C code, and there's just no substitute forpractice!

• Essential C (http://cslibrary.stanford.edu/101/)Complete coverage of the C language, including all of the syntax used inthis document.

Table of ContentsSection 1 Basic Pointers.......................................................................... pg. 3

The basic rules and drawings for pointers: pointers, pointees, pointerassignment (=), pointer comparison (==), the ampersand operator (&), theNULL pointer, bad pointers, and bad dereferences.

Section 2 Local Memory ......................................................................... pg. 11How local variables and parameters work: local storage, allocation,deallocation, the ampersand bug. Understanding the separation of localmemory between separate functions.

Section 3 Reference Parameters.............................................................. pg. 17Combines the previous two sections to show how a function can use"reference parameters" to communicate back to its caller.

Section 4 Heap Memory ........................................................................ pg. 24Builds on all the previous sections to explain dynamic heap memory: heapallocation, heap deallocation, array allocation, memory ownership models,and memory leaks.

EditionThe first edition of this document was on Jan 19, 1999. This Feb 21, 2000 editionrepresents only very minor changes. The author may be reached [email protected]. The CS Education Library may be reached [email protected].

DedicationThis document is distributed for the benefit and education of all. That someone seekingeducation should have the opportunity to find it. May you learn from it in the spirit inwhich it is given — to make efficiency and beauty in your designs, peace and fairness inyour actions.

Preface To The First EditionThis article has appeared to hover at around 80% done for 6 months! Every time I addone section, I think of two more which also need to be written. I was motivated to keepworking on it since there are so many other articles which use memory, &, ... in passingwhere I wanted something to refer to. I hope you find it valuable in its current form. I'mgoing to ship it quickly before I accidentally start adding another section!

Page 136: Data Structures

3

Section 1 —Basic PointersPointers — Before and AfterThere's a lot of nice, tidy code you can write without knowing about pointers. But onceyou learn to use the power of pointers, you can never go back. There are too many thingsthat can only be done with pointers. But with increased power comes increasedresponsibility. Pointers allow new and more ugly types of bugs, and pointer bugs cancrash in random ways which makes them more difficult to debug. Nonetheless, even withtheir problems, pointers are an irresistibly powerful programming construct. (Thefollowing explanation uses the C language syntax where a syntax is required; there is adiscussion of Java at the section.)

Why Have Pointers?Pointers solve two common software problems. First, pointers allow different sections ofcode to share information easily. You can get the same effect by copying informationback and forth, but pointers solve the problem better. Second, pointers enable complex"linked" data structures like linked lists and binary trees.

What Is A Pointer?Simple int and float variables operate pretty intuitively. An int variable is like abox which can store a single int value such as 42. In a drawing, a simple variable is abox with its current value drawn inside.

num 42

A pointer works a little differently— it does not store a simple value directly. Instead, apointer stores a reference to another value. The variable the pointer refers to issometimes known as its "pointee". In a drawing, a pointer is a box which contains thebeginning of an arrow which leads to its pointee. (There is no single, official, word forthe concept of a pointee — pointee is just the word used in these explanations.)

The following drawing shows two variables: num and numPtr. The simple variable numcontains the value 42 in the usual way. The variable numPtr is a pointer which containsa reference to the variable num. The numPtr variable is the pointer and num is itspointee. What is stored inside of numPtr? Its value is not an int. Its value is areference to an int.

num 42

A pointer variable. The current value is a reference to the pointee num above.

A simple int variable. The current value is the integer 42. This variable also plays the role of pointee for the pointer below.

numPtr

Page 137: Data Structures

4

Pointer DereferenceThe "dereference" operation follows a pointer's reference to get the value of its pointee.The value of the dereference of numPtr above is 42. When the dereference operation isused correctly, it's simple. It just accesses the value of the pointee. The only restriction isthat the pointer must have a pointee for the dereference to access. Almost all bugs inpointer code involve violating that one restriction. A pointer must be assigned a pointeebefore dereference operations will work.

The NULL PointerThe constant NULL is a special pointer value which encodes the idea of "points tonothing." It turns out to be convenient to have a well defined pointer value whichrepresents the idea that a pointer does not have a pointee. It is a runtime error todereference a NULL pointer. In drawings, the value NULL is usually drawn as a diagonalline between the corners of the pointer variable's box...

numPtr

The C language uses the symbol NULL for this purpose. NULL is equal to the integerconstant 0, so NULL can play the role of a boolean false. Official C++ no longer uses theNULL symbolic constant — use the integer constant 0 directly. Java uses the symbolnull.

Pointer AssignmentThe assignment operation (=) between two pointers makes them point to the samepointee. It's a simple rule for a potentially complex situation, so it is worth repeating:assigning one pointer to another makes them point to the same thing. The example belowadds a second pointer, second, assigned with the statement second = numPtr;.The result is that second points to the same pointee as numPtr. In the drawing, thismeans that the second and numPtr boxes both contain arrows pointing to num.Assignment between pointers does not change or even touch the pointees. It just changeswhich pointee a pointer refers to.

num 42

numPtr

second

A second pointer ptr initialized with the assignment second = numPtr;. This causes second to refer to the same pointeee as numPtr.

After assignment, the == test comparing the two pointers will return true. For example(second==numPtr) above is true. The assignment operation also works with theNULL value. An assignment operation with a NULL pointer copies the NULL valuefrom one pointer to another.

Make A DrawingMemory drawings are the key to thinking about pointer code. When you are looking atcode, thinking about how it will use memory at run time....make a quick drawing to workout your ideas. This article certainly uses drawings to show how pointers work. That's theway to do it.

Page 138: Data Structures

5

SharingTwo pointers which both refer to a single pointee are said to be "sharing". That two ormore entities can cooperatively share a single memory structure is a key advantage ofpointers in all computer languages. Pointer manipulation is just technique — sharing isoften the real goal. In Section 3 we will see how sharing can be used to provide efficientcommunication between parts of a program.

Shallow and Deep CopyingIn particular, sharing can enable communication between two functions. One functionpasses a pointer to the value of interest to another function. Both functions can access thevalue of interest, but the value of interest itself is not copied. This communication iscalled "shallow" since instead of making and sending a (large) copy of the value ofinterest, a (small) pointer is sent and the value of interest is shared. The recipient needs tounderstand that they have a shallow copy, so they know not to change or delete it since itis shared. The alternative where a complete copy is made and sent is known as a "deep"copy. Deep copies are simpler in a way, since each function can change their copywithout interfering with the other copy, but deep copies run slower because of all thecopying.

The drawing below shows shallow and deep copying between two functions, A() and B().In the shallow case, the smiley face is shared by passing a pointer between the two. In thedeep case, the smiley face is copied, and each function gets their own...

A()

B()

Shallow / Sharing Deep / Copying

A()

B()

Section 2 will explain the above sharing technique in detail.

Bad PointersWhen a pointer is first allocated, it does not have a pointee. The pointer is "uninitialized"or simply "bad". A dereference operation on a bad pointer is a serious runtime error. Ifyou are lucky, the dereference operation will crash or halt immediately (Java behaves thisway). If you are unlucky, the bad pointer dereference will corrupt a random area ofmemory, slightly altering the operation of the program so that it goes wrong someindefinite time later. Each pointer must be assigned a pointee before it can supportdereference operations. Before that, the pointer is bad and must not be used. In ourmemory drawings, the bad pointer value is shown with an XXX value...

numPtr

Bad pointers are very common. In fact, every pointer starts out with a bad value.Correct code overwrites the bad value with a correct reference to a pointee, and thereafterthe pointer works fine. There is nothing automatic that gives a pointer a valid pointee.

Page 139: Data Structures

6

Quite the opposite — most languages make it easy to omit this important step. You justhave to program carefully. If your code is crashing, a bad pointer should be your firstsuspicion.

Pointers in dynamic languages such as Perl, LISP, and Java work a little differently. Therun-time system sets each pointer to NULL when it is allocated and checks it each time itis dereferenced. So code can still exhibit pointer bugs, but they will halt politely on theoffending line instead of crashing haphazardly like C. As a result, it is much easier tolocate and fix pointer bugs in dynamic languages. The run-time checks are also a reasonwhy such languages always run at least a little slower than a compiled language like C orC++.

Two LevelsOne way to think about pointer code is that operates at two levels — pointer level andpointee level. The trick is that both levels need to be initialized and connected for thingsto work. (1) the pointer must be allocated, (1) the pointee must be allocated, and (3) thepointer must be assigned to point to the pointee. It's rare to forget step (1). But forget (2)or (3), and the whole thing will blow up at the first dereference. Remember to account forboth levels — make a memory drawing during your design to make sure it's right.

SyntaxThe above basic features of pointers, pointees, dereferencing, and assigning are the onlyconcepts you need to build pointer code. However, in order to talk about pointer code, weneed to use a known syntax which is about as interesting as....a syntax. We will use the Clanguage syntax which has the advantage that it has influenced the syntaxes of severallanguages.

Pointer Type SyntaxA pointer type in C is just the pointee type followed by a asterisk (*)...

int* type: pointer to int

float* type: pointer to float

struct fraction* type: pointer to struct fraction

struct fraction** type: pointer to struct fraction*

Pointer VariablesPointer variables are declared just like any other variable. The declaration gives the typeand name of the new variable and reserves memory to hold its value. The declarationdoes not assign a pointee for the pointer — the pointer starts out with a bad value.

int* numPtr; // Declare the int* (pointer to int) variable "numPtr".// This allocates space for the pointer, but not the pointee.// The pointer starts out "bad".

Page 140: Data Structures

7

The & Operator — Reference ToThere are several ways to compute a reference to a pointee suitable for storing in apointer. The simplest way is the & operator. The & operator can go to the left of anyvariable, and it computes a reference to that variable. The code below uses a pointer andan & to produce the earlier num/numPtr example.

num 42

numPtr

void NumPtrExample() {int num;int* numPtr;

num = 42;numPtr = &num; // Compute a reference to "num", and store it in numPtr// At this point, memory looks like drawing above

}

It is possible to use & in a way which compiles fine but which creates problems at runtime — the full discussion of how to correctly use & is in Section 2. For now we will justuse & in a simple way.

The * Operator — DereferenceThe star operator (*) dereferences a pointer. The * is a unary operator which goes to theleft of the pointer it dereferences. The pointer must have a pointee, or it's a runtime error.

Example Pointer CodeWith the syntax defined, we can now write some pointer code that demonstrates all thepointer rules...

void PointerTest() {// allocate three integers and two pointersint a = 1;int b = 2;int c = 3;int* p;int* q;

// Here is the state of memory at this point.// T1 -- Notice that the pointers start out bad...

a 1

b 2

c 3

p

q

p = &a; // set p to refer to a

Page 141: Data Structures

8

q = &b; // set q to refer to b// T2 -- The pointers now have pointees

a 1

b 2

c 3

p

q

// Now we mix things up a bit...c = *p; // retrieve p's pointee value (1) and put it in cp = q; // change p to share with q (p's pointee is now b)*p = 13; // dereference p to set its pointee (b) to 13 (*q is now 13)// T3 -- Dereferences and assignments mix things up

a 1

b 13

c 1

p

q

}

Bad Pointer ExampleCode with the most common sort of pointer bug will look like the above correct code, butwithout the middle step where the pointers are assigned pointees. The bad code willcompile fine, but at run-time, each dereference with a bad pointer will corrupt memory insome way. The program will crash sooner or later. It is up to the programmer to ensurethat each pointer is assigned a pointee before it is used. The following example shows asimple example of the bad code and a drawing of how memory is likely to react...

void BadPointer() {int* p; // allocate the pointer, but not the pointee

*p = 42; // this dereference is a serious runtime error}// What happens at runtime when the bad pointer is dereferenced...

p

Pow!

Page 142: Data Structures

9

Pointer Rules SummaryNo matter how complex a pointer structure gets, the list of rules remains short.

• A pointer stores a reference to its pointee. The pointee, in turn, storessomething useful.

• The dereference operation on a pointer accesses its pointee. A pointer mayonly be dereferenced after it has been assigned to refer to a pointee. Mostpointer bugs involve violating this one rule.

• Allocating a pointer does not automatically assign it to refer to a pointee.Assigning the pointer to refer to a specific pointee is a separate operationwhich is easy to forget.

• Assignment between two pointers makes them refer to the same pointeewhich introduces sharing.

Section 1 — Extra Optional MaterialExtra: How Do Pointers Work In JavaJava has pointers, but they are not manipulated with explicit operators such as * and &. InJava, simple data types such as int and char operate just as in C. More complex typessuch as arrays and objects are automatically implemented using pointers. The languageautomatically uses pointers behind the scenes for such complex types, and no pointerspecific syntax is required. The programmer just needs to realize that operations likea=b; will automatically be implemented with pointers if a and b are arrays or objects. Orput another way, the programmer needs to remember that assignments and parameterswith arrays and objects are intrinsically shallow or shared— see the Deep vs. Shallowmaterial above. The following code shows some Java object references. Notice that thereare no *'s or &'s in the code to create pointers. The code intrinsically uses pointers. Also,the garbage collector (Section 4), takes care of the deallocation automatically at the endof the function.

public void JavaShallow() {Foo a = new Foo(); // Create a Foo object (no * in the declaration)Foo b = new Foo(); // Create another Foo object

b=a; // This is automatically a shallow assignment --// a and b now refer to the same object.

a.Bar(); // This could just as well be written b.Bar();

// There is no memory leak here -- the garbage collector// will automatically recycle the memory for the two objects.

}

The Java approach has two main features...

• Fewer bugs. Because the language implements the pointer manipulationaccurately and automatically, the most common pointer bug are no longerpossible, Yay! Also, the Java runtime system checks each pointer valueevery time it is used, so NULL pointer dereferences are caughtimmediately on the line where they occur. This can make a programmermuch more productive.

Page 143: Data Structures

10

• Slower. Because the language takes responsibility for implementing somuch pointer machinery at runtime, Java code runs slower than theequivalent C code. (There are other reasons for Java to run slowly as well.There is active research in making Java faser in interesting ways — theSun "Hot Spot" project.) In any case, the appeal of increased programmerefficiency and fewer bugs makes the slowness worthwhile for someapplications.

Extra: How Are Pointers Implemented In The Machine?How are pointers implemented? The short explanation is that every area of memory in themachine has a numeric address like 1000 or 20452. A pointer to an area of memory isreally just an integer which is storing the address of that area of memory. The dereferenceoperation looks at the address, and goes to that area of memory to retrieve the pointeestored there. Pointer assignment just copies the numeric address from one pointer toanother. The NULL value is generally just the numeric address 0 — the computer justnever allocates a pointee at 0 so that address can be used to represent NULL. A badpointer is really just a pointer which contains a random address — just like anuninitialized int variable which starts out with a random int value. The pointer has notyet been assigned the specific address of a valid pointee. This is why dereferenceoperations with bad pointers are so unpredictable. They operate on whatever random areaof memory they happen to have the address of.

Extra: The Term "Reference"The word "reference" means almost the same thing as the word "pointer". The differenceis that "reference" tends to be used in a discussion of pointer issues which is not specificto any particular language or implementation. The word "pointer" connotes the commonC/C++ implementation of pointers as addresses. The word "reference" is also used in thephrase "reference parameter" which is a technique which uses pointer parameters for two-way communication between functions — this technique is the subject of Section 3.

Extra: Why Are Bad Pointer Bugs So Common?Why is it so often the case that programmers will allocate a pointer, but forget to set it torefer to a pointee? The rules for pointers don't seem that complex, yet every programmermakes this error repeatedly. Why? The problem is that we are trained by the tools we use.Simple variables don't require any extra setup. You can allocate a simple variable, such asint, and use it immediately. All that int, char, struct fraction code you havewritten has trained you, quite reasonably, that a variable may be used once it is declared.Unfortunately, pointers look like simple variables but they require the extra initializationbefore use. It's unfortunate, in a way, that pointers happen look like other variables, sinceit makes it easy to forget that the rules for their use are very different. Oh well. Try toremember to assign your pointers to refer to pointees. Don't be surprised when you forget.

Page 144: Data Structures

11

Section 2 —Local MemoryThanks For The MemoryLocal variables are the programming structure everyone uses but no one thinks about.You think about them a little when first mastering the syntax. But after a few weeks, thevariables are so automatic that you soon forget to think about how they work. Thissituation is a credit to modern programming languages— most of the time variablesappear automatically when you need them, and they disappear automatically when youare finished. For basic programming, this is a fine situation. However, for advancedprogramming, it's going to be useful to have an idea of how variables work...

Allocation And DeallocationVariables represent storage space in the computer's memory. Each variable presents aconvenient names like length or sum in the source code. Behind the scenes at runtime,each variable uses an area of the computer's memory to store its value. It is not the casethat every variable in a program has a permanently assigned area of memory. Instead,modern languages are smart about giving memory to a variable only when necessary. Theterminology is that a variable is allocated when it is given an area of memory to store itsvalue. While the variable is allocated, it can operate as a variable in the usual way to holda value. A variable is deallocated when the system reclaims the memory from thevariable, so it no longer has an area to store its value. For a variable, the period of timefrom its allocation until its deallocation is called its lifetime.

The most common memory related error is using a deallocated variable. For localvariables, modern languages automatically protect against this error. With pointers, as wewill see however, the programmer must make sure that allocation is handled correctly..

Local MemoryThe most common variables you use are "local" variables within functions such as thevariables num and result in the following function. All of the local variables andparameters taken together are called its "local storage" or just its "locals", such as numand result in the following code...

// Local storage exampleint Square(int num) {

int result;

result = num * num;

return result;}

The variables are called "local" to capture the idea that their lifetime is tied to thefunction where they are declared. Whenever the function runs, its local variables areallocated. When the function exits, its locals are deallocated. For the above example, thatmeans that when the Square() function is called, local storage is allocated for num andresult. Statements like result = num * num; in the function use the localstorage. When the function finally exits, its local storage is deallocated.

Page 145: Data Structures

12

Here is a more detailed version of the rules of local storage...

1. When a function is called, memory is allocated for all of its locals. In otherwords, when the flow of control hits the starting '{' for the function, all ofits locals are allocated memory. Parameters such as num and localvariables such as result in the above example both count as locals. Theonly difference between parameters and local variables is that parametersstart out with a value copied from the caller while local variables start withrandom initial values. This article mostly uses simple int variables for itsexamples, however local allocation works for any type: structs, arrays...these can all be allocated locally.

2. The memory for the locals continues to be allocated so long as the threadof control is within the owning function. Locals continue to exist even ifthe function temporarily passes off the thread of control by calling anotherfunction. The locals exist undisturbed through all of this.

3. Finally, when the function finishes and exits, its locals are deallocated.This makes sense in a way — suppose the locals were somehow tocontinue to exist — how could the code even refer to them? The nameslike num and result only make sense within the body of Square()anyway. Once the flow of control leaves that body, there is no way to referto the locals even if they were allocated. That locals are available("scoped") only within their owning function is known as "lexicalscoping" and pretty much all languages do it that way now.

Small Locals ExampleHere is a simple example of the lifetime of local storage...

void Foo(int a) { // (1) Locals (a, b, i, scores) allocated when Foo runsint i;float scores[100]; // This array of 100 floats is allocated locally.

a = a + 1; // (2) Local storage is used by the computationfor (i=0; i<a; i++) {

Bar(i + a); // (3) Locals continue to exist undisturbed,} // even during calls to other functions.

} // (4) The locals are all deallocated when the function exits.

Large Locals ExampleHere is a larger example which shows how the simple rule "the locals are allocated whentheir function begins running and are deallocated when it exits" can build more complexbehavior. You will need a firm grasp of how local allocation works to understand thematerial in sections 3 and 4 later.

The drawing shows the sequence of allocations and deallocations which result when thefunction X() calls the function Y() twice. The points in time T1, T2, etc. are marked inthe code and the state of memory at that time is shown in the drawing.

Page 146: Data Structures

13

void X() {int a = 1;int b = 2;// T1

Y(a);// T3Y(b);

// T5

}

void Y(int p) {int q;q = p + 2;// T2 (first time through), T4 (second time through)

}

T1 - X()'s localshave beenallocated andgiven values..

T2 - Y() iscalled with p=1,and its localsare allocated.X()'s localscontinue to beallocated.

T3 - Y() exitsand its localsare deallocated.We are left onlywith X()'slocals.

T4 - Y() iscalled againwith p=2, andits locals areallocated asecond time.

T5 - Y() exitsand its localsare deallocated.X()'s locals willbe deallocatedwhen it exits.

1

3

p

qY()

2

4

p

qY()

1

2

a

bX()

1

2

a

bX()

1

2

a

bX()

1

2

a

bX()

1

2

a

bX()

(optional extra...) The drawing shows the sequence of the locals being allocated anddeallocated — in effect the drawing shows the operation over time of the "stack" which isthe data structure which the system uses to implement local storage.

Observations About Local ParametersLocal variables are tightly associated with their function — they are used there andnowhere else. Only the X() code can refer to its a and b. Only the Y() code can refer toits p and q. This independence of local storage is the root cause of both its advantagesand disadvantages.

Advantages Of LocalsLocals are great for 90% of a program's memory needs....

Convenient. Locals satisfy a convenient need — functions often needsome temporary memory which exists only during the function'scomputation. Local variables conveniently provide this sort of temporary,independent memory.

Efficient. Relative to other memory use techniques, locals are veryefficient. Allocating and deallocating them is time efficient (fast) and theyare space efficient in the way they use and recycle memory.

Page 147: Data Structures

14

Local Copies. Local parameters are basically local copies of theinformation from the caller. This is also known as "pass by value."Parameters are local variables which are initialized with an assignment (=)operation from the caller. The caller is not "sharing" the parameter valuewith the callee in the pointer sense— the callee is getting its own copy.This has the advantage that the callee can change its local copy withoutaffecting the caller. (Such as with the "p" parameter in the aboveexample.) This independence is good since it keeps the operation of thecaller and callee functions separate which follows the rules of goodsoftware engineering — keep separate components as independent aspossible.

Disadvantages Of LocalsThere are two disadvantages of Locals

Short Lifetime. Their allocation and deallocation schedule (their"lifetime") is very strict. Sometimes a program needs memory whichcontinues to be allocated even after the function which originally allocatedit has exited. Local variables will not work since they are deallocatedautomatically when their owning function exits. This problem will besolved later in Section 4 with "heap" memory.

Restricted Communication. Since locals are copies of the callerparameters, they do not provide a means of communication from the calleeback to the caller. This is the downside of the "independence" advantage.Also, sometimes making copies of a value is undesirable for other reasons.We will see the solution to this problem below in Section 3 "ReferenceParameters".

Synonyms For "Local"Local variables are also known as "automatic" variables since their allocation anddeallocation is done automatically as part of the function call mechanism. Local variablesare also sometimes known as "stack" variables because, at a low level, languages almostalways implement local variables using a stack structure in memory.

The Ampersand (&) Bug — TABNow that you understand the allocation schedule of locals, you can appreciate one of themore ugly bugs possible in C and C++. What is wrong with the following code where thefunction Victim() calls the function TAB()? To see the problem, it may be useful to makea drawing to trace the local storage of the two functions...

// TAB -- The Ampersand Bug function// Returns a pointer to an intint* TAB() {

int temp;return(&temp); // return a pointer to the local int

}

void Victim() {int* ptr;ptr = TAB();*ptr = 42; // Runtime error! The pointee was local to TAB

}

Page 148: Data Structures

15

TAB() is actually fine while it is running. The problem happens to its caller after TAB()exits. TAB() returns a pointer to an int, but where is that int allocated? The problem isthat the local int, temp, is allocated only while TAB() is running. When TAB() exits,all of its locals are deallocated. So the caller is left with a pointer to a deallocatedvariable. TAB()'s locals are deallocated when it exits, just as happened to the locals forY() in the previous example.

It is incorrect (and useless) for TAB() to return a pointer to memory which is about to bedeallocated. We are essentially running into the "lifetime" constraint of local variables.We want the int to exist, but it gets deallocated automatically. Not all uses of & betweenfunctions are incorrect — only when used to pass a pointer back to the caller. The correctuses of & are discussed in section 3, and the way to pass a pointer back to the caller isshown in section 4.

Local Memory SummaryLocals are very convenient for what they do — providing convenient and efficientmemory for a function which exists only so long as the function is executing. Locals havetwo deficiencies which we will address in the following sections — how a function cancommunicate back to its caller (Section 3), and how a function can allocate separatememory with a less constrained lifetime (section 4).

Section 2 — Extra Optional MaterialExtra: How Does The Function Call Stack Work?You do not need to know how local variables are implemented during a function call, buthere is a rough outline of the steps if you are curious. The exact details of theimplementation are language and compiler specific. However, the basic structure below isapproximates the method used by many different systems and languages...

To call a function such as foo(6, x+1)...

1. Evaluate the actual parameter expressions, such as the x+1, in the caller'scontext.

2. Allocate memory for foo()'s locals by pushing a suitable "local block" ofmemory onto a runtime "call stack" dedicated to this purpose. Forparameters but not local variables, store the values from step (1) into theappropriate slot in foo()'s local block.

3. Store the caller's current address of execution (its "return address") andswitch execution to foo().

4. foo() executes with its local block conveniently available at the end of thecall stack.

5. When foo() is finished, it exits by popping its locals off the stack and"returns" to the caller using the previously stored return address. Now thecaller's locals are on the end of the stack and it can resume executing.

Page 149: Data Structures

16

For the extremely curious, here are other miscellaneous notes on the function callprocess...

• This is why infinite recursion results in a "Stack Overflow Error" — thecode keeps calling and calling resulting in steps (1) (2) (3), (1) (2) (3), butnever a step (4)....eventually the call stack runs out of memory.

• This is why local variables have random initial values — step (2) justpushes the whole local block in one operation. Each local gets its own areaof memory, but the memory will contain whatever the most recent tenantleft there. To clear all of the local block for each function call would betoo time expensive.

• The "local block" is also known as the function's "activation record" or"stack frame". The entire block can be pushed onto the stack (step 2), in asingle CPU operation — it is a very fast operation.

• For a multithreaded environment, each thread gets its own call stackinstead of just having single, global call stack.

• For performance reasons, some languages pass some parameters throughregisters and others through the stack, so the overall process is complex.However, the apparent the lifetime of the variables will always follow the"stack" model presented here.

Page 150: Data Structures

17

Section 3 —Reference ParametersIn the simplest "pass by value" or "value parameter" scheme, each function has separate,local memory and parameters are copied from the caller to the callee at the moment of thefunction call. But what about the other direction? How can the callee communicate backto its caller? Using a "return" at the end of the callee to copy a result back to the callerworks for simple cases, but does not work well for all situations. Also, sometimescopying values back and forth is undesirable. "Pass by reference" parameters solve all ofthese problems.

For the following discussion, the term "value of interest" will be a value that the callerand callee wish to communicate between each other. A reference parameter passes apointer to the value of interest instead of a copy of the value of interest. This techniqueuses the sharing property of pointers so that the caller and callee can share the value ofinterest.

Bill Gates ExampleSuppose functions A() and B() both do computations involving Bill Gates' net worthmeasured in billions of dollars — the value of interest for this problem. A() is the mainfunction and its stores the initial value (about 55 as of 1998). A() calls B() which tries toadd 1 to the value of interest.

Bill Gates By ValueHere is the code and memory drawing for a simple, but incorrect implementation whereA() and B() use pass by value. Three points in time, T1, T2, and T3 are marked in thecode and the state of memory is shown for each state...

void B(int worth) {worth = worth + 1;// T2

}void A() {

int netWorth;netWorth = 55; // T1

B(netWorth);// T3 -- B() did not change netWorth

}

T1 -- The value of interestnetWorth is local to A().

T2 -- netWorth is copiedto B()'s local worth. B()changes its local worthfrom 55 to 56.

T3 -- B() exits and its localworth is deallocated. Thevalue of interest has notbeen changed.

A() 55netWorth A() 55netWorth

B() 55 56worth

A() 55netWorth

Page 151: Data Structures

18

B() adds 1 to its local worth copy, but when B() exits, worth is deallocated, sochanging it was useless. The value of interest, netWorth, rests unchanged the wholetime in A()'s local storage. A function can change its local copy of the value of interest,but that change is not reflected back in the original value. This is really just the old"independence" property of local storage, but in this case it is not what is wanted.

By ReferenceThe reference solution to the Bill Gates problem is to use a single netWorth variablefor the value of interest and never copy it. Instead, each function can receives a pointer tonetWorth. Each function can see the current value of netWorth by dereferencing itspointer. More importantly, each function can change the net worth — just dereferencethe pointer to the centralized netWorth and change it directly. Everyone agrees whatthe current value of netWorth because it exists in only one place — everyone has apointer to the one master copy. The following memory drawing shows A() and B()functions changed to use "reference" parameters. As before, T1, T2, and T3 correspond topoints in the code (below), but you can study the memory structure without looking at thecode yet.

T1 -- The value of interest,netWorth, is local to A()as before.

T2 -- Instead of a copy, B()receives a pointer tonetWorth. B()dereferences its pointer toaccess and change the realnetWorth.

T3 -- B() exits, andnetWorth has beenchanged.

A() 55netWorth A() 55 56netWorth

B() worth

A() 56netWorth

The reference parameter strategy: B() receives a pointer to the value of interest instead ofa copy.

Passing By ReferenceHere are the steps to use in the code to use the pass-by-reference strategy...

• Have a single copy of the value of interest. The single "master" copy.

• Pass pointers to that value to any function which wants to see or changethe value.

• Functions can dereference their pointer to see or change the value ofinterest.

• Functions must remember that they do not have their own local copies. Ifthey dereference their pointer and change the value, they really arechanging the master value. If a function wants a local copy to changesafely, the function must explicitly allocate and initialize such a localcopy.

Page 152: Data Structures

19

SyntaxThe syntax for by reference parameters in the C language just uses pointer operations onthe parameters...

1. Suppose a function wants to communicate about some value of interest —int or float or struct fraction.

2. The function takes as its parameter a pointer to the value of interest — anint* or float* or struct fraction*. Some programmers willadd the word "ref" to the name of a reference parameter as a reminder thatit is a reference to the value of interest instead of a copy.

3. At the time of the call, the caller computes a pointer to the value of interestand passes that pointer. The type of the pointer (pointer to the value ofinterest) will agree with the type in (2) above. If the value of interest islocal to the caller, then this will often involve a use of the & operator(Section 1).

4. When the callee is running, if it wishes to access the value of interest, itmust dereference its pointer to access the actual value of interest.Typically, this equates to use of the dereference operator (*) in thefunction to see the value of interest.

Bill Gates By ReferenceHere is the Bill Gates example written to use reference parameters. This code nowmatches the by-reference memory drawing above.

// B() now uses a reference parameter -- a pointer to// the value of interest. B() uses a dereference (*) on the// reference parameter to get at the value of interest.void B(int* worthRef) { // reference parameter

*worthRef = *worthRef + 1; // use * to get at value of interest// T2

}

void A() {int netWorth;netWorth = 55; // T1 -- the value of interest is local to A()

B(&netWorth); // Pass a pointer to the value of interest.// In this case using &.

// T3 -- B() has used its pointer to change the value of interest}

Don't Make CopiesReference parameters enable communication between the callee and its caller. Anotherreason to use reference parameters is to avoid making copies. For efficiency, makingcopies may be undesirable if the value of interest is large, such as an array. Making thecopy requires extra space for the copy itself and extra time to do the copying. From adesign point of view, making copies may be undesirable because as soon as there are twocopies, it is unclear which one is the "correct" one if either is changed. Proverb: "Aperson with one watch always knows what time it is. A person with two watches is neversure." Avoid making copies.

Page 153: Data Structures

20

Simple Reference Parameter Example — Swap()The standard example of reference parameters is a Swap() function which exchanges thevalues of two ints. It's a simple function, but it does need to change the caller's memorywhich is the key feature of pass by reference.

Swap() FunctionThe values of interest for Swap() are two ints. Therefore, Swap() does not take intsas its parameters. It takes a pointers to int — (int*)'s. In the body of Swap() theparameters, a and b, are dereferenced with * to get at the actual (int) values of interest.

void Swap(int* a, int* b) {int temp;

temp = *a;*a = *b;*b = temp;

}

Swap() CallerTo call Swap(), the caller must pass pointers to the values of interest...

void SwapCaller() {int x = 1;int y = 2;

Swap(&x, &y); // Use & to pass pointers to the int values of interest// (x and y).

}

ba temp 1

SwapCaller()

Swap()

2 1y1 2x

The parameters to Swap() are pointers to values of interest which are back in the caller'slocals. The Swap() code can dereference the pointers to get back to the caller's memory toexchange the values. In this case, Swap() follows the pointers to exchange the values inthe variables x and y back in SwapCaller(). Swap() will exchange any two ints givenpointers to those two ints.

Swap() With ArraysJust to demonstrate that the value of interest does not need to be a simple variable, here'sa call to Swap() to exchange the first and last ints in an array. Swap() takes int*'s, butthe ints can be anywhere. An int inside an array is still an int.

void SwapCaller2() {int scores[10];scores[0] = 1;scores[9[ = 2;Swap(&(scores[0]), &(scores[9]));// the ints of interest do not need to be

// simple variables -- they can be any int. The caller is responsible// for computing a pointer to the int.

Page 154: Data Structures

21

The above call to Swap() can be written equivalently as Swap(scores, scores+9)due to the array syntax in C. You can ignore this case if it is not familiar to you — it'snot an important area of the language and both forms compile to the exact same thinganyway.

Is The & Always Necessary?When passing by reference, the caller does not always need to use & to compute a newpointer to the value of interest. Sometimes the caller already has a pointer to the value ofinterest, and so no new pointer computation is required. The pointer to the value ofinterest can be passed through unchanged.

For example, suppose B() is changed so it calls a C() function which adds 2 to the valueof interest...

// Takes the value of interest by reference and adds 2.void C(int* worthRef) {

*worthRef = *worthRef + 2;}

// Adds 1 to the value of interest, and calls C().void B(int* worthRef) {

*worthRef = *worthRef + 1; // add 1 to value of interest as before

C(worthRef); // NOTE no & required. We already have// a pointer to the value of interest, so// it can be passed through directly.

}

What About The & Bug TAB?All this use of & might make you nervous — are we committing the & bug from Section2? No, it turns out the above uses of & are fine. The & bug happens when an & passes apointer to local storage from the callee back to its caller. When the callee exits, its localmemory is deallocated and so the pointer no longer has a pointee. In the above, correctcases, we use & to pass a pointer from the caller to the callee. The pointer remains validfor the callee to use because the caller locals continue to exist while the callee is running.The pointees will remain valid due to the simple constraint that the caller can only exitsometime after its callee exits. Using & to pass a pointer to local storage from the callerto the callee is fine. The reverse case, from the callee to the caller, is the & bug.

The ** CaseWhat if the value of interest to be shared and changed between the caller and callee isalready a pointer, such as an int* or a struct fraction*? Does that change therules for setting up reference parameters? No. In that case, there is no change in the rules.They operate just as before. The reference parameter is still a pointer to the value ofinterest, even if the value of interest is itself a pointer. Suppose the value of interest isint*. This means there is an int* value which the caller and callee want to share andchange. Then the reference parameter should be an int**. For a structfraction* value of interest, the reference parameter is struct fraction**. Asingle dereference (*) operation on the reference parameter yields the value of interest asit did in the simple cases. Double pointer (**) parameters are common in linked list orother pointer manipulating code were the value of interest to share and change is itself apointer, such as a linked list head pointer.

Page 155: Data Structures

22

Reference Parameter SummaryPassing by value (copying) does not allow the callee to communicate back to its callerand has also has the usual disadvantages of making copies. Pass by reference usespointers to avoid copying the value of interest, and allow the callee to communicate backto the caller.

For pass by reference, there is only one copy of the value of interest, and pointers to thatone copy are passed. So if the value of interest is an int, its reference parameter is an int*.If the value of interest is a struct fraction*, its reference parameters is a struct fraction**.Functions use the dereference operator (*) on the reference parameter to see or change thevalue of interest.

Section 3 — Extra Optional MaterialExtra: Reference Parameters in JavaBecause Java has no */& operators, it is not possible to implement reference parametersin Java directly. Maybe this is ok — in the OOP paradigm, you should change objects bysending them messages which makes the reference parameter concept unnecessary. Thecaller passes the callee a (shallow) reference to the value of interest (object of interest?),and the callee can send it a message to change it. Since all objects are intrinsicallyshallow, any change is communicated back to the caller automatically since the object ofinterest was never copied.

Extra: Reference Parameters in C++Reference parameters are such a common programming task that they have been added asan official feature to the C++ language. So programming reference parameters in C++ issimpler than in C. All the programmer needs to do is syntactically indicate that they wishfor a particular parameter to be passed by reference, and the compiler takes care of it. Thesyntax is to append a single '&' to right hand side of the parameter type. So an intparameter passes an integer by value, but an int& parameter passes an integer value byreference. The key is that the compiler takes care of it. In the source code, there's noadditional fiddling around with &'s or *'s. So Swap() and SwapCaller() written with C++look simpler than in C, even though they accomplish the same thing...

Page 156: Data Structures

23

void Swap(int& a, int& b) { // The & declares pass by referenceint temp;

temp = a; // No *'s required -- the compiler takes care of ita = b;b = temp;

}

void SwapCaller() {int x = 1;int y = 2;

Swap(x, y); // No &'s required -- the compiler takes care of it}

The types of the various variables and parameters operate simply as they are declared(int in this case). The complicating layer of pointers required to implement thereference parameters is hidden. The compiler takes care of it without allowing thecomplication to disturb the types in the source code.

Page 157: Data Structures

24

Section 4 —Heap Memory"Heap" memory, also known as "dynamic" memory, is an alternative to local stackmemory. Local memory (Section 2) is quite automatic — it is allocated automatically onfunction call and it is deallocated automatically when a function exits. Heap memory isdifferent in every way. The programmer explicitly requests the allocation of a memory"block" of a particular size, and the block continues to be allocated until the programmerexplicitly requests that it be deallocated. Nothing happens automatically. So theprogrammer has much greater control of memory, but with greater responsibility sincethe memory must now be actively managed. The advantages of heap memory are...

Lifetime. Because the programmer now controls exactly when memory isallocated and deallocated, it is possible to build a data structure inmemory, and return that data structure to the caller. This was neverpossible with local memory which was automatically deallocated when thefunction exited.

Size. The size of allocated memory can be controlled with more detail.For example, a string buffer can be allocated at run-time which is exactlythe right size to hold a particular string. With local memory, the code ismore likely to declare a buffer size 1000 and hope for the best. (See theStringCopy() example below.)

The disadvantages of heap memory are...

More Work. Heap allocation needs to arranged explicitly in the codewhich is just more work.

More Bugs. Because it's now done explicitly in the code, realistically onoccasion the allocation will be done incorrectly leading to memory bugs.Local memory is constrained, but at least it's never wrong.

Nonetheless, there are many problems that can only be solved with heap memory, sothat's that way it has to be. In languages with garbage collectors such as Perl, LISP, orJava, the above disadvantages are mostly eliminated. The garbage collector takes overmost of the responsibility for heap management at the cost of a little extra time taken atrun-time.

What Does The Heap Look Like?Before seeing the exact details, let's look at a rough example of allocation anddeallocation in the heap...

AllocationThe heap is a large area of memory available for use by the program. The program canrequest areas, or "blocks", of memory for its use within the heap. In order to allocate ablock of some size, the program makes an explicit request by calling the heap allocationfunction. The allocation function reserves a block of memory of the requested size in theheap and returns a pointer to it. Suppose a program makes three allocation requests to

Page 158: Data Structures

25

allocate memory to hold three separate GIF images in the heap each of which takes 1024bytes of memory. After the three allocation requests, memory might look like...

Local Heap

(Free)

(Gif1)

(Gif2)

(Gif3)3 separate heap blocks — each 1024 bytes in size.

Each allocation request reserves a contiguous area of the requested size in the heap andreturns a pointer to that new block to the program. Since each block is always referred toby a pointer, the block always plays the role of a "pointee" (Section 1) and the programalways manipulates its heap blocks through pointers. The heap block pointers aresometimes known as "base address" pointers since by convention they point to the base(lowest address byte) of the block.

In this example, the three blocks have been allocated contiguously starting at the bottomof the heap, and each block is 1024 bytes in size as requested. In reality, the heapmanager can allocate the blocks wherever it wants in the heap so long as the blocks donot overlap and they are at least the requested size. At any particular moment, some areasin the heap have been allocated to the program, and so are "in use". Other areas have yetto be committed and so are "free" and are available to satisfy allocation requests. Theheap manager has its own, private data structures to record what areas of the heap arecommitted to what purpose at any moment The heap manager satisfies each allocationrequest from the pool of free memory and updates its private data structures to recordwhich areas of the heap are in use.

DeallocationWhen the program is finished using a block of memory, it makes an explicit deallocationrequest to indicate to the heap manager that the program is now finished with that block.The heap manager updates its private data structures to show that the area of memoryoccupied by the block is free again and so may be re-used to satisfy future allocationrequests. Here's what the heap would look like if the program deallocates the second ofthe three blocks...

Page 159: Data Structures

26

Local Heap

(Free)

(Gif1)

(Gif3)

(Free)

After the deallocation, the pointer continues to point to the now deallocated block. Theprogram must not access the deallocated pointee. This is why the pointer is drawn in gray— the pointer is there, but it must not be used. Sometimes the code will set the pointer toNULL immediately after the deallocation to make explicit the fact that it is no longervalid.

Programming The HeapProgramming the heap looks pretty much the same in most languages. The basic featuresare....

• The heap is an area of memory available to allocate areas ("blocks") ofmemory for the program.

• There is some "heap manager" library code which manages the heap forthe program. The programmer makes requests to the heap manager, whichin turn manages the internals of the heap. In C, the heap is managed by theANSI library functions malloc(), free(), and realloc().

• The heap manager uses its own private data structures to keep track ofwhich blocks in the heap are "free" (available for use) and which blocksare currently in use by the program and how large those blocks are.Initially, all of the heap is free.

• The heap may be of a fixed size (the usual conceptualization), or it mayappear to be of a fixed but extremely large size backed by virtual memory.In either case, it is possible for the heap to get "full" if all of its memoryhas been allocated and so it cannot satisfy an allocation request. Theallocation function will communicate this run-time condition in some wayto the program — usually by returning a NULL pointer or raising alanguage specific run-time exception.

• The allocation function requests a block in the heap of a particular size.The heap manager selects an area of memory to use to satisfy the request,marks that area as "in use" in its private data structures, and returns apointer to the heap block. The caller is now free to use that memory bydereferencing the pointer. The block is guaranteed to be reserved for thesole use of the caller — the heap will not hand out that same area ofmemory to some other caller. The block does not move around inside the

Page 160: Data Structures

27

heap — its location and size are fixed once it is allocated. Generally, whena block is allocated, its contents are random. The new owner is responsiblefor setting the memory to something meaningful. Sometimes there isvariation on the memory allocation function which sets the block to allzeros (calloc() in C).

• The deallocation function is the opposite of the allocation function. Theprogram makes a single deallocation call to return a block of memory tothe heap free area for later re-use. Each block should only be deallocatedonce. The deallocation function takes as its argument a pointer to a heapblock previously furnished by the allocation function. The pointer must beexactly the same pointer returned earlier by the allocation function, notjust any pointer into the block. After the deallocation, the program musttreat the pointer as bad and not access the deallocated pointee.

C SpecificsIn the C language, the library functions which make heap requests are malloc() ("memoryallocate") and free(). The prototypes for these functions are in the header file <stdlib.h>.Although the syntax varies between languages, the roles of malloc() and free() are nearlyidentical in all languages...

void* malloc(unsigned long size); The malloc() functiontakes an unsigned integer which is the requested size of the blockmeasured in bytes. Malloc() returns a pointer to a new heap block if theallocation is successful, and NULL if the request cannot be satisfiedbecause the heap is full. The C operator sizeof() is a convenient way tocompute the size in bytes of a type —sizeof(int) for an int pointee,sizeof(struct fraction) for a struct fraction pointee.

void free(void* heapBlockPointer); The free() functiontakes a pointer to a heap block and returns it to the free pool for later re-use. The pointer passed to free() must be exactly the pointer returnedearlier by malloc(), not just a pointer to somewhere in the block. Callingfree() with the wrong sort of pointer is famous for the particularly uglysort of crashing which it causes. The call to free() does not need to givethe size of the heap block — the heap manager will have noted the size inits private data structures. The call to free() just needs to identify whichblock to deallocate by its pointer. If a program correctly deallocates all ofthe memory it allocates, then every call to malloc() will later be matchedby exactly one call to free() As a practical matter however, it is not alwaysnecessary for a program to deallocate every block it allocates — see"Memory Leaks" below.

Simple Heap ExampleHere is a simple example which allocates an int block in the heap, stores the number 42in the block, and then deallocates it. This is the simplest possible example of heap blockallocation, use, and deallocation. The example shows the state of memory at threedifferent times during the execution of the above code. The stack and heap are shownseparately in the drawing — a drawing for code which uses stack and heap memory needsto distinguish between the two areas to be accurate since the rules which govern the twoareas are so different. In this case, the lifetime of the local variable intPtr is totallyseparate from the lifetime of the heap block, and the drawing needs to reflect thatdifference.

Page 161: Data Structures

28

void Heap1() {int* intPtr;// Allocates local pointer local variable (but not its pointee)// T1

Local Heap

intPtr

// Allocates heap block and stores its pointer in local variable.// Dereferences the pointer to set the pointee to 42.intPtr = malloc(sizeof(int));*intPtr = 42;// T2

Local Heap

intPtr 42

// Deallocates heap block making the pointer bad.// The programmer must remember not to use the pointer// after the pointee has been deallocated (this is// why the pointer is shown in gray).free(intPtr);// T3

Local Heap

intPtr

}

Simple Heap Observations• After the allocation call allocates the block in the heap. The program

stores the pointer to the block in the local variable intPtr. The block is the"pointee" and intPtr is its pointer as shown at T2. In this state, the pointermay be dereferenced safely to manipulate the pointee. The pointer/pointeerules from Section 1 still apply, the only difference is how the pointee isinitially allocated.

Page 162: Data Structures

29

• At T1 before the call to malloc(), intPtr is uninitialized does not have apointee — at this point intPtr "bad" in the same sense as discussed inSection 1. As before, dereferencing such an uninitialized pointer is acommon, but catastrophic error. Sometimes this error will crashimmediately (lucky). Other times it will just slightly corrupt a random datastructure (unlucky).

• The call to free() deallocates the pointee as shown at T3. Dereferencingthe pointer after the pointee has been deallocated is an error.Unfortunately, this error will almost never be flagged as an immediaterun-time error. 99% of the time the dereference will produce reasonableresults 1% of the time the dereference will produce slightly wrong results.Ironically, such a rarely appearing bug is the most difficult type to trackdown.

• When the function exits, its local variable intPtr will be automaticallydeallocated following the usual rules for local variables (Section 2). Sothis function has tidy memory behavior — all of the memory it allocateswhile running (its local variable, its one heap block) is deallocated by thetime it exits.

Heap ArrayIn the C language, it's convenient to allocate an array in the heap, since C can treat anypointer as an array. The size of the array memory block is the size of each element (ascomputed by the sizeof() operator) multiplied by the number of elements (See CSEducation Library/101 The C Language, for a complete discussion of C, and arrays andpointers in particular). So the following code heap allocates an array of 100 structfraction's in the heap, sets them all to 22/7, and deallocates the heap array...

void HeapArray() {struct fraction* fracts;int i;

// allocate the arrayfracts = malloc(sizeof(struct fraction) * 100);

// use it like an array -- in this case set them all to 22/7for (i=0; i<99; i++) {

fracts[i].numerator = 22;fracts[i].denominator = 7;

}

// Deallocate the whole arrayfree(fracts);

}

Page 163: Data Structures

30

Heap String ExampleHere is a more useful heap array example. The StringCopy() function takes a C string,makes a copy of that string in the heap, and returns a pointer to the new string. The callertakes over ownership of the new string and is responsible for freeing it.

/* Given a C string, return a heap allocated copy of the string. Allocate a block in the heap of the appropriate size, copies the string into the block, and returns a pointer to the block. The caller takes over ownership of the block and is responsible for freeing it.*/char* StringCopy(const char* string) {

char* newString;int len;

len = strlen(string) + 1; // +1 to account for the '\0'newString = malloc(sizeof(char)*len); // elem-size * number-of-elementsassert(newString != NULL); // simplistic error check (a good habit)strcpy(newString, string); // copy the passed in string to the block

return(newString); // return a ptr to the block}

Heap String ObservationsStringCopy() takes advantage of both of the key features of heap memory...

Size. StringCopy() specifies, at run-time, the exact size of the blockneeded to store the string in its call to malloc(). Local memory cannot dothat since its size is specified at compile-time. The call tosizeof(char) is not really necessary, since the size of char is 1 bydefinition. In any case, the example demonstrates the correct formula forthe size of an array block which is element-size * number-of-elements.

Lifetime. StringCopy() allocates the block, but then passes ownership of itto the caller. There is no call to free(), so the block continues to exist evenafter the function exits. Local memory cannot do that. The caller will needto take care of the deallocation when it is finished with the string.

Memory LeaksWhat happens if some memory is heap allocated, but never deallocated? A programwhich forgets to deallocate a block is said to have a "memory leak" which may or maynot be a serious problem. The result will be that the heap gradually fill up as therecontinue to be allocation requests, but no deallocation requests to return blocks for re-use.For a program which runs, computes something, and exits immediately, memory leaksare not usually a concern. Such a "one shot" program could omit all of its deallocationrequests and still mostly work. Memory leaks are more of a problem for a program whichruns for an indeterminate amount of time. In that case, the memory leaks can graduallyfill the heap until allocation requests cannot be satisfied, and the program stops workingor crashes. Many commercial programs have memory leaks, so that when run for longenough, or with large data-sets, they fill their heaps and crash. Often the error detectionand avoidance code for the heap-full error condition is not well tested, precisely becausethe case is rarely encountered with short runs of the program — that's why filling theheap often results in a real crash instead of a polite error message. Most compilers have a

Page 164: Data Structures

31

"heap debugging" utility which adds debugging code to a program to track everyallocation and deallocation. When an allocation has no matching deallocation, that's aleak, and the heap debugger can help you find them.

OwnershipStringCopy() allocates the heap block, but it does not deallocate it. This is so the callercan use the new string. However, this introduces the problem that somebody does need toremember to deallocate the block, and it is not going to be StringCopy(). That is why thecomment for StringCopy() mentions specifically that the caller is taking on ownership ofthe block. Every block of memory has exactly one "owner" who takes responsibility fordeallocating it. Other entities can have pointers, but they are just sharing. There's onlyone owner, and the comment for StringCopy() makes it clear that ownership is beingpassed from StringCopy() to the caller. Good documentation always remembers todiscuss the ownership rules which a function expects to apply to its parameters or returnvalue. Or put the other way, a frequent error in documentation is that it forgets tomention, one way or the other, what the ownership rules are for a parameter or returnvalue. That's one way that memory errors and leaks are created.

Ownership ModelsThe two common patterns for ownership are...

Caller ownership. The caller owns its own memory. It may pass a pointerto the callee for sharing purposes, but the caller retains ownership. Thecallee can access things while it runs, and allocate and deallocate its ownmemory, but it should not disrupt the caller's memory.

Callee allocated and returned. The callee allocates some memory andreturns it to the caller. This happens because the result of the calleecomputation needs new memory to be stored or represented. The newmemory is passed to the caller so they can see the result, and the callermust take over ownership of the memory. This is the pattern demonstratedin StringCopy().

Heap Memory SummaryHeap memory provides greater control for the programmer — the blocks of memory canbe requested in any size, and they remain allocated until they are deallocated explicitly.Heap memory can be passed back to the caller since it is not deallocated on exit, and itcan be used to build linked structures such as linked lists and binary trees. Thedisadvantage of heap memory is that the program must make explicit allocation anddeallocate calls to manage the heap memory. The heap memory does not operateautomatically and conveniently the way local memory does.

Page 165: Data Structures

Tree List Recursion Problem Page: 1

http://cslibrary.stanford.edu/109/TreeListRecursion.html

The Great Tree-List Recursion Problemby Nick Parlante

[email protected] Copyright 2000, Nick Parlante

This article presents one of the neatest recursive pointer problems ever devised. This an advanced problem thatuses pointers, binary trees, linked lists, and some significant recursion. This article includes the problem statement,a few explanatory diagrams, and sample solution code in Java and C. Thanks to Stuart Reges for originally showingme the problem.

Stanford CS Education Library Doc #109

This is article #109 in the Stanford CS Education Library -- http://cslibrary.stanford.edu/109/. This and other freeeducational materials are available at http://cslibrary.stanford.edu/. Permission is given for this article to beused, reproduced, or sold so long this paragraph and the copyright are clearly reproduced. Related articles in thelibrary include Linked List Basics (#103), Linked List Problems (#105), and Binary Trees (#110).

Contents

1. Ordered binary tree 2. Circular doubly linked list 3. The Challenge 4. Problem Statement 5. Lessons and Solution Code

Introduction

The problem will use two data structures -- an ordered binary tree and a circular doubly linked list. Both datastructures store sorted elements, but they look very different.

1. Ordered Binary Tree

In the ordered binary tree, each node contains a single data element and "small" and "large" pointers to sub-trees(sometimes the two pointers are just called "left" and "right"). Here's an ordered binary tree of the numbers 1through 5...

Page 166: Data Structures

Tree List Recursion Problem Page: 2

http://cslibrary.stanford.edu/109/TreeListRecursion.html

Figure-1 -- ordered binary tree

All the nodes in the "small" sub-tree are less than or equal to the data in the parent node. All the nodes in the"large" sub-tree are greater than the parent node. So in the example above, all the nodes in the "small" sub-tree offthe 4 node are less than or equal to 4, and all the nodes in "large" sub-tree are greater than 4. That pattern appliesfor each node in the tree. A null pointer effectively marks the end of a branch in the tree. Formally, a null pointerrepresents a tree with zero elements. The pointer to the topmost node in a tree is called the "root".

2. Circular Doubly Linked List

Here's a circular doubly linked list of the numbers 1 through 5...

Page 167: Data Structures

Tree List Recursion Problem Page: 3

http://cslibrary.stanford.edu/109/TreeListRecursion.html

Figure-2 -- doubly linked circular list

The circular doubly linked list is a standard linked list with two additional features...

"Doubly linked" means that each node has two pointers -- the usual "next" pointer that points to the nextnode in the list and a "previous" pointer to the previous node. "Circular" means that the list does not terminate at the first and last nodes. Instead, the "next" from thelast node wraps around to the first node. Likewise, the "previous" from the first node wraps around to thelast node.

We'll use the convention that a null pointer represents a list with zero elements. It turns out that a length-1 listlooks a little silly...

Figure-3 -- a length-1 circular doubly linked list

The single node in a length-1 list is both the first and last node, so its pointers point to itself. Fortunately, thelength-1 case obeys the rules above so no special case is required.

The Trick -- Separated at Birth?

Here's the trick that underlies the Great Tree-List Problem: look at the nodes that make up the ordered binarytree. Now look at the nodes that make up the linked list. The nodes have the same type structure -- they eachcontain an element and two pointers. The only difference is that in the tree, the two pointers are labeled "small"

Page 168: Data Structures

Tree List Recursion Problem Page: 4

http://cslibrary.stanford.edu/109/TreeListRecursion.html

and "large" while in the list they are labeled "previous" and "next". Ignoring the labeling, the two node types arethe same.

3. The Challenge

The challenge is to take an ordered binary tree and rearrange the internal pointers to make a circular doubly linkedlist out of it. The "small" pointer should play the role of "previous" and the "large" pointer should play the role of"next". The list should be arranged so that the nodes are in increasing order...

Figure-4 -- original tree with list "next" arrows added

This drawing shows the original tree drawn with plain black lines with the "next" pointers for the desired liststructure drawn as arrows. The "previous" pointers are not shown.

Complete Drawing

Page 169: Data Structures

Tree List Recursion Problem Page: 5

http://cslibrary.stanford.edu/109/TreeListRecursion.html

Figure-5 -- original tree with "next" and "previous" list arrows added

This drawing shows the all of the problem state -- the original tree is drawn with plain black lines and thedesired next/previous pointers are added in as arrows. Notice that starting with the head pointer, the structure ofnext/previous pointers defines a list of the numbers 1 through 5 with exactly the same structure as the list infigure-2. Although the nodes appear to have different spatial arrangement between the two drawings, that's justan artifact of the drawing. The structure defined by the the pointers is what matters.

4. Problem Statement

Here's the formal problem statement: Write a recursive function treeToList(Node root) that takes an orderedbinary tree and rearranges the internal pointers to make a circular doubly linked list out of the tree nodes. The"previous" pointers should be stored in the "small" field and the "next" pointers should be stored in the "large"field. The list should be arranged so that the nodes are in increasing order. Return the head pointer to the new list.The operation can be done in O(n) time -- essentially operating on each node once. Basically take figure-1 as inputand rearrange the pointers to make figure-2.

Try the problem directly, or see the hints below.

Hints

Page 170: Data Structures

Tree List Recursion Problem Page: 6

http://cslibrary.stanford.edu/109/TreeListRecursion.html

Hint #1

The recursion is key. Trust that the recursive call on each sub-tree works and concentrate on assembling the outputsof the recursive calls to build the result. It's too complex to delve into how each recursive call is going to work --trust that it did work and assemble the answer from there.

Hint #2

The recursion will go down the tree, recursively changing the small and large sub-trees into lists, and then appendthose lists together with the parent node to make larger lists. Separate out a utility function append(Node a,Node b) that takes two circular doubly linked lists and appends them together to make one list which is returned.Writing a separate utility function helps move some of the complexity out of the recursive function.

5. Lessons and Solution Code

The solution code is given below in Java and C. The most important method is treeToList() and the helper methodsjoin() and append(). Here are the lessons I see in the two solutions...

Trust that the recursive calls return correct output when fed correct input -- make the leap of faith. Look atthe partial results that the recursive calls give you, and construct the full result from them. If you try tostep into the recursive calls to think how they are working, you'll go crazy. Decomposing out well defined helper functions is a good idea. Writing the list-append code separatelyhelps you concentrate on the recursion which is complex enough on its own.

Java Solution Code

// TreeList.java/* Demonstrates the greatest recursive pointer problem ever -- recursively changing an ordered binary tree into a circular doubly linked list. See http://cslibrary.stanford.edu/109/ This code is not especially OOP.

This code is free for any purpose. Feb 22, 2000 Nick Parlante [email protected]*/

/* This is the simple Node class from which the tree and list are built. This does not have any methods -- it's just used as dumb storage by TreeList. The code below tries to be clear where it treats a Node pointer as a tree vs. where it is treated as a list.*/class Node { int data; Node small; Node large; public Node(int data) {

Page 171: Data Structures

Tree List Recursion Problem Page: 7

http://cslibrary.stanford.edu/109/TreeListRecursion.html

this.data = data; small = null; large = null; }}

/* TreeList main methods: -join() -- utility to connect two list nodes -append() -- utility to append two lists -treeToList() -- the core recursive function -treeInsert() -- used to build the tree*/class TreeList { /* helper function -- given two list nodes, join them together so the second immediately follow the first. Sets the .next of the first and the .previous of the second. */ public static void join(Node a, Node b) { a.large = b; b.small = a; }

/* helper function -- given two circular doubly linked lists, append them and return the new list. */ public static Node append(Node a, Node b) { // if either is null, return the other if (a==null) return(b); if (b==null) return(a); // find the last node in each using the .previous pointer Node aLast = a.small; Node bLast = b.small; // join the two together to make it connected and circular join(aLast, b); join(bLast, a); return(a); }

/* --Recursion-- Given an ordered binary tree, recursively change it into a circular doubly linked list which is returned. */ public static Node treeToList(Node root) { // base case: empty tree -> empty list if (root==null) return(null); // Recursively do the subtrees (leap of faith!) Node aList = treeToList(root.small); Node bList = treeToList(root.large); // Make the single root node into a list length-1 // in preparation for the appending

Page 172: Data Structures

Tree List Recursion Problem Page: 8

http://cslibrary.stanford.edu/109/TreeListRecursion.html

root.small = root; root.large = root; // At this point we have three lists, and it's // just a matter of appending them together // in the right order (aList, root, bList) aList = append(aList, root); aList = append(aList, bList); return(aList); }

/* Given a non-empty tree, insert a new node in the proper place. The tree must be non-empty because Java's lack of reference variables makes that case and this method messier than they should be. */ public static void treeInsert(Node root, int newData) { if (newData<=root.data) { if (root.small!=null) treeInsert(root.small, newData); else root.small = new Node(newData); } else { if (root.large!=null) treeInsert(root.large, newData); else root.large = new Node(newData); } } // Do an inorder traversal to print a tree // Does not print the ending "\n" public static void printTree(Node root) { if (root==null) return; printTree(root.small); System.out.print(Integer.toString(root.data) + " "); printTree(root.large); } // Do a traversal of the list and print it out public static void printList(Node head) { Node current = head; while (current != null) { System.out.print(Integer.toString(current.data) + " "); current = current.large; if (current == head) break; } System.out.println(); } // Demonstrate tree->list with the list 1..5 public static void main(String[] args) { // first build the tree shown in the problem document // http://cslibrary.stanford.edu/109/ Node root = new Node(4); treeInsert(root, 2);

Page 173: Data Structures

Tree List Recursion Problem Page: 9

http://cslibrary.stanford.edu/109/TreeListRecursion.html

treeInsert(root, 1); treeInsert(root, 3); treeInsert(root, 5); System.out.println("tree:"); printTree(root); // 1 2 3 4 5 System.out.println(); System.out.println("list:"); Node head = treeToList(root); printList(head); // 1 2 3 4 5 yay! }}

C Solution Code

/* TreeList.c C code version of the great Tree-List recursion problem. See http://cslibrary.stanford.edu/109/ for the full discussion and the Java solution. This code is free for any purpose. Feb 22, 2000 Nick Parlante [email protected]*/

#include <stdio.h>#include <stddef.h>#include <stdlib.h>

/* The node type from which both the tree and list are built */struct node { int data; struct node* small; struct node* large;};typedef struct node* Node;

/* helper function -- given two list nodes, join them together so the second immediately follow the first. Sets the .next of the first and the .previous of the second.*/static void join(Node a, Node b) { a->large = b; b->small = a;}

/* helper function -- given two circular doubly linked lists, append them and return the new list.*/static Node append(Node a, Node b) {

Page 174: Data Structures

Tree List Recursion Problem Page: 10

http://cslibrary.stanford.edu/109/TreeListRecursion.html

Node aLast, bLast; if (a==NULL) return(b); if (b==NULL) return(a); aLast = a->small; bLast = b->small; join(aLast, b); join(bLast, a); return(a);}

/* --Recursion-- Given an ordered binary tree, recursively change it into a circular doubly linked list which is returned.*/static Node treeToList(Node root) { Node aList, bList; if (root==NULL) return(NULL);

/* recursively solve subtrees -- leap of faith! */ aList = treeToList(root->small); bList = treeToList(root->large); /* Make a length-1 list ouf of the root */ root->small = root; root->large = root;

/* Append everything together in sorted order */ aList = append(aList, root); aList = append(aList, bList); return(aList);

/* Create a new node */static Node newNode(int data) { Node node = (Node) malloc(sizeof(struct node)); node->data = data; node->small = NULL; node->large = NULL; return(node);}

/* Add a new node into a tree */static void treeInsert(Node* rootRef, int data) { Node root = *rootRef; if (root == NULL) *rootRef = newNode(data); else { if (data <= root->data) treeInsert(&(root->small), data); else treeInsert(&(root->large), data); }}

Page 175: Data Structures

Tree List Recursion Problem Page: 11

http://cslibrary.stanford.edu/109/TreeListRecursion.html

static void printList(Node head) { Node current = head; while(current != NULL) { printf("%d ", current->data); current = current->large; if (current == head) break; } printf("\n");}

/* Demo that the code works */int main() { Node root = NULL; Node head; treeInsert(&root, 4); treeInsert(&root, 2); treeInsert(&root, 1); treeInsert(&root, 3); treeInsert(&root, 5); head = treeToList(root); printList(head); /* prints: 1 2 3 4 5 */ return(0);}

Page 176: Data Structures

UnixProgramming

ToolsBy Parlante, Zelenski, and many others Copyright ©1998-2001, Stanford University

IntroductionThis article explains the overall edit-compile-link-debug programming cycle andintroduces several common Unix programming tools -- gcc, make, gdb, emacs, and theUnix shell. The goal is to describe the major features and typcial uses of the tools andshow how they fit together with enough detail for simple projects. We've used a versionof this article at Stanford to help students get started with Unix.

ContentsIntroduction — the compile-link process 1The gcc compiler/linker 2The make project utility 5The gdb debugger 8The emacs editor 13Summary of Unix shell commands 15

This is document #107, Unix Programming Tools, in the Stanford CS Education Library.This and other free educational materials are available at http://cslibrary.stanford.edu/.This document is free to be used, reproduced, or sold so long as it is intact andunchanged.

Other ResourcesThis article is an introduction — for more detailed information about a particular tool, seethe tool's man pages and xinfo entries. Also, O'Reilly & Associates publishes a prettygood set of references for many Unix related tools (the books with animal pictures on thecover). For basic coverage of the C programming language, see CS Education Library#101, (http://cslibrary.stanford.edu/101/).

The Compile ProcessBefore going into detail about the individual tools themselves, it is useful to review theoverall process that goes into building an executable program. After the source text fileshave been edited, there are two steps in the build process: compiling and linking. Eachsource file (foo.c) is compiled into an object file (foo.o). Each object file contain a systemdependent, compiled representation of the program as described in its source file.Typically the file name of an object module is the same as the source file that produced it,but with a ".o" file extension — "main.c" is compiled to produce "main.o". The .o filewill include references, known as symbols, to functions, variables, etc. that the codeneeds. The individual object files are then linked together to produce a single executablefile which the system loader can use when the program is actually run. The link step will

Page 177: Data Structures

2

also bring in library object files that contain the definitions of library functions likeprintf() and malloc(). The overall process looks like this...

main.c module1.c module2.c

main.o module1.o module2.o

program

libraryfunctions

C compiler

Linker

Section 1 — gccThe following discussion is about the gcc compiler, a product of the open-source GNUproject (www.gnu.org). Using gcc has several advantages— it tends to be pretty up-to-date and reliable, it's available on a variety of platforms, and of course it's free and open-source. Gcc can compile C, C++, and objective-C. Gcc is actually both a compiler and alinker. For simple problems, a single call to gcc will perform the entire compile-linkoperation. For example, for small projects you might use a command like the followingwhich compiles and links together three .c files to create an executable named "program".

gcc main.c module1.c module2.c -o program

The above line equivalently could be re-written to separate out the three compilationsteps of the .c files followed by one link step to build the program.

gcc -c main.c ## Each of these compiles a .cgcc -c module1.cgcc -c module2.cgcc main.o module1.o module2.o -o program ## This line links the .o's

## to build the program

The general form for invoking gcc is...

gcc options files

where options is a list of command flags that control how the compiler works, andfiles is a list of files that gcc reads or writes depending on the options

Command-line optionsLike most Unix programs, gcc supports many command-line options to control itsoperation. They are all documented in its man page. We can safely ignore most of theseoptions, and concentrate on the most commonly used ones: -c, -o, -g, -Wall,-I, -L, and -l.

Page 178: Data Structures

3

-c files Direct gcc to compile the source files into an object files without goingthrough the linking stage. Makefiles (below) use this option to compilefiles one at a time.

-o file Specifies that gcc's output should be named file. If this option is notspecified, then the default name used depends on the context...(a) ifcompiling a source .c file, the output object file will be named with thesame name but with a .o extension. Alternately, (b) if linking to createan executable, the output file will be named a.out. Most often, the -ooption is used to specify the output filename when linking anexecutable, while for compiling, people just let the default .c/.onaming take over.

It's a memorable error if your -o option gets switched around in thecommand line so it accidentally comes before a source file like"...-o foo.c program" -- this can overwrite your source file --bye bye source file!

-g Directs the compiler to include extra debugging information in itsoutput. We recommend that you always compile your source with thisoption set, since we encourage you to gain proficiency using thedebugger such as gdb (below).

Note -- the debugging information generated is for gdb, and couldpossibly cause problems with other debuggers such as dbx.

-Wall Give warnings about possible errors in the source code. The issuesnoticed by -Wall are not errors exactly, they are constructs that thecompiler believes may be errors. We highly recommend that youcompile your code with -Wall. Finding bugs at compile time is soooomuch easier than run time. the -Wall option can feel like a nag, but it'sworth it. If a student comes to me with an assignment that does notwork, and it produces -Wall warnings, then maybe 30% of the time,the warnings were a clue towards the problem. 30% may not soundlike that much, but you have to appreciate that it's free debugging.

Sometimes -Wall warnings are not actually problems. The code is ok,and the compiler just needs to be convinced. Don't ignore the warning.Fix up the source code so the warning goes away. Getting used tocompiles that produce "a few warnings" is a very bad habit.

Here's an example bit of code you could use to assign and test a flagvariable in one step...

int flag;

if (flag = IsPrime(13)) {...

}

The compiler will give a warning about a possibly unintendedassignment, although in this case the assignment is correct. Thiswarning would catch the common bug where you meant to type == buttyped = instead. To get rid of the warning, re-write the code to makethe test explicit...

Page 179: Data Structures

4

int flag;

if ((flag = IsPrime(13)) != 0) {...

}

This gets rid of the warning, and the generated code will be the sameas before. Alternately, you can enclose the entire test in another set ofparentheses to indicate your intentions. This is a small price to pay toget -Wall to find some of your bugs for you.

-Idir Adds the directory dir to the list of directories searched for #includefiles. The compiler will search several standard directoriesautomatically. Use this option to add a directory for the compiler tosearch. There is no space between the "-I" and the directory name. Ifthe compile fails because it cannot find a #include file, you need a -I tofix it.

Extra: Here's how to use the unix "find" command to find your#include file. This example searches the /usr/include directory for allthe include files with the pattern "inet" in them...

nick% find /usr/include -name '*inet*'/usr/include/arpa/inet.h/usr/include/netinet/usr/include/netinet6

-lmylib (lower case 'L') Search the library named mylib for unresolvedsymbols (functions, global variables) when linking. The actual name ofthe file will be libmylib.a, and must be found in either the defaultlocations for libraries or in a directory added with the -L flag (below).

The position of the -l flag in the option list is important because thelinker will not go back to previously examined libraries to look forunresolved symbols. For example, if you are using a library thatrequires the math library it must appear before the math library on thecommand line otherwise a link error will be reported. Again, there isno space between the option flag and the library file name, and that's alower case 'L', not the digit '1'. If your link step fails because a symbolcannot be found, you need a -l to add the appropriate library, orsomehow you are compiling with the wrong name for the function orglobal variable.-Ldir Adds the directory dir to the list of directories searched for library filesspecified by the -l flag. Here too, there is no space between theoption flag and the library directory name. If the link step fails becausea library file cannot be found, you need a -L, or the library file name iswrong.

Page 180: Data Structures

5

Section 2 — makeTyping out the gcc commands for a project gets less appealing as the project gets bigger.The "make" utility automates the process of compiling and linking. With make, theprogrammer specifies what the files are in the project and how they fit together, and thenmake takes care of the appropriate compile and link steps. Make can speed up yourcompiles since it is smart enough to know that if you have 10 .c files but you have onlychanged one, then only that one file needs to be compiled before the link step. Make hassome complex features, but using it for simple things is pretty easy.

Running makeGo to your project directory and run make right from the shell with no arguments, or inemacs (below) [esc]-x compile will do basically the same thing. In any case, makelooks in the current directory for a file called Makefile or makefile for its buildinstructions. If there is a problem building one of the targets, the error messages arewritten to standard error or the emacs compilation buffer.

MakefilesA makefile consists of a series of variable definitions and dependency rules. A variable ina makefile is a name defined to represent some string of text. This works much likemacro replacement in the C pre-processor. Variables are most often used to represent alist of directories to search, options for the compiler, and names of programs to run.Variables are not pre-declared, you just set them with '='. For example, the line :

CC = gcc

will create a variable named CC, and set its value to be gcc. The name of the variable iscase sensitive, and traditionally make variable names are in all upper case letters.

While it is possible to make up your own variable names, there are a few names that areconsidered standard, and using them along with the default rules makes writing amakefile much easier. The most important variables are: CC, CFLAGS, and LDFLAGS.

CC The name of the C compiler, this will default to cc or gcc in mostversions of make.

CFLAGS A list of options to pass on to the C compiler for all of your sourcefiles. This is commonly used to set the include path to include non-standard directories (-I) or build debugging versions (-g).

LDFLAGS A list of options to pass on to the linker. This is most commonlyused to include application specific library files (-l) and set thelibrary search path (-L).

To refer to the value of a variable, put a dollar sign ($) followed by the name inparenthesis or curly braces...

CFLAGS = -g -I/usr/class/cs107/include$(CC) $(CFLAGS) -c binky.c

The first line sets the value of the variable CFLAGS to turn on debugging information andadd the directory /usr/class/cs107/include to the include file search path. Thesecond line uses CC variable to get the name of the compiler and the CFLAGS variable

Page 181: Data Structures

6

to get the options for the compiler. A variable that has not been given a value has theempty-string value.

The second major component of a makefile is the dependency/build rule. A rule tells howto make a target based on changes to a list of certain files. The ordering of the rules doesnot make any difference, except that the first rule is considered to be the default rule --the rule that will be invoked when make is called without arguments (the most commonway).

A rule generally consists of two lines: a dependency line followed by a command line.Here is an example rule :

binky.o : binky.c binky.h akbar.htab$(CC) $(CFLAGS) -c binky.c

This dependency line says that the object file binky.o must be rebuilt whenever any ofbinky.c, binky.h, or akbar.h change. The target binky.o is said to depend onthese three files. Basically, an object file depends on its source file and any non-systemfiles that it includes. The programmer is responsible for expressing the dependenciesbetween the source files in the makefile. In the above example, apparently the sourcecode in binky.c #includes both binky.h and akbar.h-- if either of those two .hfiles change, then binky.c must be re-compiled. (The make depend facility tries toautomate the authoring of the makefile, but it's beyond the scope of this document.)

The command line lists the commands that build binky.o -- invoking the C compilerwith whatever compiler options have been previously set (actually there can be multiplecommand lines). Essentially, the dependency line is a trigger which says when to dosomething. The command line specifies what to do.

The command lines must be indented with a tab characte -- just using spaces will notwork, even though the spaces will sortof look right in your editor. (This design is a resultof a famous moment in the early days of make when they realized that the tab format wasa terrible design, but they decided to keep it to remain backward compatible with theiruser base -- on the order of 10 users at the time. There's a reason the word "backward" isin the phrase "backward compatible". Best to not think about it.)

Because of the tab vs. space problem, make sure you are not using an editor or tool whichmight substitute space characters for an actual tab. This can be a problem when usingcopy/paste from some terminal programs. To check whether you have a tab character onthat line, move to the beginning of that line and try to move one character to the right. Ifthe cursor skips 8 positions to the right, you have a tab. If it moves space by space, thenyou need to delete the spaces and retype a tab character.

For standard compilations, the command line can be omitted, and make will use a defaultbuild rule for the source file based on its file extension, .c for C files, .f for Fortran files,and so on. The default build rule for C files looks like...

$(CC) $(CFLAGS) -c source-file.c

It's very common to rely on the above default build rule -- most adjustments can be madeby changing the CFLAGS variable. Below is a simple but typical looking makefile. Itcompiles the C source contained in the files main.c, binky.c, binky.h, akbar.c,akbar.h, and defs.h. These files will produce intermediate files main.o,binky.o, and akbar.o. Those files will be linked together to produce the executablefile program. Blank lines are ignored in a makefile, and the comment character is '#'.

Page 182: Data Structures

7

## A simple makefile

CC = gccCFLAGS = -g -I/usr/class/cs107/includeLDFLAGS = -L/usr/class/cs107/lib -lgraph

PROG = programHDRS = binky.h akbar.h defs.hSRCS = main.c binky.c akbar.c

## This incantation says that the object files## have the same name as the .c files, but with .oOBJS = $(SRCS:.c=.o)

## This is the first rule (the default)## Build the program from the three .o's$(PROG) : $(OBJS)tab$(CC) $(LDFLAGS) $(OBJS) -o $(PROG)

## Rules for the source files -- these do not have## second build rule lines, so they will use the## default build rule to compile X.c to make X.omain.o : main.c binky.h akbar.h defs.h

binky.o : binky.c binky.h

akbar.o : akbar.c akbar.h defs.h

## Remove all the compilation and debugging filesclean :tabrm -f core $(PROG) $(OBJS)

## Build tags for these sourcesTAGS : $(SRCS) $(HDRS)tabetags -t $(SRCS) $(HDRS)

The first (default) target builds the program from the three .o's. The next three targetssuch as "main.o : main.c binky.h akbar.h defs.h" identify the .o's thatneed to be built and which source files they depend on. These rules identify what needs tobe built, but they omit the command line. Therefore they will use the default rule whichknows how to build one .o from one .c with the same name. Finally, makeautomatically knows that a X.o always depends on its source X.c, so X.c can beomitted from the rule. So the first rule could b ewritten without main.c --"main.o : binky.h akbar.h defs.h".

The later targets, clean and TAGS, perform other convenient operations. The cleantarget is used to remove all of the object files, the executable, and a core file if you'vebeen debugging, so that you can perform the build process from scratch . You can makeclean if you want to recover space by removing all the compilation and debuggingoutput files. You also may need to make clean if you move to a system with adifferent architecture from where your object libraries were originally compiled, and so

Page 183: Data Structures

8

you need to recompile from scratch. The TAGS rule creates a tag file that most Unixeditors can use to search for symbol definitions.

Compiling in EmacsEmacs has built-in support for the compile process. To compile your code from emacs,type M-x compile. You will be prompted for a compile command. If you have amakefile, just type make and hit return. The makefile will be read and the appropriatecommands executed. The emacs buffer will split at this point, and compile errors will bebrought up in the newly created buffer. In order to go to the line where a compile erroroccurred, place the cursor on the line which contains the error message and hit ^c-^c.This will jump the cursor to the line in your code where the error occurred (“cc” is thehistorical name for the C compiler).

Section 3 — gdbYou may run into a bug or two in your programs. There are many techniques for findingbugs, but a good debugger can make the job a lot easier. In most programs of anysignificant size, it is not possible to track down all of the bugs in a program just by staringat the source — you need to see clues in the runtime behavior of the program to find thebug. It's worth investing time to learn to use debuggers well.

GDBWe recommend the GNU debugger gdb, since it basically stomps on dbx in everypossible area and works nicely with the gcc compiler. Other nice debuggingenvironments include ups and CodeCenter, but these are not as universally available asgdb, and in the case of CodeCenter not as cheaply. While gdb does not have a flashygraphical interface as do the others, it is a powerful tool that provides the knowledgeableprogrammer with all of the information they could possibly want and then some.

This section does not come anywhere close to describing all of the features of gdb, butwill hit on the high points. There is on-line help for gdb which can be seen by using thehelp command from within gdb. If you want more information try xinfo if you arelogged onto the console of a machine with an X display or use the info-browser modefrom within emacs.

Starting the debuggerAs with make there are two different ways of invoking gdb. To start the debugger fromthe shell just type...

gdb program

where program is the name of the target executable that you want to debug. If you donot specify a target then gdb will start without a target and you will need to specify onelater before you can do anything useful.

As an alternative, from within emacs you can use the command [Esc]-x gdb whichwill then prompt you for the name of the executable file. You cannot start an inferior gdbsession from within emacs without specifying a target. The emacs window will then splitbetween the gdb buffer and a separate buffer showing the current source line.

Running the debuggerOnce started, the debugger will load your application and its symbol table (whichcontains useful information about variable names, source code files, etc.). This symbol

Page 184: Data Structures

9

table is the map produced by the -g compiler option that the debugger reads as it isrunning your program.

The debugger is an interactive program. Once started, it will prompt you for commands.The most common commands in the debugger are: setting breakpoints, single stepping,continuing after a breakpoint, and examining the values of variables.

Running the Programrun Reset the program, run (or rerun) from the

beginning. You can supply command-linearguments the same way you can supply command-line arguments to your executable from the shell.

step Run next line of source and return to debugger. If asubroutine call is encountered, follow into thatsubroutine.

step count Run count lines of source.

next Similar to step, but doesn't step into subroutines.

finish Run until the current function/method returns.

return Make selected stack frame return to its caller.

jump address Continue program at specified line or address.

When a target executable is first selected (usually on startup) the current source file is setto the file with the main function in it, and the current source line is the first executableline of the this function.

As you run your program, it will always be executing some line of code in some sourcefile. When you pause the program (when the flow of control hits a “breakpoint” of bytyping Control-C to interrupt), the “current target file” is the source code file in which theprogram was executing when you paused it. Likewise, the “current source line” is the lineof code in which the program was executing when you paused it.

BreakpointsYou can use breakpoints to pause your program at a certain point. Each breakpoint isassigned an identifying number when you create it, and so that you can later refer to thatbreakpoint should you need to manipulate it.

A breakpoint is set by using the command break specifying the location of the codewhere you want the program to be stopped. This location can be specified in severalways, such as with the file name and either a line number or a function name within thatfile (a line needs to be a line of actual source code — comments and whitespace don'tcount). If the file name is not specified the file is assumed to be the current target file, andif no arguments are passed to break then the current source line will be the breakpoint.gdb provides the following commands to manipulate breakpoints:

info break Prints a list of all breakpoints with numbers andstatus.

Page 185: Data Structures

10

break function Place a breakpoint at start of the specified functionbreak linenumber Prints a breakpoint at line, relative to current source

file.break filename:linenumber Place a breakpoint at the specified line within the

specified source file.

You can also specify an if clause to create a conditional breakpoint:

break fn if expression Stop at the breakpoint, only if expression evaluatesto true. Expression is any valid C expression,evaluated within current stack frame when hittingthe breakpoint.

disable breaknumenable breaknum Disable/enable breakpoint identified by breaknum

delete breaknum Delete the breakpoint identified by breaknum

commands breaknum Specify commands to be executed when breaknumis reached. The commands can be any list of Cstatements or gdb commands. This can be useful tofix code on-the-fly in the debugger without re-compiling (Woo Hoo!).

cont Continue a program that has been stopped.

For example, the commands...

break binky.c:120break DoGoofyStuff

set a breakpoint on line 120 of the file binky.c and another on the first line of the functionDoGoofyStuff. When control reaches these locations, the program will stop and giveyou a chance to look around in the debugger.

Gdb (and most other debuggers) provides mechanisms to determine the current state ofthe program and how it got there. The things that we are usually interested in are (a)where are we in the program? and (b) what are the values of the variables around us?

Examining the stackTo answer question (a) use the backtrace command to examine the run-time stack.The run-time stack is like a trail of breadcrumbs in a program; each time a function call ismade, a crumb is dropped (an run-time stack frame is pushed). When a return from afunction occurs, the corresponding stack frame is popped and discarded. These stackframes contain valuable information about the sequence of callers which brought us to thecurrent line, and what the parameters were for each call.

Gdb assigns numbers to stack frames counting from zero for the innermost (currentlyexecuting) frame. At any time gdb identifies one frame as the “selected” frame. Variablelookups are done with respect to the selected frame. When the program being debuggedstops (at a breakpoint), gdb selects the innermost frame. The commands below can beused to select other frames by number or address.

Page 186: Data Structures

11

backtrace Show stack frames, useful to find the callingsequence that produced a crash.

frame framenumber Start examining the frame with framenumber. Thisdoes not change the execution context, but allowsto examine variables for a different frame.

down Select and print stack frame called by this one. (Themetaphor here is that the stack grows down witheach function call.)

up Select and print stack frame that called this one.

info args Show the argument variables of current stackframe.

info locals Show the local variables of current stack frame.

Examining source filesAnother way to find our current location in the program and other useful information is toexamine the relevant source files. gdb provides the following commands:

list linenum Print ten lines centered around linenum in currentsource file.

list function Print ten lines centered around beginning offunction (or method).

list Print ten more lines.

The list command will show the source lines with the current source line centered inthe range. (Using gdb from within emacs makes these command obsolete since it doesall of the current source stuff for you.)

Examining dataTo answeer the question (b) “what are the values of the variables around us?” use thefollowing commands...

print expression Print value of expression. Expression is any valid Cexpression, can include function calls andarithmetic expressions, all evaluated within currentstack frame.

set variable = expression Assign value of variable to expression. You canset any variable in the current scope. Variableswhich begin with $ can be used as temporaryvariables local to gdb.

display expression Print value of expression each time the programstops. This can be useful to watch the change in avariable as you step through code.

undisplay Cancels previous display requests.

Page 187: Data Structures

12

In gdb, there are two different ways of displaying the value of a variable: a snapshot ofthe variable’s current value and a persistent display for the entire life of the variable. Theprint command will print the current value of a variable, and the display commandwill make the debugger print the variable's value on every step for as long as the variableexists. The desired variable is specified by using C syntax. For example...

print x.y[3]

will print the value of the fourth element of the array field named y of a structure variablenamed x. The variables that are accessible are those of the currently selected function'sactivation frame, plus all those whose scope is global or static to the current target file.Both the print and display functions can be used to evaluate arbitrarily complicatedexpressions, even those containing, function calls, but be warned that if a function hasside-effects a variety of unpleasant and unexpected situations can arise.

ShortcutsFinally, there are some things that make using gdb a bit simpler. All of the commandshave short-cuts so that you don’t have to type the whole command name every time youwant to do something simple. A command short-cut is specified by typing just enough ofthe command name so that it unambiguously refers to a command, or for the specialcommands break, delete, run, continue, step, next and print you need onlyuse the first letter. Additionally, the last command you entered can be repeated by justhitting the return key again. This is really useful for single stepping for a range whilewatching variables change.

Miscellaneouseditmode mode Set editmode for gdb command line. Supported

values for mode are emacs, vi, dumb.

shell command Execute the rest of the line as a shell command.

history Print command history.

Debugging StrategiesSome people avoid using debuggers because they don't want to learn another tool. This isa mistake. Invest the time to learn to use a debugger and all its features — it will makeyou much more productive in tracking down problems.

Sometimes bugs result in program crashes (a.k.a. “core dumps”, “register dumps”, etc.)that bring your program to a halt with a message like “Segmentation Violation” or thelike. If your program has such a crash, the debugger will intercept the signal sent by theprocessor that indicates the error it found, and allow you to examine the state program.Thus with almost no extra effort, the debugger can show you the state of the program atthe moment of the crash.

Often, a bug does not crash explicitly, but instead produces symptoms of internalproblems. In such a case, one technique is to put a breakpoint where the program ismisbehaving, and then look up the call stack to get some insight about the data andcontrol flow path that led to the bad state. Another technique is to set a breakpoint atsome point before the problems start and step forward towards the problems, examiningthe state of the program along the way.

Page 188: Data Structures

13

Section 4 — emacsThe following is a quick introduction to the “emacs” text editor which is a free programproduced by GNU (www.gnu.org). It's a fine editor, and it happens to integrate withmany other Unix tools nicely. There's a fabulous history of various editor adherentshaving long and entertaining arguments about why their editor is best, but we're justgoing to avoid that subject entirely.

To start editing a new or existing file using emacs, simply type the following to the UNIXprompt...

emacs filename

where filename is the file to be edited. The X-Windows version of emacs is calledxemacs, and if you're using it... well just look in the menus. The commands are all thesame, but you don't have to remember the funny key-combinations.

All the fancy editing commands, such as find-and-replace, are invoked through typingspecial key sequences. Two important key sequences to remember are: ^x (holding downthe “ctrl” key while typing “x”) and [esc]-x (simply pressing the “esc” key followedby typing “x”), both of which are used to start many command sequences. Note that forhistorical reasons in most user manuals for emacs, the “esc” key is actually referred to asthe “Meta” or “M-” key. Therefore, you may see the [esc]-x written as equivalentlyas M-x.

To save the file being edited the sequence is ^x^s. To exit (and be prompted to save)emacs, the sequence is ^x^c. To open another file within emacs, the sequence is ^x^f.This sequence can be used to open an existing file as well as a new file. If you havemultiple files open, emacs stores them in different “buffers”. To switch from one bufferto another (very handy when you are editing a .c source file and need to refer to theprototypes and definitions in the .h header file), you use the key sequence ^x-b (notethe “b” is typed plain). You can then enter the name of the file to switch to thecorresponding buffer (a default name is provided for fast switching). The arrow keysusually work as the cursor movement keys, but there are other nagivation keycombinations listed below.

Running emacsemacs <filename> Run emacs (on a particular file). Make sure you don't already have

an emacs job running which you can just revive with fg. Adding a'&' after the above command will run emacs in the background,freeing up your shell)

^z Suspend emacs— revive with % command above, or the fgcommand

^x^c Quit emacs^x^f Load a new file into emacs^x^v Load a new file into emacs and unload previous file^x^s Save the file^x-k Kill a buffer

Moving About^f Move forward one character^b Move backward one character^n Move to next line^p Move to previous line

Page 189: Data Structures

14

^a Move to beginning of line^e Move to end of line^v Scroll down a pageM-v Scroll up a page

M-< Move to beginning of document^x-[ Move to beginning of pageM-> Move to end of document^x-] Move to end of page

^l Redraw screen centered at line under the cursor^x-o Move to other screen^x-b Switch to another buffer

Searching^s Search for a string^r Search for a string backwards from the cursor (quit both of these

with ^f)M-% Search-and-replace

Deleting^d Deletes letter under the cursor^k Kill from the cursor all the way to the end of the line^y Yanks back all the last kills. Using the ^k ^y combination you can

get a primitive cut-paste effect to move text around

Regionsemacs defines a region as the space between the mark and the point. A mark is set with^-space (control-spacebar). The point is at the cursor position.

M-w Copy the region

^w Delete the region. Using ^y will also yank back the last regionkilled or copied — this is the way to get a cut/copy/paste effect withregions.

Screen Splitting^x-2 Split screen horizontally^x-3 Split screen vertically^x-1 Make active window the only screen^x-0 Make other window the only screen

MiscellaneousM-$ Check spelling of word at the cursor^g In most contexts, cancel, stop, go back to normal commandM-x goto-line num Goes to the given line number^x-u UndoM-x shell Start a shell within emacsM-q Re-flow the current line-breaks to make a single paragraph of text

Page 190: Data Structures

15

CompilingM-x compile Compile code in active window. Easiest if you have a makefile set

up.^c ^c Do this with the cursor in the compile window, scrolls to the next

compiler error. Cool!

Getting Help^h emacs help^h t Run the emacs tutorial

emacs does command completion for you. Typing M-x space will give you a list of emacscommands. There is also a man page on emacs. Type man emacs in a shell.

Printing Your Source FilesThere's a really neat way to print out hardcopies of your source files. Use a commandcalled “enscript”. Commonly, it's used at the Unix command line as follows:

enscript -2GrPsweet5 binky.c lassie.c *.h

This example shoes printing the two source files binky.c and lassie.c, as well asall of the header files to printer sweet5. You can change these parameters to fit yourneeds.

Section 5 — Unix ShellThis section summarizes many of the commands used in the Unix shell.

Directory Commandscd directory Change directory. If directory is not specified, goes to home

directory.pwd Show current directory (print working directory)ls Show the contents of a directory. ls -a will also show files whose

name begins with a dot. ls -l shows lots of miscellaneous info abouteach file. ls -t sorts the most recently changed to the top.

rm file Delete a filemv old new Rename a file from old to new (also works for moving things

between directories). If there was already a file named new, it getsoverwritten.

cp old new Creates a file named new containing the same thing as old. If therewas already a file named new, it is overwritten.

mkdir name Create a directoryrmdir name Delete a directory. The directory must be empty.

Shorthand Notations & Wildcards. Current directory.. Parent directory~ Your home directory~/cs107 The cs107 directory in your home directory~user Home directory of user* Any number of characters (not '.') Ex: *.c is all files ending in '.c'? Any single character (not '.')

Page 191: Data Structures

16

Miscellaneous Commandscat file Print the contents of file to standard outputmore file Same as cat, but only a page at a time (useful for displaying)less file Same as more, but with navigability (less is more)

w Find out who is on the system and what they are doingps List all your processes (use the process id's in kill below)jobs Show jobs that have been suspended (use with fg)

program& Runs program in the backgroundctrl-z Suspend the current program% Continue last job suspended, or use fg (foreground)%number Continue a particular job (the number comes from the jobs list)

kill process-id Kill a processkill -9 process Kill a process with extreme prejudice

grep exp files Search for an expression in a set of fileswc file Count words, lines, and characters in a filescript Start saving everything that happens in a file. type exit when done

lpr file Print file to the default printerlpr -Pinky file Print file to the printer named inky

diff file1 file2 Show the differences between two filestelnet hostname Log on to another machine

source file Execute the lines in the given file as if they were typed to the shell

Getting Helpman subject Read the manual entry on a particular subjectman -k keyword Show all the manual pages for a particular keyword

Historyhistory Show the most recent commands executed!! Re-execute the last command (or type up-arrow with modern shells)!number Re-execute a particular command by number!string Re-execute the last command beginning with string^wrong^right^ Re-execute the last command, substituting right for wrongctrl-P Scroll backwards through previous commands

Pipesa > b Redirect a's standard output to overwrite file ba >> b Redirect a's standard output to append to the file ba >& b Redirect a's error output to overwrite file ba < b Redirect a's standard input to read from the file ba | b Redirect a's standard output to b's standard input