Title: *Pointers
1Pointers
- Annatala Wolf
- 222 Lecture 7
2List (a new container class)
- Intuitively, List is like a string of items with
a marker pointing to one of them. - Its similar to Sequence, except it isnt random
access. You can only access at the location of
the marker, but you can move the marker forward,
or send it to the start or the end. - Mathematically, List is modeled by ordered pair
of string of Item - The first string is the stuff before the marker,
and the second string is the stuff after it. - A List remembers where its marker is!
3Conceptualizing List
- Heres a List of Text
- (ltfoo, bargt, ltbaz, quuxgt)
- Normally we think of List in a loose sort of
string of Item way, but remember that the
location of that fence between the two strings
matters. The List above is not equal to the List
below - (ltfoogt, ltbar, baz, quuxgt)
- Thinking about the List as a pair of strings
rather than, say, one string with an index into
it, makes the specs much easier to read and
understand. - It also makes it easier to visualize what its
operations do.
4List_Kernel Operations
- Add_Right(x)
- Remove_Right(x)
- requires self.right gt 0
- Accessor current
- requires self.right gt 0
- Advance( )
- requires self.right gt 0
- Move_To_Start( )
- Move_To_Finish( )
- Left_Length( )
- Right_Length( )
All the action in a list will take place at the
location of the first item in the right
string. This makes Lists requires clauses
extremely simple! The only restriction is that
Remove_Right(x), current, and Advance() cant
be called when self.right is empty.
5List Examples (Tracing Table)
6Why List?
- Why would you choose List instead of a more
flexible component, like Sequence? - You might want to make sure you remember to
access items in a list-like manner. Choosing a
component that restricts how you can access data
is a common safety measure in programming. - List might also be much simpler to implement
- We might be able to get better performance from
List. - List might be so natural to implement with
pointers that it serves as a useful building
block for other containers, such as Sequence.
(As it turns out, this is the case.)
7Non-layered Kernels
- List, Queue, and Stack are very useful components
because they are easy to implement using
pointers. - Non-layered kernels (kernels written using only
pointers and default Resolve types in Rep) are
the best choice for the base component on which
containers like Array, Partial_Map, Sequence,
etc., are layered. - When we layer components, wed prefer the
layering be shallow. If we restrict the number
of levels of layers, we will improve
performance.
8Practice with List
- As a client of List, write a layered extension
for List that adds Retreat( ), the reverse of
Advance( ). - procedure Retreat( )
- /!
- requires
- self.left gt 0
- ensures
- self.left self.right
- self.left self.right and
- self.left self.left - 1
- !/
9Retreat( )
- procedure_body Retreat( )
-
- object Integer counter
- counter self.Left_Length()
- self.Move_To_Start()
- while (counter gt 1)
- self.Advance()
- counter--
-
-
- // What is the downside of implementing this as
an - // extension, rather than a Kernel operations?
10When Kernel is not enough
- If we implement List_Retreat as a layered
extension, it will run in linear time (bad) - This is because the Kernel operations dont give
us what we need to meet the performance
requirements we want - An alternative to this is to implement
List_Retreat directly (well do this in Lab 5) - It will be just like implementing a Kernel (same
restrictions), but well add a Retreat operation - Our List_Retreat wont depend on any other
implementation of List_Kernel, naturally
11A Few Pointers
xkcd.com
12Data and Memory
- Data is stored in memory
- abstractly, thoughsystem may control how memory
is actually allocated - Frequently, a large object will have all its
memory allocated adjacently - In C, strings are implemented as array of char
- For example, a string of characters might be
H
e
d
l
r
o
W
o
l
l
?
\0
13Bytes and Storage
- Each object in memory takes up a certain amount
of spaceeven primitive values do - Java characters are 2 bytes each (based on old
Unicode standard) - Older languages like C use 1 byte for char
- Integer size depends on the language, and for
non-standardized languages (like C) it can
depend on the architecture of the machine as well
(yes, this is a terrible annoyance) - Full object types like Integer or Character are
often larger still
14Addresses
- Every location in memory (even virtual memory)
has a numeric address - data memory addr. belongings home addr.
- Theres stuff at every location, whether youre
using it or not - Often, leftover garbage from previous use
100
102
120
118
116
114
112
108
106
104
110
122
124
126
096
098
128
H
e
d
l
r
o
W
o
l
l
?
\0
_at_
g
q
d
15Binary Storage
- At the lowest level, everything is stored
digitally, as individual bits - Heres the same string only with numeric
versions of the values inside
100
102
120
118
116
114
112
108
106
104
110
122
124
126
096
098
128
72
101
100
108
114
111
87
111
108
108
32
0
64
103
113
38
100
16Limitations of Static Memory
- Say you need to write a program that takes
Integer input from a user over and over until the
user enters the number -123, then prints all of
those Integers back in sorted order (by ). - Assume you dont have access to any container
objects, like Array or Set or Sorting_Machine - input gtgt num
- while (num ! -123)
- object Integer num2
- input gtgt num2
- // wait... now what?
- // num2 disappears each loop...
17Bad Solutions
- We cant just make a variable in a while loop
because that variable will disappear each loop. - We cant make 100,000 variables what if we need
100,001? - We could do this by using recursion. But imagine
trying to sort the values with a recursive
operation that can only access one or two values
at a time! - It can be done, but its not intuitive, and its
not the way we typically use recursion in C or
Java. - Functional programming languages, like LISP, do
use this approach to access dynamic memory.
18Dynamic Memory
- What we need is dynamic memory thats simpler to
use than recursion. Dynamic means we wont know
how much memory we need until the program has
been running a while. There are several possible
cases - the program relies on outside input, which is
unknown - the algorithm is solving some problem that needs
to run before we know how much memory it will
need to finish - its more convenient (in some cases)
- In procedural programming languages like Java and
C, dynamic memory is accessed in two ways - implicitly through recursion
- explicitly by using pointers
19How Dynamic Memory Works
- You already understand how recursion works!
- Pointers do it by separating the place a variable
is declared from the place its object is created - This lets us define objects that are capable of
linking to other objects of the same type,
without recursively creating 8 objects. We can
create exactly the number we need, when needed,
in a linked list. - It allows us to dynamically create a large array
of objects all at the same time, where we decide
at runtime how many objects we need. - It allows polymorphism to decouple dependencies.
- ( these are not done in Resolve, but well
discuss them later)
20Scope of Static Objects
- Static objects are allocated on the program
stack. Every time you hit an open brace, all the
objects declared inside that scope are created. - When you hit a closing brace, all those objects
go out of scope (cease to exist). - if (blah)
- object Integer i
- Print(i)
Program Stack
i 0
sub()
This frame in the stack (and its variables
and objects) will disappear at the closing brace.
main()
21You Have Been Spoiled
- When you create a stack-based object, youre
really doing a bunch of things at once. But you
need to understand that the things youre doing
are separate concepts. - Heres what actually happens when the compiler
runs - object Foo bar
- create a new variable (a name for an object)
called bar - set the type of bar to Foo (for safety, and for
instance operations) - then, allocate memory on the stack big enough to
hold a Foo - create a new Foo object in that space
- run the Resolve constructor to initialize the new
Foo object - This happens for all objects declared in a
particular scope when the preceding is
encountered (while code is running).
22The Difference!
- Static, stack-based objects vs. pointers
- Declaring a pointer gives you a variable, but
doesnt create an object until you tell it to!
Initially the pointer variable is dangling it
doesnt name a real object, and if you try to use
it like it has an object, its a (serious) error. - If your pointer goes out of scope and is tied to
an object, that object does not go out of scope
(it persists after the pointer is gone). This
can also lead to serious errors. - Pointers generally allocate memory on the heap,
not the program stack. - In C, you can allocate a dynamic, primitive
array with a pointer (but we dont do this in
Resolve).
23Pointers
- Essentially, a pointer is just a number. Its
usually represented as an unsigned integer. - The number refers to a location in memory, and
can be used to get the value at that location.
If the value at address 128129 is a
pointer, it references the value living at
address 100.
100
128
72
101
100
108
114
111
87
111
108
108
32
0
64
103
113
38
100
24Pointer Types
- Pointers are a parameterized type. They need to
know the type of the objects they can point to,
for two reasons - Type safety so you can use the object correctly
- Size so the computer can allocate the right
amount of memory (and make dynamic arrays)
If the value at 128129 is a Foo pointer, and a
Foo objects Rep needs 2 bytes, then the pointer
currently refers to 100101 when that value is
treated like a Foo.
Each box here means 2 bytes.
100
128
72
101
100
108
114
111
87
111
108
108
32
0
64
103
113
38
100
25The Heap
- The word heap means several things in computer
science. In the context of memory, it refers to
the place in memory where objects are created
dynamically (not counting how you can use
recursion on the program stack to get memory). - Theres not usually a cutoff between the stack
and the heap. Generally the stack grows up, the
heap grows down, and when they meet youre out of
memory (segmentation fault you tried to put a
stack variable into the heap segment or vice
versa).
The Heap
Big object!
memory not in use (random crap here)
(tiny object)
i 0
no wasted memory down here
sub()
main()
Program Stack
26Pointers? Fear Not!
- Many people consider this to be the hardest part
of 222 - But pointers are easy! (LIFE is hard.)
xkcd.com
27Pointer Terminology
- alias when two pointers (both alive, generally)
are referencing the same object (so one object
has multiple names) - alive, valid a pointer that is currently
pointing at a valid object - bad, dangling, dead, or wild a pointer that is
not NULL, but is not pointing at a valid object
(this is dangerous) - dereference to get the contents of what a
pointer is pointing to (only works if its alive) - reference another name for a pointer, or what a
pointer does (it references an object) in C
this often means a living, aliased pointer you
cant reassign that acts like its not a pointer - This is secretly the way that passing by
reference works! It passes a copy of a pointer
to the object, but acts like its a stack-based
object, and dereferences the pointer
automatically when used. So if you modify the
object via the alias, it modifies the original.
28Minor Differences
- The terms bad, dangling, dead, and wild are all
non-standard. They can be used interchangeably,
but the following terminology is most common - The only official terminology is valid pointer
for living pointers (though alive is very
common), and invalid pointer for everything else
(meaning NULL, dangling, or wild pointers). - A dangling pointer is a pointer after its object
is deleted. - A wild pointer is a pointers initial value (only
in languages where pointers receive no default
initial value). - Some authors use either of the terms dangling or
wild to mean both dangling and wild pointers (our
book does this). - Dead usually means dangling, but could mean
invalid, wild, or both dangling and wild. - Bad has no standard meaning. It usually means
invalid.
29Pointer_C(the safer, checked version of
Pointer)
- Intuitively, modeled by pointer to Item
- No default value! Pointers begin wild.
- Instantiated as Pointer_CltItemgt
- Dont instantiate it as a class. Instead, inline
the template directly into your declaration - object Pointer_CltFoogt fptr
- Pointers support
- p q assignment (set the value of the
pointer) - p q (or !) equality testing (test for
aliasing) - p dereferencing (access the object it points
to) - (If p is read pee, I generally read p
like star-pee.)
30Global Pointer Operations
- New(ptr) dynamic memory allocation obj. create
- allocates space for new object of type Item
- creates new object of type Item in that space
- stores the address of this new Item in ptr
- Delete(ptr) dynamic memory cleanup obj. destroy
- destroys the object (runs its destructor,
Finalize, etc.) - flags memory used by the Item at ptr for deletion
- this allows the memory to be reused later
- ptr is now dangling (it no longer points at a
valid object) - NULL a special constant pointers can be set to
- if we set a pointer to NULL, we can tell that
its not alive, for safety - but if a pointer isnt NULL, it can still be
dangling / wild
31Manipulating Pointers
- The operator copies the value of one pointer
into another pointer. It can copy NULL, or a
dangling value, or it aliases one pointer to
anothers object. - The dereference operator (p) returns a reference
to the object the pointer points to, but only if
p is a living pointer. Otherwise, its a
dereference error. - The operator tests to see if two pointers are
aliased (you can also test if a pointer NULL) - p q true iff the pointers refer to the same
object - p q true iff the objects the pointers refer
to are equal to each other, but not necessarily
the same object (only possible if the type of p
has operator)
32Using New and Delete
- With a stack-based object, the object is created
when the opening brace is encountered, and
destroyed when the closing brace is encountered. - A heap-based object is created when New() is
called on a pointer, and destroyed when Delete()
is called on any pointer that currently points to
that object. - If a pointer is destroyed (a pointer on the stack
goes out of scope, or a pointer on the heap is
Deleted), this does not delete the object the
pointer points to! - Just keep in mind the syntax for deleting the
object p is Delete(p), not Delete(p), since you
can only Delete things by using a pointer to
them. New() works the same way.
33Why Delete?
- Delete is necessary because its the only way the
computer knows youre done using dynamic memory. - Java doesnt need to delete objects because it
uses smart pointers that keep track of how many
references exist. When that number drops to
zero, Java flags the object for garbage
collection. - A small loss of performance is exchanged for
safety. - In C, however, if you fail to Delete something
and lose the last reference to it, you have a
memory leak a region of memory you can no longer
access until the program terminates and the OS
frees up the memory. - If a memory leak happens repeatedly, it can crash
your program because youll run out of memory.
34How To Think About Pointers
- Every time you say object Foo bar, you create a
new name for an object, and you also create a new
object at the same time. Those two are then
stuck togetheryou always use that name to refer
to that object (unless you pass it by reference,
but that uses pointers). - With pointers, you create the name first. You
can create the object later, or even attach the
name to a different object that already exists
(thus giving it more than one name an alias). - When youre done using an object, you have to
destroy the object to get the memory back.
35Using Pointers
- // Create a name for a Foo object, called baz.
- // Unlike a stack-based object, this does not
create a - // new Foo object (yet). Currently, baz is wild!
- object Pointer_CltFoogt baz
- // Set the value of baz (that is, the place it
points to) - // to the value of quux. Now they point to the
same - // place, and if quux is alive, they alias the
same object. - baz quux
- // Create a new object and attach baz to that
object. Now - // it must be true that baz is no longer is
aliased to quux. - New(baz)
- // You use (baz) like you would a stack-based
Foo object. - (baz).FooProcedure() // baz is alive, so
use star-baz
36Pointers In Resolve/C
- To limit pointer errors, we only use pointers
inside Kernels and non-layered extensions (that
is, at the lowest level). - When we use pointers in this class, they will
usually point to a Record of some sort.
Unfortunately, binds later than . This
means the computer always tries to run before
, and you have to use parenthesis to get the
order right. - object Pointer_CltNodegt p // new pointer on
stack - New(p) // new Node record
on heap - // dereference p, then use to get ps Nodes
data field - (p)data x
- // Error! Tries to use on a pointer. (Which
is silly.) - // pdata x
37Pointer Pictures
- I use the following conventions for pointers in
this class - Pointers are triangles with arrows coming out of
them - The value of a pointer is where the arrowhead
points - NULL pointers, dangling pointers, etc. see
illustrations
3
p
a known-to-be- dangling pointer
a pointer whose value is unknown
3
q
p, q, and r are living pointers to Integer
objects. Notice p q p q p r
p ! r
a NULL pointer (poor flaccid guy)
r
38Nodes
- A common use for pointers is to create some
arbitrarily big recursive structure, like a
string or a tree. Think of it like data arranged
in a graph - Each node in the graph holds
- a piece of the data (an Item)
- one or more pointers to other nodes
- // example Node fields from Stack_Kernel_1
- field_name (Node, 0, Item, data)
- field_name (Node, 1, Pointer_C ltNodegt, next)
3
4
8
0
(Node) RecordltItem, Pointer_CltNodegtgt
(Item)
data
(Pointer_CltNodegt)
next
39Linked Lists
- A linked list is a common data structure used
to hold a dynamic amount of information. - Each node in a linked list contains a pointer to
the next node in the list. - Inserting a new node into a linked list takes
constant time, but finding the location is linear.
data field
the rest of the linked list
next field (pointer to the next node)
first node
second node
40Example Using Linked Lists
- /! requires first pts to first node in a linked
list - ensures x added into a new node, to front
of list, - and first is updated
!/ - procedure Prepend (Pointer_CltNodegt first, Item
x) - // make a new node and stick x into it
- object Pointer_CltNodegt insert
- New(insert)
- (insert)data x // consumes x !
- // stick insert into the list and update
first - (insert)next first
- first insert
-
-
41Contract
- requires first pts to first node in a linked
list - ensures x added into a new node, to front of
list, - and first is updated
Our job is to make a new Node for x, link the
Node to the front of this linked list, and
finally update first.
A linked list must have at least one node in it,
so we know first is alive.
first(Pointer_CltNodegt,passed by reference)
This could be alive, dangling, or NULL. (We
dont know the length of the linked list, or
even what convention may apply.)
?
?
x(Item, passedby reference)
(first)(must exist)
42Make a Pointer (on the stack)
- object Pointer_CltNodegt insert
passed in by referencesecretly is a safe
pointer to matching argument could be on stack
or heap
somewhere on the heap
the top of the program stack
insert
first
?
x
(first)
43Make a New Node
passed in by reference
somewhere on the heap
the top of the program stack
Ø
(default values)
insert
first
(insert)
?
x
(first)
44Swap in the Data
passed in by reference
somewhere on the heap
the top of the program stack
ltOM NOM NOM SWAPgt
insert
first
(insert)
Ø
?
x
(first)
Consumed!
45Link New Node to First Node
passed in by reference
somewhere on the heap
the top of the program stack
insert
first
(insert)
Ø
?
x
A new name (alias) for this Node object!
(first)--- or ---((insert)next)
46Update first Parameter
passed in by reference
somewhere on the heap
the top of the program stack
Aliased!
insert
first
(insert)--- or ---(first)
A new name (alias) for this Node object!
Ø
?
x
This Node object lost the name (first), but
gained the name ((first)next).
((insert)next)--- or ---((first)next)
47Finish the Operation
passed in by reference
somewhere on the heap
the top of the program stack
We can reach everything no memory leaks.
insert
first
(first) (now, its only name)
Gone out of scope! The pointer gets deleted, but
its object is fine. This is not the same as
Delete(insert), which would delete the object.
Ø
?
(Still dunno where this guy points...)
x
((first)next) (now, its only name)
48Linked List Conventions
- To use a linked list, you will need a pointer to
the first node so you can access it (unless the
first node is on the stack, which is uncommon).
In Resolve, it will typically be part of the Rep.
Another way to tell when a list ends is by using
a convention, like the last pointer in the list
is NULL.
(Rep) RecordltPointer_CltNodegt, Integergt
(Pointer_CltNodegt)
(selftop)
top
(Integer)
One way to tell when a list ends is by caching
the lists length.
2
((selftop)next)
length
self
49Traversing Linked Lists
- To traverse a linked list, youll want to make
use of aliasing. Its often a lot easier to use
an alias than to write a really long expression,
and sometimes you need to use one. - You have to hold on to all the data with
something, at all times. If you ever lose all
pointers to some part of the list, you have a
memory leak. ? - // make a copy of the pointer to the first node
- object Pointer_CltNodegt alias selffirst
- // then move the pointer to the next node
- alias (alias)next
50Visualizing Traversal
p (p)next
(p)next
p
p
This works even if the next node doesnt exist
(but the current node must exist). If its the
last node in the list, p now contains what the
last node points to (NULL or dangling).
To get to the next node, we alias our traversing
pointer to the next node in the list.
51Pointer Practice!Node RecordltItem data,
Pointer_CltNodegt nextgt
Get into groups! Use an alias to traverse the
list.
- local_function Item Get_Item (
- preserves Pointer_CltNodegt first,
- preserves Integer index )
- /!
- requires
- index gt 0 and
- first points to the first node of a
singly-linked list containing at
least (index 1) nodes - ensures
- Get_Item reference to the data field
of the (index 1th) node in the
linked list, starting from first - !/
(Pointers are easy to pass by copy.)
52Get_Item
- local_function Item Get_Item ( preserves
Pointer_CltNodegt
first, preserves Integer index) -
- // make an pointer and alias it to first node
in list - object Pointer_CltNodegt search first
- // advance the alias through the list until
we hit index - while (index gt 0)
- search (search)next
- index-- // passed by copy, so ok
-
- // search is now an alias for the node at
index - return (search)data
53Finalize
- Destructors must clean up all of the memory
allocated by an object over its lifetime. - Rep does this for you automatically for its
fields. - Just before Rep does this, the Finalize( )
operation (if it exists) is called. Its only
job is to Delete anything that was allocated. - If you allocate memory dynamically, you will need
to implement Finalize() or your objects will leak
like a sieve.
54Sequence_Kernel_1_Naïve
- Representation
- selffirst Pointer_CltNodegt
- selflength Integer
- Correspondence
- if self.length 0 ltgt
- else the data fields in the singly-linked list
that begins with (self.first), in order - Convention
- self.length gt 0, and if self.length gt 0 then
self.first points to a singly linked list with
self.length nodes
Node is a Record Item data P_CltNodegt
next
55SK1NRemove(pos, x)
- procedure Remove(preserves Integer pos, produces
Item x) - object Pointer_CltNodegt target selffirst,
before NULL - while (pos gt 0)
- before target
- target (target)next
- pos--
-
- object Pointer_CltNodegt after
(target)next - (target)data x
- if (before NULL)
- selffirst after
-
- else
- (before)next after
-
- Delete(target)
- selflength--
When we remove a node, to patch the list, we will
need to fix the node that points to it. We need
to keep track of the previous node to fix it.
If we just removed the first node, we have to fix
selffirst instead!
Otherwise, just link the previous node up with
the next.
56Practice! SK1NAdd(pos, x)
- Node next, data
- Rep first, length
- no null convention
- length number of nodes in linked list
- Write Add(pos, x) for Sequence
- hint you need to consider two cases, and when
walking through the list you need to stop one
step before where youll be adding the item - you will probably want to make two pointers
Get into groups of 3-5!
57SK1NAdd (pos, x)
- procedure Add(preserves Integer pos, consumes
Item x) - object Pointer_CltNodegt before, insert, after
selffirst - New(insert)
- (insert)data x
- object Integer afterIndex
- while (afterIndex lt pos)
- before after
- after (after)next
- afterIndex
-
- (insert)next after
- if (pos 0)
- selffirst insert
- else
- (before)next insert
-
Create aliases, make new node, consume x.
Move through list keep track of nodes before and
after the planned insertion.
If first node, update first
else update node before.
58Other Approaches
- There are other possible plans of attack none is
superior. Aim for clarity. - If long expressions make sense to you, use them.
If more aliases seem to make things simpler, use
aliases.
object Pointer_CltNodegt second second
(selffirst)next return (second)data //
equivalent // return ((selffirst)next)data
59The Special Case
- The reason I say Sequence_Kernel_1_Naïve is
that this model differs slightly from the actual
Sequence_Kernel_1. Specifically, SK1 uses a
trick to remove the special case. - The reason we need a special case in SK1 is that
one of the pointers to data nodes in the list is
not in the linked list itself. Namely,
selffirst must be updated if we change the
first node. - While this is hardly a major inconvenience now,
if the linked list is a little more complicated,
it can become a real headache.
60Sentinel Node
- A sentinel node (or dummy node) is a node used to
mark a boundary, rather than to hold data. - The basic idea if we include an extra node at
the front of the list that doesnt hold data, we
can generalize the algorithm so we no longer need
to treat the first node as a special case.
This data field is unused.
This is the data at index 0.
pre_front
?
(remainder of list)
length
sentinel node
regular data node(index 0)
self
61Terminology Note
- The book refers to sentinel nodes as smart
nodes to make a point that the nodes are useful
lest you mistakenly interpret the term dummy as
stupid rather than placeholder. - Please be aware that this is not typically what
the term smart means in programming parlance.
Smart usually means an enhanced or
self-maintaining version of an object. - For example, Pointer_C is a smart pointer.
62Add() Using Sentinel Node
- procedure Add(preserves Integer pos, consumes
Item x) - object Pointer_CltNodegt before
selfpre_first, insert - object Pointer_CltNodegt after
(before)next - New(insert)
- (insert)data x
- object Integer afterIndex
- while (afterIndex lt pos)
- before after
- after (after)next
- afterIndex
-
- (insert)next after
- (before)next insert
- selflength
This time, the node before the insert must also
be in the list (selfpre_first is a sentinel).
Move through list, just as before. All of the
pointers to data nodes are now contained in the
linked list.
Now theres only one case to fix!
63List_Kernel_1
In LK1, we use a sentinel node at the start of a
singly-linked list. There are (left_length
right_length 1) nodes in the list. The last
node in the linked list is pointed to by
finish. The node that holds the last data item
in the Lists left string is pointed to by
last_left (its equal to pre_start if left is
empty).
pre_start
last_left
Get into groups of 2-4 and write Initialize.
finish
?
4
left_length
sentinel node
regular data node
right_length
here, abstract self (ltgt, lt4gt)
self
64private LK1Initialize()
- local_procedure_body Initialize()
-
- New(selfpre_start)
- selflast_left selfpre_start
- selffinish selfpre_start
65Thinking about LK1
- The sentinel node eliminates some special cases,
but not all of them. - For example, in both Add_Right(x) and
Remove_Right(x), there is a special case when you
need to update selffinish. This is because
theres no sentinel node at the end in LK1. - If you add or remove the last node from the
linked-list, you have to update selffinish.
66Doubly-linked List
(Node)
- A doubly-linked list is similar to a
singly-linked list, except you can traverse the
list in both directions.
data
next
previous
The price we pay for this flexibility (apart from
the obvious tiny increase in space for extra
pointers) is a small amount of extra maintenance.
67List_Retreat_2
- In Lab 5 your job is to write the component
List_Retreat_2. Its very similar to
List_Kernel_1, except - Its a non-layered extension, so in addition to
implementing all eight List operations, you need
to implement Retreat(). - It uses a doubly-linked list to hold the data!
- It uses two sentinel nodes one at the beginning,
and one at the end. (This actually makes the
algorithms much easier!)
68List_Retreat_2
- For List_Retreat_2 (in Lab 5), the value of Rep
for an empty list should be a doubly-linked list,
something like this picture.
pre_start
last_left
Having two sentinel nodes will eliminate more
special cases, which makes it easier to write the
algorithms.
post_finish
0
?
?
left_length
0
right_length
self
69Practice Doubly-Linked ListsGet into groups of
3-5!
- local_procedure Remove (
- produces Item x, preserves Pointer_CltNodegt
target - )
- /!
- requires
- there exists i, j Integer where
- (i gt 1 and
- j gt i and
- target points to the ith node
of a - doubly-linked list containing j
nodes) - ensures
- x target.data and targets Node
is deleted - and the doubly-linked list is
repaired - !/
70Remove(x, target)
- local_procedure Remove (
- produces Item x, preserves Pointer_CltNodegt
target - )
- x (target)data
- object Pointer_CltNodegt left
(target)previous - object Pointer_CltNodegt right
(target)next - (left)next right
- (right)previous left
- Delete(target)
These aliases arent necessary, but they make the
code much easier to write and follow.
71State of a Pointer
- A variable can be in one of four states
- Alive / Valid (refers to an actual object)
- Any kind of variable
- Out of scope (no longer available to use)
- Any kind of variable
- NULL (flagged, so you know its invalid)
- Java references, C/C pointers
- Bad / Dangling / Dead / Wild (dangerous)
- C pointers only
72NULL At least it warns you!
- If you know a pointer is NULL, you know its
badso using NULL to flag bad pointers is a
common convention for avoiding errors.
Frisby (omgfrisby.com)
73Pointer Reference Errors
- Aliasing error when two references refer to the
same object, but you act like theyre two
objects. - If you change one, the other changes too. In
some circumstances this can be extremely
dangerous. - To avoid aliasing errors, limit aliasing to short
sections of code (local scope). - Dereferencing NULL just what it says.
- It will stop the program if not caught, but is
not dangerous. - You can catch this at runtime with exception
handling. - Deleting NULL not an error!
- The program just ignores this command.
74Pointer Errors (Serious)Generally found in C,
C, C
- Memory Leak youre losing (leaking) available
memory (eventually this will cause a crash). - You can no longer access an object you created on
the heap. - If you drop the last pointer to an object on the
heap, you cant ever free that memory. - Using invalid pointers when you dereference or
delete a non-NULL, invalid pointer. - You will end up changing memory in arbitrary
ways. - These errors are not only dangerous, they can be
truly impossible to debug!
75When Errors Arent
- When working with RESOLVE/C without pointers,
these problems are easily avoided. - Pass-by-reference is fine, because the incoming
reference is always alive and the pointer
cant be reassigned to something bad. - The swapping paradigm avoids much of the need for
handling pointers directly, as does the use of
templates instead of polymorphism. - RESOLVE discipline always encapsulates pointers
at the Kernel level, so if your Kernels work, you
wont ever get pointer errors.
76Life of a Stack-based Object
Stack-based objects are deleted when you leave
the scope in which they were declared/created.
All variables you declare on the stack are
alive once you declare them (or, for
parameters, once the operation starts).
77Life of a Java Reference
Memory leaks cannot result when a living
reference changes Java keeps track of objects
and deletes for you automatically.
Dereferencing a null pointer is the only
reference-type error possible in Java (and its
catchable).
Java references (which are pointers) start with a
default value of null. They cant be explicitly
deleted, so they are never dangling.
If a reference isnt a data member, Java code
wont compile unless you assign it a value before
using it.
78Life of a C/C Pointer
Dereferencing NULL is catchable, fortunately.
If you reassign a living pointer, or it goes out
of scope, and it was the last reference to its
object this is a memory leak.
When a pointers object is deleted, all aliases
equal to that pointer also change state from
alive to dangling, spontaneously.
If you use an invalid pointer as though it were
alive this is an invalid pointer error.
These two errors are extremely dangerous!
79Spontaneous Transition
- The green transition illustrates a situation that
may lead to serious errors. - Any time a pointers object is deleted, the
object is no longer safe to use. Consider
?
Delete(p)
Now what happens?
p
p
?
q
q
80Dangers of Careless Aliasing
- When Delete is called on pointer foo, all
pointers aliased to foo spontaneously die, even
though no code mentions these pointers
explicitly! - It is possible for the status of a pointer to
change from alive to dangling, even when no code
refers to that pointer. - This is one reason aliasing is best kept to local
scope.
Delete(p)
Now, both p and q are dangling!
p
p
q
q
81Study Note
- The remaining slides in this lecture are useful
practice for thinking about pointers, but you
wont be tested on the contents. - So dont study them too hard. ?
82Other Graph Structures
- Its possible to use pointers to allocate memory
dynamically in any sort of directed graph
structure, not just a linked list. - For example, we could easily use pointers to
create a node-based binary tree structure
83Object-Based Pointers
Rep for Binary_Tree_Kernel_1
- Another approach would be to use a Rep that
includes one or more pointers to the objects own
type (rather than to a primitive Node). - If the object is a recursively-defined structure
where each child should be able to do all the
stuff the parent object should, this makes sense.
data
left_subtree_ptr
right_subtree_ptr
size
height
self
84Binary_Tree_Kernel_1
- correspondence
- if self.size 0 self empty tree
- else self (self.data root,
self.left_subtree_ptr left_subtree,
self.right_subtree_ptr right_subtree) - convention
- if self.size 0 both pointers NULL and
self.data is its default value - else both pointers are alive
- self.size self
- self.height HEIGHT(self)
- Get into groups of 3-5 and write
- Initialize()
- Finalize()
- Compose(x, left, right)
- Decompose(x, left, right)
Rep for Binary_Tree_Kernel_1
data
left_subtree_ptr
right_subtree_ptr
size
You may want to start Compose() with
self.Clear(), and two New()s.
height
self
85BTK1 Local Operations
- procedure_body Initialize ()
- selfleft_subtree_ptr NULL
- selfright_subtree_ptr NULL
-
- procedure_body Finalize ()
- Delete(left_subtree_ptr)
- Delete(right_subtree_ptr)
-
Why dont we need to iterate through the tree to
free everything up? Because the pointers here
are to objects, not simple Nodes. (When deleted,
each subtree runs Finalize() and calls Delete on
its subtrees!)
Remember, a call to Delete using a NULL pointer
will just end up being ignored.
86BTK1Compose
- procedure_body Compose (
- consumes Item x,
- consumes Subtree_Type left_subtree,
- consumes Subtree_Type right_subtree
- )
- self.Clear()
- selfdata x
- New(selfleft_subtree_ptr)
- New(selfright_subtree_ptr)
- selfsize 1 left_subtree.Size()
right_subtree.Size() - if (left_subtree.Height() gt
right_subtree.Height()) - selfheight left_subtree.Height() 1
- else
- selfheight right_subtree.Height()
1 -
- ((selfleft_subtree_ptr)) left_subtree
- ((selfright_subtree_ptr))
right_subtree
This will ensure x gets consumed, and the old
subtrees get deleted.
Swap will work as long as our tree pointers are
valid.
87BTK1Decompose
- procedure_body Decompose (
- produces Item x,
- produces Subtree_Type left_subtree,
- produces Subtree_Type right_subtree
- )
-
- selfdata x
- ((selfleft_subtree_ptr)) left_subtree
- ((selfright_subtree_ptr)) right_subtree
- self.Clear()
This is the fast way to clear selfdata and set
both pointers to NULL.