Anda di halaman 1dari 103

Pointers and Dynamic

Memory

Memory Management
Memory management
Arrays
Dynamic memory
Dynamic arrays and pointers
Building a vector class

Memory Management

Memory Requirements
code
storage
data
storage

Memory Management
When

a program runs memory has


to be allocated for its processes and
its variables
That is, working memory, or RAM

There

needs to be a system for


efficiently allocating such memory
We will just consider how memory is

allocated to program data (variables)

RAM
We

can consider Random Access


Memory (RAM) as a long sequence of
bytes
Starting with 0
Ending with the amount of main memory

(-1)
RAM

is addressable and supports


random access
That is, we can go to byte 2,335,712

without having to visit all the preceding

RAM Illustrated
Consider a computer with 1GB * of RAM
*1 GB = 1,073,741,824 bytes
0

107374181107374181107374181107374181107374182107374182107374182107374182
6
7
8
9
0
1
2
3

This is a fairly abstract


illustration (so ignores a
variety of issues)

Goals
As

much as possible we would like to


satisfy three goals when allocating
memory
Time efficiency
That is, we should be able to quickly find

sufficient memory to store a variable


Space

efficiency

We don't want to waste memory

Low

overhead

We don't want a large amount of

Stack Memory
There

is a very simple way of


organizing memory that meets these
goals
Assign memory locations to variables in

sequence
With no gaps

Once a variable is no longer required

release the memory, allowing it to be


over-written
We

will call this organization the

Stack Memory Simple


Example
Let's look at a simple example of allocating
memory on a stack as described preciously
int x = 12;
x = x * 3;
double d = 23.567;
36
12

23.567

3600 3601 3602 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612

Why start at byte


No reason, it's just an arbitrary
3600?
value
Notice that this example ignores all sorts of
complicating issues, functions, reference variables and
so on

Stack and Functions


Let's

look at a more complex


example of allocating memory
That includes a main function and two

other function calls


To

make the example a bit simpler I


will stop showing actual byte values
And just use coloured boxes to represent

the memory allocated to variables

Another Example
int main(){
int r = 3;
double area =
circleArea(r);
double square(double
x){
return x * x;
}
main memory
x
3.141
3
3
3
5
r
radius
pi
square's

double circleArea(double
radius){
double pi = 3.1415;
double sq_r =
square(radius);
return sq_r * pi;
}

memory

start of circleArea's memory

Another Example
int main(){
int r = 3;
double area =
circleArea(r);
double square(double
x){
return x * x;
}
main memory
3.141
3
3
9
5
r
radius
pi
sq_r
r

radius

pi

double circleArea(double
radius){
double pi = 3.1415;
double sq_r =
square(radius);
return sq_r * pi;
}

sq_r

start of circleArea's memory

Another Example
int main(){
int r = 3;
double area =
circleArea(r);
double square(double
x){
return x * x;
}
main memory
28.27
3
4
r
area
r

area

double circleArea(double
radius){
double pi = 3.1415;
double sq_r =
square(radius);
return sq_r * pi;
}

Stack Memory
In

stack memory variables for a


function are allocated the next bytes
in main memory
From the last variable allocated space by

the same program


Once

a function ends its memory is


released
Remember that a function's variables

only exist while the function is executing


So the memory previously allocated to a

Reference Parameters
Some

functions have reference


parameters
There are two common reasons for
doing this
Efficiency
int sum(const vector<int> & v)
Sums the contents of the vector

And

because we want the changes


made to the variable to persist after
the function

Stack Problems
There

is one big issue with stack


memory
Because memory is allocated in

sequence it is not possible to change the


byte size of a variable
Strings

and vectors frequently


change size
It is more correct to say that a string

variable may refer to strings of different


sizes

Changing a Vector's Size


int main(){
vector <int>
vec(3);
v[0] = 1;
v[1] = 2;
v[2] = 3;
double vid =
2.397;
main
memory
insert_next(3,
vec);
1 2 3 2.397
vec

vid*
*very important
double

void insert_next(int n, vector


<int> & v){
for(int i=0; i < n; i++)
v.push_back(i + v.size() +
1);
}
}

Changing a Vector's Size


int main(){
vector <int>
vec(3);
v[0] = 1;
v[1] = 2;
v[2] = 3;
double vid =
2.397;
main
memory
insert_next(vec,
3);
1 2 3 2.397
4
vec

void insert_next(vector <int> &


v, int n){
for(int i=0; i < n; i++)
v.push_back(i + v.size() +
1);
}
This is a problem,
}
we've just corrupted
the first 4 bytes of
vid
3 o

vid*
v*
n
i
*very important *v is a reference to vec, it
double
actually contains the byte
address of vec

Types Of Memory
It

turns out you can divide memory


into three broad categories
Static
Automatic
Dynamic

These

types of memory are typically


used for different sorts of program
data
Although C++ gives programmers

choice over which type of memory to

Storage
code
storage
static
data
storage
dynami
c
automati
c

Static Storage
Statically

stored variables last for the


lifetime of a program
The number of static variables does
not change as a program runs
So no special system is required to

maintain them
Static

storage is used for global


constants
And other things

Automatic Storage
Function

variables and parameters


use automatic storage by default
Automatic variables are typically
managed on a stack as we have
been discussing
The stack is an allocated area of

memory that grows and shrinks as


functions are called
Memory is allocated sequentially
(without gaps)

Functions and Memory


A

variable defined in a function block


only persists for the lifetime of that
function call
Unless it is declared as static

Consider

what memory might be


allocated when a function is running
Memory required for the functions data

and only required during the function


call
Memory that is to persist beyond the

Arrays
A Digression From Memory

Arrays
Before

looking at dynamic memory


let's look at a basic data structure
the array
Arrays are used to store multiple values

of the same type, much as vectors do


Except that arrays are much more basic
data structures without methods (such
as size)
Arrays

are indexed and we can either


refer to individual elements or the

What Is An Array?
An

array variable is a collection of


other variables
You can think of an array as something

that contains variables


This is important because an integer
array is not an integer, it is a collection
of integers

The

items stored in an array


(elements) are stored sequentially in
main memory
Just like variables on the stack

Declaring Arrays
An

array is declared with a type, and


[]s to indicate that the variable is an
array
The type declares the type of the array
elements name of the
array
size of the
int myArray[10]
array

type of the
data stored in
the array

brackets declare
that the variable is
an array

Array Indexing
The

elements of the array are


accessed using an index
The indexes are addresses of the

elements
The first element always has an index of 0
The last index is always array size 1

Array

indexes follow the name of the


array and are enclosed in []s
Individual array elements are used in

exactly the same way as variables

Using an Array
double arr[4]; // array
arr[0] = 1.23; //assign
arr[3] = 2.14; //assign
double x = 0;
x = arr[3]; //access 4th

1.23
arr[0]

2.14
arr[1]

arr[2]

arr[3]

of 4 doubles
1st element
4th element
element

2.1
0
4
x

the table represents main memory, each cell is an 8 byte


double

Initializing Arrays
Array

elements have to be given


values individually
This is often done in a loop

There

is a special shorthand notation


for initializing arrays when they are
declared
int arr[] = {1, 3, 7, 9};
This shorthand notation is only allowed

on the same line as the declaration

Array Size
An

array's size must be specified


when it is declared
It must be a literal (i.e. a number) or
A constant
It can't be given using a variable

An

array's size cannot change during


the life of a program
Arrays can therefore be allocated space

in automatic memory as their size


cannot change

Arrays and Vectors


Arrays

are not vectors, and are not


classes
Arrays do not have methods
Like size or push_back
The size of an array has to be recorded

in a separate variable, and may not be


changed
An

array is essentially a way of


referring to a collection of values of
the same type

Arrays and Loops


Arrays

are often processed using

loops
Allowing each element to be accessed in

turn
The loop control variable is used as an
index into the array
The loop ends once the array has been
processed
Often when the loop control variable is equal
to the size of the array

This

is very similar to how we have

For Loops
// Assume an int array named arr,
// with a constant called ARR_SIZE
// The loop prints the contents of arr
for (int i = 0; i < ARR_SIZE; i++){
cout << arr[i] << endl;
}
The condition is i < ARR_SIZE
because the last legal index is
ARR_SIZE 1
Assumes the existence of a constant
called ARR_SIZE, e.g. const int
ARR_SIZE = 10;

While Loops
// Assume an int array named arr,
// with a constant called ARR_SIZE
// The loop prints the contents of arr
int i = 0;
while (i < ARR_SIZE){
cout << arr[i] << endl;
i++;
}
A very similar loop to the for loop,
we must not forget to increment
the index, i

Size as a Constant
It

is much better to define array size


as a constant than to use a literal
value (like 10)
If the programmer wants to change
the array size this only needs to be
done once
Use the constant whenever the array

size is referenced, and


Avoid using magic numbers!

Index Out of Bounds


What

happens if a program attempts


to access a non-legal index?
A run-time error occurs, either
An illegal attempt to access a memory

location has been made ( stack is


corrupted ) , or
Something less predictable
Always

check that an index is legal

Between 0 and array size 1

Arrays And Functions


Array digression continues

Array and Functions


Arrays

can be passed to functions as


parameters
Parameter lists can include arrays
The array type, and the fact that it is an

array must be specified in the parameter


list
An array is specified just like any declaration

Here

is the header for an array sum


function
int sumArray(int arr[], int sz){

Sum Function
// Sums the array, returning the sum
int sumArray(int arr[], int sz){
int sum = 0;
for (int i = 0; i < sz; i++){
sum += arr[i];
}
return sum;
}
sz is used to specify the size of the
array
To use this function give it the
appropriate arguments
x = sumArrray(myArr, ARR_SIZE);

Searching Arrays
A

common array task is to search an


array for a particular value

int search(int arr[], int sz, int x){


int result = -1;
for (int i = 0; i < sz; i++){
if (arr[i] == x)
return i;
Returns the index of the target as soon as
}
is found
return itresult;
}
Returns -1 (an invalid index) if the target
isn't found

Aside Using Arrays


Make

sure that you distinguish


between an array and its contents
int arr[4];
arr is the entire array
arr[1] is one element of the array

The

array is a sequence of integers,


one element is an integer
They are not interchangeable

Is the Array Full?


It

is often important to know how


much of an array is used
Unassigned elements shouldn't be

processed
e.g. summing or calculating the average of an
array

Consider

what input a function

requires
Array, and its maximum size, or
Array, and current size ,or

Returning Arrays
Arrays

can be returned from a


function
However the obvious
This
does
way
of not
specifying
work!
the return type is illegal
int[] getAnArray() {
In addition, returning an array raises the

question of how big the array is


Before

continuing this we need to


know more about how arrays really
work

Arrays And Memory


What is an array really?

Changing Arrays
What

happens if you pass an array to


a function that changes the array
parameter?
void doubleArray(int arr[], int sz){
for (int i = 0; i < sz; i++){
arr[i] = arr[i]
2; this is not
Notice *
that
}
pass by reference
}

Array Parameters
int main()
{
double arr[] = {1, 2, 3};
printArray(arr, ARR_SIZE);
cout << "Running: void doubleArray";
cout << "(double arr[], int sz);" << endl;
doubleArray(arr, ARR_SIZE);
What has
printArray(arr, ARR_SIZE);
happened?
return 0;
The array passed
}

to the function
has changed.
This does not
happen with
other pass-byvalue

What's an Array?
To

understand what is going on in


the previous example we need to
know more
It's easy to think of an array as a

container, like a bookcase or a filing


cabinet
But these are structures in their own
right
An

array is just a collection of values

All of the same type, and

More About Arrays


An

array is a sequence of bytes in


main memory reserved for the array
contents
e.g. int arr[10];
Reserves 40 contiguous bytes
An int is 4 bytes, 4 * 10 = 40
Each element can be referenced using
indexes

The

variable arr is just the address


of, or a pointer to the first array
element

More About Indexing


Consider

this assignment statement:

arr[8] = 23;

To

find this array element

Look up the address stored in arr


Multiply type size (4 for an int) by the

index
Add this to the address in arr to find the
address of arr[8] element
Known as an offset calculation

Arrays and Functions


When

an array is passed to a
function it is passed much like a
reference parameter
That is, the function is given the main

memory location of the first item in the


array
But this is because array variables are
addresses, not because they are
inherently pass-by-reference
Because

the address is passed any

Pointer Basics

Arrays are Pointers


So

an array variable is really just the


address of the first element of the
array
Note that arr in int arr[10] is not of type

int
It is a type that called a pointer
Actually a pointer to an int

Array variables are special kinds of

pointer in that they are constant


The address stored in the pointer cannot
change

Variables Review
A

variable is a location in main


memory where data is stored
The type of a variable indicates the

amount of main memory required to


store the data, and
The operations that may be performed
upon the data
Every

variable has a byte address

Its location in main memory


Which is stored by the system in some

Main Memory
Main memory
is a sequence
of bytes
0

20
2220
-11

23

Reserves 4 bytes for x in


int x =
main memory and stores
23;
23
The address of each variable is kept
track of in something called a symbol
table

Pointers
A

pointer is a special type of variable

That stores an address rather than a

value
They are called pointers as they can be
considered to point to a variable
It

is necessary to record the type of


data that a pointer variable points to
So that the appropriate operations can

be performed on the value it points to

Pointers and Types


Pointers

store addresses

Addresses are always the same size on

the same system


Often 8 bytes

So

why do we have to say what type


of data is going to be pointed to?
To reserve enough space for the data

and
To ensure that the appropriate
operations are used with the data

Declaring a Pointer
Pointer

variable are identified by an *


that follows the type in the
declaration
int * p;

This

declares a variable called p that


will point to (or refer to) an integer
Note that the type of a pointer is not
the same as the type it points to
p is a pointer to an int, and not an int

Declaring a Pointer
Previously

I declared a pointer like

this
int * p;
The spaces are not necessary

You

can do this

int *p;

Or

this

int* p;

Or

even this
But this is kind of

int*p;

ugly!

Pointers and Values


The

operation shown below is illegal

int x = 12;
int *p = x;

Remember

error C2440: 'initializing' :


cannot convert from 'int' to
'int *'

that the type of p is an


address (to an int), and not an int

Addresses are actually whole numbers

but it is illegal to assign arbitrary


numbers to them
This is a good thing!

Address Operator
Pointers

can be assigned the address


of an existing variable
Using the address operator, &
In this way it is possible to access the

variable using a pointer


The

address operator is the same


symbol as the operator to denote
reference parameters
But they are different operators
Although they perform similar tasks

Main Memory and


Pointers
0

212

2201

23

int x =
23;
int* p =
&x;
0

212
409
6 address
the

p contains
of x
& is the address of
operator

23

2201

Dereferencing
Pointers

can be used to access


variables
But only after they have been assigned

the address of a variable


To

change the value of a variable a


pointer points to the pointer has to
be dereferenced
Using the * operator which can be

thought of meaning the variable pointed


to

Pointer Assignment
int x = 12;
int *p = &x; //assign p the address of x
// Use p to assign 23 to x
*p = 23; //dereferences p
cout << x << endl;
cout << p << endl;
address
cout << *p << endl; value of
of x
x
value of the
variable that p
points to (i.e. x)

The sizeof Operator


The

sizeof operator allows us to find


out the size of a type or variable
cout << sizeof(int);
cout << sizeof(c); //size of a char
cout << sizeof(Cylinder); //the size of a

Cylinder
sizeof

returns a number of bytes

Reference Operator
The

& operator is also used to create


reference parameters
Where a variable is passed to a function

by reference, rather than by value


Reference parameters are not pointers
A

reference parameter is given the


address of the argument passed to
the function
And thereafter behaves like a normal

variable

Overloaded Operators

C++ tends to re-use operators


The meaning can be determined by the context

The * operator
multiplication: 12 * 3;
declaration of a pointer: int* p;
dereference: *p = 23;

The & operator

address of: p = &x;


pass by reference: void swap(int & x, int &

y)

Pointers and
Assignment
int
int
int
int

x =
y =
*p1
*p2

12;
77;
= &x; //assign p1 the address of x
= &y; //assign p2 the address of y

p1
p2

1
2
7
7

x
y

Pointers and
Assignment
int x = 12;
int y = 77;
int *p1 = &x; //assign p1 the address of x
int *p2 = &y; //assign p2 the address of y
p1 = p2; //assigns the address in p2 to p1
p1
p2

1
2
7
7

x
y

Pointers and
Assignment
int
int
int
int
*p1

x = 12;
y = 77;
*p1 = &x; //assign p1 the address of x
*p2 = &y; //assign p2 the address of y
= *p2;
p1
p2

7
1
2
7
7
7

x
y

Why Use Pointers?


In

practice we don't often use


pointers like the preceding examples
There is little point in making pointers to

individual integers stored on the stack


Pointers

are key to managing


memory for objects that change size
during run-time
Such objects are allocated space in

another area of main memory in


dynamic memory

Dynamic Memory

Stack and Dynamic


Memory
Stack

memory is allocated at run

time
But it has a duration and size that can

be easily and correctly predicted


Some

data requires memory of


unknown size or duration
That is, as programmers we don't know

how much memory the program will


require
Such data should be stored in dynamic

Dynamic Memory
Dynamic

memory allows memory


usage to be determined at run-time
Allows users to decide the size of
data structures like vectors and
dynamic arrays
Allows space to be reserved for large
objects only as it is needed

Uses of Dynamic
Memory
There

are many examples of data


that is stored dynamically
Vectors and dynamic arrays
Reference structures such as linked lists
Variable size character strings
Object variables
Although these do not have to be stored
dynamically

Why Not Use the Stack


Data

that is stored dynamically may

Change size, and may


Persist beyond the duration of a function

It

cannot therefore be stored on the


stack
Because stack memory is allocated

sequentially
And all memory associated with a
function is released when the function
ends

Pointers
Data

that is allocated dynamically is


accessed through pointers
When a new dynamic variable is created

a pointer is assigned its address


Pointers

point to data in dynamic

memory
Pointers themselves are usually

automatic variables and therefore reside


on the stack
One

common use of pointers is in

Dynamic Arrays
And Back To Arrays

Array Size Revisited


As

noted previously array size must


be given a constant value
And the size of an array cannot be

changed while the program is running


(during run-time)
Which supports the organization of stack
memory
It

is possible to allocate memory at


run-time
From a free store of memory, called

Dynamic Memory
Think

of memory being used by a


program as falling into two
categories*
memory, the

Stack
amount
be
to
allocated is easily
managed

Dynamic
memory,
reserved when needed at
run-time, but needs more
administration to use

* In reality its a bit more

Dynamic Arrays
int arrSize = 0;
cout << "How big an array? ";
cin >> arrSize;
int* arr = new int[arrSize];

The variable is a
pointer, used to
store an address

Creates this
array in dynamic
memory
Data created in dynamic
memory persists until the
application is terminated,
regardless
of
which
function it was created in

Using Dynamic Arrays


Once

a dynamic array has been


created it can be used like any other
array
Individual array elements are accessed

using an index
A

dynamic array can be passed to a


function that takes an array
parameter
The only difference is that a dynamic

array's address can be changed

Deleting Arrays
Dynamic

memory continues to be
allocated even outside the scope in
which it was created
Whereas memory for a function's

parameters and variables is released


when the function ends
Dynamic

memory remains allocated

until
The application terminates, or
It is explicitly released

delete
Every

dynamic array created by a


program must also be deleted
This also applies any dynamic memory

as we will see later


Use delete[] to delete a dynamic array

simple rule is that

Any variable that is created using the

keyword new must be deleted by the


programmer

Dynamic Arrays
int arrSize = 0;
cout << "How big an array? ";
cin >> arrSize;
int* arr = new int[arrSize];
// arr is used in the program
delete[] arr;

Deallocates the
dynamic
memory

Indicates that an array is to be


deleted
Only memory allocated with
new needs to be deleted

Returning Arrays
An

array can be returned from a


function

But
it must be returned
as a pointer
int*
oddArray(int
sz){
int* result = new int[sz];
for (int i = 0; i < sz; i++){
result[i] = i * 2 + 1;
}
Use the function something
return result; like this:
}
int* arr = oddArray(10);
arr should be deleted at some
point

Returning Array Size


In

the previous example an array


was created in a function and then
returned
The size of the array was known outside

the function, and passed to the function


If

a function creates an array of


unknown size, the size must also be
returned
This may require the use of a reference

(&) parameter to access the size

Pointers and Objects

Use Pointers ...


To

refer to the same data in multiple


ways
For example, return a pointer to an

element in a data structure


To

create data in dynamic memory

To create dynamic data (strings, vectors,

...)
To only reserve space for large objects
when necessary
To create reference structures

Memory
Memory

used by a program can be


broken down into two main
components
Stack memory, used to store
variables in the main and other
functions
Stores any variable not created with new

Dynamic

memory

Often referred to as the free store or the

heap

New
Pointers

can be used to create


variables with no identifiers
These variables are accessed using

pointers
Such

variables are dynamically


allocated
They are created and destroyed as the

program is running
This allows, for example, vectors to be
created with the size determined at run-

The NULL Pointer


Pointers

store memory addresses


Initially a pointer may not refer to a
memory address
So does not have a meaningful value

The

constant, NULL is used to


indicate that a pointer's address has
not been assigned
NULL actually equals 0
As memory addresses are in fact whole

numbers

Pointers and Classes


Pointers

are frequently used with

classes
Some objects can be very large
Objects may contain bitmaps, or sound

files
Without dynamic memory space has to
be reserved for objects that may not be
used
Pointers

must be dereferenced to
access an object's methods

New Example
int *p1 = new int; //pointer to an int
// Make a new Student, default constructor
int *p2 = new Student(); ... assumes the
existence of a
// Make a new Student
Student class ...
int *p3 = new Student(94101);
*p1 = 119;
int id = p2.getID();

Error! p2 is not a
Student it is an address
...

Pointers and Objects


A

pointer must be dereferenced to


access the methods or attributes of
an object
That the pointer points to

This

has to be done carefully, for


example:
p2.displayStudent(); //error!
*p2.displayStudent (); //error!
(*p2).displayStudent (); //works

It

is much easier to use the ->

Referring to Objects
The

-> operator can be used to refer


to an object's methods using a
pointer
Student* p = new Student(94101);
p->addGrade(3, 3.33);

The

-> operator is only used with


pointers

It should not be used with regular object

variables
Use dot notation for such variables

Deletion
Stack

(non dynamic) memory is


released automatically
When a function finishes execution all of

its memory is released


Dynamic

memory is only released


automatically when an application
ends
The free store is finite
Dynamically allocated memory should

be explicitly released when not required

Deleting Memory
The

keyword delete is used to free


dynamically allocated memory
When deleting a dynamic array
delete must be followed by []s
Memory space that has been
deallocated cannot be freed again
Attempting to do this results in an error

delete Operator
The

delete operator is used to free


memory that has been dynamically
allocated
int * p = new int;
*p = 23;
...
delete p;

Each

new should have a matching


delete
Although often not in the same function

New and Delete


int* arr = NULL;
indicates that arr
int sz = 0;
does not point to
cout << Please enter size; anything
allocates space for an int
cin >> sz;
array of size sz, and
arr = new int[sz];
returns its address which is
int* p_int = new int(12);
then assigned to arr
delete p_int;
frees up 4 bytes
delete[] arr;
arr = NULL;
frees up the 40 bytes
delete[] arr;
allocated for the dynamic
delete p_int;
array
safe since arr is NULL, but
results in an
pointless

Building a Simple
Vector Class
Bringing It All Toegether

Building a Vector Class


We

will write a class that uses


pointers and dynamic arrays to
behave much like a vector
To keep things simple we will just
Implement a vector of doubles rather

than a template for any type


Implement push_back and size methods
Start by allowing access to the vector
elements using get(index) and set(index)
The

example will be developed in

Anda mungkin juga menyukai