Title: Topic 12
1Topic 12 Dynamic Memory Allocation Linked
Lists
2Static Memory Allocation
- So far, we have only seen static memory
allocation. - This means that the number of variables a program
will use, and their sizes, are fixed and known
when the program is written and compiled. - It is possible to read through a program and
calculate exactly how much memory will be
required when the program is run, and how much
memory each variable and/or data structure will
need.
3Static Memory Allocation
- Notice that this requires that the maximum number
of elements in an array must be known when the
program is written. - This is not practical, as the total number of
variables may not be known in advance. - For example, in writing a student record database
program, the number of students may not be known
in advance. This may result in far too much
memory being allocated or not enough.
4Static Memory Allocation
- It is clear why the approach we have seen thus
far is limited. - We need to know how many variables the program
will need in advance. - If this number is too low, the program will not
be able to handle the necessary data. - If this number is too high, the program will
waste memory, as all of the allocated memory will
not be used.
5Dynamic Memory Allocation
- These disadvantages are overcome by making use of
dynamic memory allocation. - This refers to a program deciding at run-time how
much memory to allocate. - A program can call certain functions to allocate
memory at any time. Therefore, the program can
use input data as the basis for determining how
much space to request and what type of data to
store in this space.
6Dynamic Memory Allocation
- As dynamic memory allocation allows the program
to make direct use of memory, it is clear that
pointers must be used in some fashion. - Using dynamic data structures requires the
sophisticated use of pointers thus we will
briefly summarize the use of pointers we have
covered thus far.
7A Quick Review of Pointers
- A pointer is a variable that holds a memory
address. - We can use the operator to dereference a
pointer. This refers to following the pointer
to get what it is pointing to. - We can pass pointers to (the addresses of)
variables into functions this allows the
function to modify variables local to the calling
function (call-by-reference). This allows the
use of function output parameters.
8A Quick Review of Pointers
- The name of an array is simply the address of the
first element of that array. Thus, we can think
of an array name as a pointer to the array. - A string is simply an array of characters, thus
it behaves just like an array. - As arrays are simply pointers to their first
elements, anytime we pass an array (or string)
into a function, this call is call-by-reference.
The function can modify the array.
9Something NewKind of.Null Pointers
- A pointer is a memory address.
- Any pointer can have a special value, NULL. This
value means does not point to anything. - If a pointer is set to NULL, it cannot be
dereferenced. Any attempt to do so will cause a
segmentation fault. - A pointer is set to NULL using the standard
assignment statement syntax
int num1_p num1_p NULL
10Null Pointers
- We can also test to see if a pointer is set to
NULL, using standard comparisons. - if (num1_p NULL)
- / points to nothing /
- else
- / points to something /
- Why this is an important concept will become
clear later. Null pointers are used extensively
to mark the end of dynamic data structures, such
as linked lists.
11A Quick Review of Pointers
- When we define structures, we can also create
pointers to variables of these user-defined
datatypes. - Thus, we can see that pointers are simply memory
addresses. They are the components of the C
language used to manage memory.
12The malloc Function
- We now introduce the malloc() function. This
function name is short for memory allocation. - When this function is called, the program finds
some unallocated space in memory. - This function takes exactly one parameter, the
amount of memory that the program wishes to
allocate. - It returns a pointer, of type void which is the
address where the memory that was just allocated
begins.
13The malloc Function
- The one parameter passed into malloc is the size
of the memory block we wish to allocate. - This size is determined using the sizeof()
function. This function takes a datatype as a
parameter and returns the size of the datatype
(how much space in memory one variable of that
datatype occupies in memory).
14The malloc Function
- Thus, to allocate enough space in memory to store
an integer, we would call malloc as - malloc(sizeof(int))
- Similarly, to allocate enough space in memory to
store a student_record, we would call malloc as - malloc(sizeof(student_record))
15The malloc Function
- So, now we can allocate memory of the correct
size to store a variablehow do we use it? - Remember that pointers can only point to a
certain datatype, i.e. an int can only point to
an integer, nothing else. - malloc returns a void . This is simply a memory
address with no type. - Therefore, we must cast the return value of
malloc, the address of the just-allocated memory
block, to the type of pointer we want to store in
that memory block.
16The malloc Function
- Thus, to allocate enough space in memory to store
an integer, we would call malloc as - int int1_p
- int1_p (int )malloc(sizeof(int))
- Similarly, to allocate enough space in memory to
store a student_record, we would call malloc as - student_record TopStudent_p
- TopStudent_p
- (student_record )malloc(sizeof(student_record))
17The malloc Function
student_record TopStudent_p TopStudent_p
(student_record )malloc(sizeof(student_record))
- What this call to malloc does is to allocate
memory the size of one student_record. The
memory address of this block of memory is stored
in the pointer TopStudent. We cast this address
to a student_record pointer this indicates that
we will use this block of memory as a
student_record datatype.
18ReferencingDynamically Allocated Memory
- So, now we can allocate memory dynamically and
obtain a pointer to a specific datatype, which
will be stored in the newly allocated memory. - Once this is done, values can be stored in the
newly allocated memory using the pointer
referencing operator , just as pointers were
used before.
19ReferencingDynamically Allocated Memory
int num1_p, num2_p student_record record1_p,
record2_p num1_p (int )malloc(sizeof(int))
num2_p (int )malloc(sizeof(int)) record1_p
(student_record ) malloc(sizeof(student_record))
record2_p (student_record ) malloc(sizeof(st
udent_record)) num1_p 27 num2_p
99 (record1_p).letter_grade
A strcpy(record2_p-gtlast,Six) strcpy(record2
_p-gtfirst,Jeffrey)
20The calloc Function
- The malloc function works well to allocate one
variable, as we simply pass it the size of memory
we need to allocate, which is easily obtained by
using the sizeof function. - Sometimes we wish to allocate arrays dynamically.
This can be done easily using the calloc
function.
21The calloc Function
- calloc is short for contiguous allocation.
- This function takes two parameters, the number of
elements to allocate and the size of one element. - It returns a pointer to the beginning of the
newly allocated memory block, thus this pointer
is equal to the address of the first element of
the array. - Note that this function also initializes every
element of the new array to zero.
22The calloc Function
- Once an array is dynamically allocated in such a
manner, it can be accessed using the same
operators and other uses as a statically
allocated array. - This can be shown in a simple example.
23Dynamically AllocatingArrays with calloc
int array_of_nums char string int
size_of_string, number_of_nums printf(How
many characters in the string?) scanf(d,size
_of_string) string (char )calloc(size_of_strin
g, sizeof(char) scanf(s,string) printf(How
many numbers?) scanf(d,number_of_nums) arra
y_of_nums (int )calloc(number_of_nums,sizeof(
int)) for (count 0 count lt number_of_nums
count) scanf(d,array_of_numscount)
24Freeing Dynamically Allocated Memory
- Now we have seen that malloc and calloc can be
used to dynamically allocate memory. - When a static variable, i.e. a local variable of
a function, falls out of scope, for example, when
the function it is defined in finishes (executes
a return statement or the closing brace), C
automatically frees that memory. - This means the memory used for these variables is
deallocated (marked as free it can be used in
the future when new memory must be allocated).
25Freeing DynamicallyAllocated Memory
- This is NOT true of dynamically allocated memory.
While the pointer that we set using malloc may
fall out of scope and be unallocated, the memory
it points to (the memory malloc allocated) is not
freed. - In C, it is the responsibility of the programmer
to free any memory that is dynamically allocated
in the program.
26The free Function
- This is done using the free function.
- It takes one parameter, the pointer returned by
malloc. - When this function is called, the memory
allocated by malloc at the address contained in
the pointer is deallocated (marked as free it
can be used in the future when new memory must be
allocated). - Thus, malloc and free can be thought of as
companion functions. One allocates memory when it
is needed, the other frees memory when it is no
longer needed.
27The free Function
char string int size_of_string printf(How
many characters in the string?) scanf(d,size
_of_string) string (char )calloc(size_of_strin
g, sizeof(char) scanf(s,string) printf(The
string you typed in was\ns\n,
string) free(string)
28One Common Problem
- One problem is quite common when using
dynamically allocated memory. - Sometimes, multiple pointers can point to the
same memory block. - If the free function is called with one of these
pointers, the memory block they both point to is
unallocated. Thus, if the second pointer (not
the one passed to free) is later used, the memory
it points to is no longer allocated. - This leads to the same problem as overstepping
the bounds of an array (attempting to use memory
that is not allocated).
29One Common Problem
char string, string2 int size_of_string pri
ntf(How many characters in the
string?) scanf(d,size_of_string) string
(char )calloc(size_of_string, sizeof(char) strin
g2 string scanf(s,string) printf(The
string you typed in was\ns\n,
string) free(string) . . .
30The Linked List
- Now that we understand the concept of dynamic
memory allocation, we can look at a linked list. - A linked list is a data structure which is a
sequence of nodes in which each node is linked,
or connected to, the node following it.
31The Linked List
Data 1
Data 1
Data 2
Next node
Data 2
Data 3
Next node
Data 3
Next node
Data 4
Data 4
NO NEXT
32The Linked List
- In this linked list example, each node has two
pieces of data. Each node also has a pointer to
the next node. - So, we need two things to form a linked list a
way to combine various datatypes and variables
together into one datatype and a way to point
to the next one of these combination datatypes. - Sohow can we accomplish this?
33The Linked List
- The first goal, combining various datatypes and
variables into one datatype, is easily handled
with a structure. - The second goal, being able to point to the
next structure is easily handled using pointers. - So, we have all of the components we need in
order to construct a linked list.
34Structures with Pointer Components
- We can form a linked list by defining a structure
with a pointer component. - typedef struct a_node
- char lastname20
- float num_grade
- struct a_node next_p
- a_node2
35Structureswith Pointer Components
typedef struct a_node char lastname20 float
num_grade struct a_node next_p a_node2
- The a_node name is known as a structure tag.
What is does is let us use the phrase struct
a_node as an alternative to using the work
a_node2. - We use this as the datatype for the link pointer,
as the compiler has not yet reached the
definition of the word a_node2. It has reached
the definition of the phrase struct a_node, so we
can use this as the datatype.
36Structureswith Pointer Components
typedef struct char lastname20 float
num_grade a_node2 next_p a_node2
- This definition is illegal and will result in a
compile error. When the compiler reaches the
declaration of the link pointer, it does not yet
know what an a_node2 is. Thus, this will cause a
compiler error.
37Structureswith Pointer Components
typedef struct a_node char lastname20 float
num_grade struct a_node next_p a_node2
- Notice that when we use this structure tag, we
need to use the entire phrase struct a_node and
not simply a_node.
38Creating a Linked List
- We can then setup a linked list using these
structures
a_node2 node1_p, node2_p, node3_p node1_p
(a_node2 )malloc(sizeof(a_node2)) node2_p
(a_node2 )malloc(sizeof(a_node2)) node3_p
(a_node2 )malloc(sizeof(a_node2)) strcpy(node1_p
-gtlastname, Smith) strcpy(node2_p-gtlastname,
Jones) strcpy(node3_p-gtlastname,
Williams) node1_p-gtnum_grade90
node2_p-gtnum_grade20 node3_p-gtnum_grade100 nod
e1_p-gtnext_p node2_p node2_p-gtnext_p
node3_p node3_p-gtnext_p NULL
39Creating a Linked List
Smith
Williams
Jones
90
20
100
1080
2370
NULL
Node1_p
Node3_p
Node2_p
40Creating a Linked List
- Notice that if we have the pointer to the first
node in the linked list, we do not need the
pointers to the other nodes, as the first node
contains a pointer to the second node, the second
node contains a pointer to the third node, etc. - Thus, we can access any node in the linked list
by starting at the first node and following the
link pointers. - Thus, we can think of a linked list as . . .
41Creating a Linked List
Address 2370
Address 1080
Smith
Williams
Jones
90
20
100
1080
2370
NULL
Node1_p
42Accessing the DataContained in a Linked List
- So, we can see that node1_p points to the first
node in our linked list. This first node is
known as the head of the linked list. - We can access the data in the first node by using
the -gt operator - printf(Names\n,node1_p-gtlastname)
- printf(Gradef\n,node1_p-gtnum_grade)
43Accessing the Data Contained in a Linked List
- The first node also contains a pointer to the
second node. Therefore, we can access this
pointer and use it to access the data in the
second node -
- printf(Name?)
- scanf(s,node1_p-gtnext_p-gtlastname)
- printf(Grade?)
- scanf(f,(node1_p-gtnext_p-gtnum_grade))
- printf(Names,node1_p-gtnext_p-gtlastname)
- printf(Gradef,node1_p-gtnext_p-gtnum_grade)
44Accessing the DataContained in a Linked List
- We can access the data contained in nodes past
the second node in the same manner we can simply
follow the pointers to the next node until we
arrive at the node that contains the data we want
to access. - Thus, if the address stored in node1_p is known
by a function, that function can access every
element (node) of the linked list.
45The Tail Node
- Notice in the picture that the last node in the
list had its link pointer set to NULL. - Thus, we can test to see if we are at the last
node in a linked list. - If the link pointer of a node is set to NULL, we
are at the end of the list. If the link pointer
of a node is not set to NULL, it will point to
the next node in the list.
46The Tail Node
- In this way, the NULL pointer is used to indicate
the end of the linked list. - The last node in the linked list is called the
tail of the list. - So, the head of the list is the first node, the
tail of the list is the last node, and we can
tell that we are at the tail of the list if the
link pointer is set to NULL.
47Linked List Advantages
- Linked lists provide a number of advantages.
- First and foremost, they are perfectly sized for
the data that they store. When a data record is
encountered, a node to store it is allocated.
This defeats the limitations of arrays, where we
needed to know the array size at program compile
time.
48Linked List Advantages
- As linked lists are linked using pointers, it
is easy to manipulate them. - Two common operations that can be easily
performed on linked lists are the insertion of a
node into the list (at some point) and the
deletion of a node from the list.
49Linked List AdvantagesInserting a Node into the
List
Address 2370
Address 1080
Smith
Williams
Jones
90
20
100
1080
2370
NULL
8460
Node1_p
2370
50Linked List AdvantagesDeleting a Node from the
List
Address 2370
Address 1080
Smith
Williams
Jones
90
20
100
1080
2370
NULL
2370
Node1_p
51Linked List Traversal
- A common operation on a linked list is traversing
a list. This means that we keep following the
link pointers until we arrive at the end of the
list. - We can write a function that traverses the linked
list we have previously made, displaying the data
stored in each node.
52Linked List Traversal
void display_list(a_node2 head_p) a_node2
current_p if (head_p NULL) printf(Empty
List!) else current_p head_p while
(current_p ! NULL)
printf(s,current_p-gtlastname)
printf(d\n,current_p-gtnum_grade)
current_p current_p-gtnext_p
53Linked List Traversal
- We can also write a function that searches a
linked list for a target data item, in this case,
a last name.
54Linked List Traversal
a_node2 serach_list(a_node2 head_p, char
target) a_node2 current_p for(current_p
head_p current_p ! NULL
strcmp(current_p-gtlastname,target) ! 0
current_p current_p-gtnext) return
current_p