SLIDE 1 Monday Week 05 Dynamic Memory Allocation
1/36
Statically allocated objects ... are allocated by the compiler (to global/stack areas) have a fixed size which is known at compile time have a lifetime determined by location (global/stack) Dynamically allocated objects ... are allocated by the executing program (in heap) have a size which is known at allocation time exist until explicitly removed by program
... Dynamic Memory Allocation
2/36
Example: function to return a lower-cased version of a string char *old = "This is MY string."; char *new; new = lowerCase(old); print("%s\n%s\n", old, new); // Output: // This is MY string. // this is my string.
Notes: the old string is unchanged, so the function is making a new copy
- ld is a pointer to a string in the global data area
new has no string buffer unless it's made to point at one tolower(ch) function in ctype.h is useful here
... Dynamic Memory Allocation
3/36
Attempt #1: use a local string buffer char *lowerCase(char *str) { char *cp; // input string cursor char *op; // output string cursor char out[BUFSIZ]; // output string buffer
for (cp = str; *cp != '\0'; cp++) { *op = tolower(*cp);
} *op = '\0'; return out; // what is the precise effect? } BUG: the out variable is removed when lowerCase() returns.
... Dynamic Memory Allocation
4/36
Attempt #2: user-supplied string buffer (different interface) char *old = "This is MY string."; char new[BUFSIZ]; ... lowerCase(old, new); ... void lowerCase(char *in, char *out) { char *cp, *op = out; for (cp = in; *cp != '\0'; cp++) { *op = tolower(*cp);
} *op = '\0'; } More common way of writing loop body: *op++ = tolower(*cp);
... Dynamic Memory Allocation
5/36
Attempt #3: dynamically allocated buffer char *lowerCase(char *str) { char *cp; // input string cursor char *op; // output string cursor char *out; // output string buffer
- p = out = (char *)malloc(strlen(str)+1);
assert(out != NULL); for (cp = str; *cp != '\0'; cp++) { *op = tolower(*cp);
} *op = '\0'; return out; // what is the precise effect? }
Memory Management
6/36
void free(void *ptr)
SLIDE 2
releases a block of memory allocated by malloc() *ptr is a dynamically allocated object if *ptr was not malloc()'d, chaos will follow Things to note: the contents of the memory block are not changed all pointers to the block still exist, but are not valid the memory may be re-used as soon as it is free()'d
... Memory Management
7/36
Warning! Warning! Warning! Warning!
Careless use of malloc() / free() / pointers can mess up the data in the heap so that later malloc() or free() cause run-time errors possibly well after the original error occurred Such errors are very difficult to track down and debug. Be very careful with your use of malloc() / free() / pointers.
... Memory Management
8/36
Given a pointer variable: you can check whether its value is NULL you can (maybe) check that it is an address you cannot check whether it is a valid address
... Memory Management
9/36
Typical usage pattern for dynamically allocated objects: // single dynamic object e.g. struct size_t size = sizeof(Type); Type *ptr = (Type *)malloc(size); assert(ptr != NULL); ... use object referenced by ptr e.g. ptr->name ... free(ptr); // dynamic array with "nelems" elements int nelems = NumberOfElements; size_t eSize = sizeof(ElemType); ElemType *arr = (Type *)malloc(nelems*eSize); assert(arr != NULL); ... use array referenced by arr e.g. arr[4] ... free(arr);
Exercise: Making Sentences
10/36
Implement a function char *makeSentence(char *strings[]) which takes an array of strings (e.g. argv[]) returns a new string made by concatenating strings with original strings separated by a space in new string assume that strings[] is terminated by a NULL element
Memory Leaks
11/36
Well-behaved programs do the following: allocate a new object via malloc() use the object for as long as needed free() the object when no longer needed A program which does not free() each object before the last reference to it is lost contains a memory leak. Such programs may eventually exhaust available heapspace.
... Memory Leaks
12/36
Example function with memory leak:
int median(int v[], int n) { int i, j, min, tmp, *sorted; sorted = (int *)malloc(n*sizeof(int)); assert(sorted != NULL); for (i = 0; i < n; i++) { sorted[i] = v[i]; } for (i = 0; i < n; i++) { min = i; for (j = i+1; j < n; j++) { if (sorted[j] < sorted[min]) min = j; } tmp = sorted[i]; sorted[i] = sorted[min]; sorted[min] = tmp; } return sorted[n/2]; }
The array referenced by sorted exists after function returns.
SLIDE 3
... Memory Leaks
13/36
Example function without memory leak:
int median(int v[], int n) { int i, j, min, tmp, med, *sorted; sorted = (int *)malloc(n*sizeof(int)); assert(sorted != NULL); for (i = 0; i < n; i++) { sorted[i] = v[i]; } for (i = 0; i < n; i++) { min = i; for (j = i+1; j < n; j++) { if (sorted[j] < sorted[min]) min = j; } tmp = sorted[i]; sorted[i] = sorted[min]; sorted[min] = tmp; } med = sorted[n/2]; free(sorted); return med; // would return sorted[n/2]; work? }
The array referenced by sorted is cleaned up before function returns.
Memory Management Functions
14/36
void *malloc(size_t nbytes) aim: allocate some memory for a data object attempt to allocate a block of memory of size nbytes in the heap if successful, returns a pointer to the start of the block if insufficient space in heap, returns NULL Things to note: the initial contents of the memory block are random returned address should be cast to appropriate pointer type
... Memory Management Functions
15/36
void free(void *ptr) releases a block of memory allocated by malloc() *ptr is a dynamically allocated object if *ptr was not malloc()'d, chaos will follow Things to note: the contents of the memory block are not changed all pointers to the block still exist, but are not valid the memory may be re-used as soon as it is free()'d
... Memory Management Functions
16/36
void *realloc(void *ptr, size_t nbytes) aim: increase the size of an array allocates a memory block of size nb bytes if successful, copies all data from *ptr object into it after copying, performs free(ptr) assumes that nb is larger than size of *ptr object if insufficient memory, returns NULL and does not free
... Memory Management Functions
17/36
void *calloc(size_t nelems, size_t nbytes) aim: create a new array with memory zero'd nelems is the number of elements nbytes is the size of each element allocates a memory block of size nelems*nbytes bytes if successful, all bytes in memory block are set to zero if insufficient memory, returns NULL
Exercise: Generic Matrix Library
18/36
Use dynamically allocated memory to implement the following library of Matrix operations: typedef struct { int nrows, ncols; float **data; } Matrix; int readMatrix(Matrix *); void printMatrix(Matrix); void copyMatrix(Matrix, Matrix); void transMatrix(Matrix, Matrix); void addMatrix(Matrix, Matrix, Matrix); void mulMatrix(Matrix, Matrix, Matrix); Notes: readMatrix reads nrows, ncols, then the data can only add matrices with equal rows and columns can only multiply an n×m matrix with an m×p matrix
Dynamic Structures Static/Dynamic Sequences
20/36
We have studied arrays extensively
SLIDE 4 fixed size collection of heterogeneous elements can be accessed via index or via "moving" pointer The "fixed size" aspect is a potential problem: how big to make the array? (big ... just in case) what to do if it fills up? (realloc() may help) The rigid sequence is another problems: inserting/deleting an item in middle of array
... Static/Dynamic Sequences
21/36
Inserting a value into a sorted array (insert(a,&n,4)):
... Static/Dynamic Sequences
22/36
Deleting a value from a sorted array (delete(a,&n,3)):
Exercise: Insert/Delete in Sorted Array
23/36
Implement the above insert and delete operations: // insert value v into array a of length n void insert(int *a, int *n, int v); // delete value v from array a of length n void delete(int *a, int *n, int v); What special cases do we need to consider?
Dynamic Sequences
24/36
The problems with using arrays can be solved by allocating elements individually linking them together as a "chain" Benefits: insertion/deletion have minimal effect on list overall
- nly use as much space as needed for values
Self-referential Structures
25/36
To realise a "chain of elements", need a node containing a value a link to the next node In C, we can define such nodes as: struct node { int data; struct node *next; };
... Self-referential Structures
26/36
When definining self-referential types with typedef typedef struct node { int data; struct node *next; } NodeT;
typedef struct node NodeT; struct node { int data; NodeT *next; };
... Self-referential Structures
27/36
SLIDE 5
Note that the following definition does not work: typedef struct { int data; NodeT *next; } NodeT; Because NodeT is not yet known (to the compiler) when we try to use it to define the type of the next field. The following is also illegal in C: struct node { int data; struct node recursive; };
Linked Lists
28/36
To represent a chained (linked) list of nodes: we need a pointer to the first node each node contains a pointer to the next node the next pointer in the last node is NULL
... Linked Lists
29/36
Linked lists are more flexible than arrays: values do not have to be adjacent in memory values can be rearranged simply by altering pointers the number of values can change dynamically values can be added or removed in any order Disadvantages: it is not difficult to get pointer manipulations wrong each value also requires storage for next pointer
Memory Storage for Linked Lists
30/36
Linked list nodes are typically located in the heap because nodes are dynamically created Variables containing pointers to list nodes are likely to be local variables (in the stack) Pointers to the start of lists are often passed as parameters to function returned as function results
... Memory Storage for Linked Lists
31/36
Iteration over Linked Lists
32/36
When manipulating list elements typically have pointer p to current node (NodeT *p) to access the data in current node: p->data to get pointer to next node: p->next To iterate over a linked list: set p to point at first node (head) examine node pointed to by p change p to point to next node stop when p reaches end of list (NULL)
... Iteration over Linked Lists
33/36
Standard method for scanning all elements in a linked list: NodeT *list; // pointer to first Node in list NodeT *p; // pointer to "current" Node in list p = list; while (p != NULL) { ... do something with p->data ...
SLIDE 6 p = p->next; } // which is frequently written as for (p = list; p != NULL; p = p->next) { ... do something with p->data ... }
... Iteration over Linked Lists
34/36
... Iteration over Linked Lists
35/36
Exercise: Print a List
36/36
Write a function that will given a pointer to the first node in the list: print all values stored in the list separated by a space, terminated by '\n' Use the following function interface: void printList(NodeT *list) { ... }
Produced: 25 Aug 2014