Variables and Memory PDF
Variables and Memory PDF
Allocating memory
There are two ways that memory gets allocated for data storage:
o For standard array declarations, this is why the size has to be constant
o dynamically allocated space usually placed in a program segment known as the heap or the free
store
o Exact amount of space or number of items does not have to be known by the compiler in
advance.
2. Storing its address in a pointer (so that the space can be accesed)
De-allocation:
1. Deallocation is the "clean-up" of space being used for variables or other data storage
2. Compile time variables are automatically deallocated based on their known extent (this is the
same as scope for "automatic" variables)
3. It is the programmer's job to deallocate dynamically created space
To allocate space dynamically, use the unary operator new, followed by the type being allocated.
If creating an array dynamically, use the same form, but put brackets with a size after the type:
These statements above are not very useful by themselves, because the allocated spaces have no names!
BUT, the new operator returns the starting address of the allocated space, and this address can be stored
in a pointer:
Notice that this is one more way of initializing a pointer to a valid target (and the most important one).
So once the space has been dynamically allocated, how do we use it?
For single items, we go through the pointer. Dereference the pointer to reach the dynamically created
target:
For dynamically created arrays, you can use either pointer-offset notation, or treat the pointer as the
array name and use the standard bracket notation:
To deallocate memory that was created with new, we use the unary operator delete. The one operand
should be a pointer that stores the address of the space to be deallocated:
Note that the pointer ptr still exists in this example. That's a named variable subject to scope and extent
determined at compile time. It can be reused:
delete [] name_of_pointer;
Example:
After deallocating space, it's always a good idea to reset the pointer to null unless you are pointing it at
another valid target right away.
To consider: So what happens if you fail to deallocate dynamic memory when you are finished with it?
(i.e. why is deallocation important?)
I want to resize this so that the array called list has space for 5 more numbers (presumably because the old one
is full).
There are four main steps.
1. Create an entirely new array of the appropriate type and of the new size. (You'll need another pointer for
this).
3. Copy the data from the old array into the new array (keeping them in the same positions). This is easy
with a for-loop.
6. Delete the old array -- you don't need it anymore! (Do as your Mom says, and take out the garbage!)
7. Change the pointer. You still want the array to be called "list" (its original name), so change the list
pointer to the new address.
8. list = temp;
That's it! The list array is now 5 larger than the previous one, and it has the same data in it that the original one
had. But, now it has room for 5 more items.
The memory for the locals continues to be allocated so long as the thread of control is within the
owning function. Locals continue to exist even if the function temporarily passes off the thread of
control by calling another function. The locals exist undisturbed through all of this.
Finally, when the function finishes and exits, its locals are deallocated. This makes sense in a way
(suppose the locals were somehow to continue to exist) how could the code even refer to them?
The names like number or result only make sense within the body of Square() anyway. Once the
flow of control leaves that body, there is no way to refer to the locals even if they were allocated.
That locals are available (scoped) only within their owning function.
Local Copies
Local parameters are basically local copies of the information from the caller. This is also known
as pass by value.
Parameters are local variables which are initialized with an assignment (=) operation from the
caller. The caller is not sharing the parameter value with the callee. In other words, the callee is
getting its own copy.
This has the advantage that the callee can change its local copy without affecting the caller. This
independence is good since it keeps the operation of the caller and callee functions separate
which follows the rules of good software engineering (keep separate components as independent
as possible).
However, since locals are copies of the caller parameters, they do not provide a means of
communication from the callee back to the caller. This is the downside of the independence
advantage. Also, sometimes making copies of a value is very expensive.
int main()
{
int* ptr = local_pointer();
f();
return 0;
}
Note that this bug happens when an & passes a pointer to local storage from the called back to
its caller. When the called exits, its local memory is deallocated and so the pointer no longer has
a pointee.
However, if we use & to pass a pointer from the caller to the called, it's fine. The pointer remains
valid for the callee to use because the caller locals continue to exist while the called is running.
The object that the pointer is pointing to will remain valid due to the simple constraint that the
caller can only exit sometime after its called function exits. Using & to pass a pointer to local
storage from the caller to the called is fine. The reverse case, from the callee to the caller, is
when the bug occurs as shown in the example above.
Automatic Memory Management
Before we go into manual memory management, it might be better look at automatic memory
management.
Automatic memory management is closely related to local variables. A local variable occupies
memory that the system allocates when it sees the variable's definition during execution. The
system also deallocates that memory automatically at the end of the block that contains the
definition.
Programmers sometimes make a mistake of returning invalid pointer as we see in the example
below. A pointer becomes invalid once the corresponding variable has been deallocated.
int * badPointer() {
int i = 100;
return &i;
}
The function badPointer() returns the address of the local variable i. However, when the function
returns, actually ends the execution of the block and deallocates i. So, the pointer that &i; is now
no longer valid. Actually, the content of the variable i is correct at the moment the function
returns. The problem is that the memory for i is allocated in the stack frame for badPointer().
When badPointer() returns, all the memory in its stack frame is deallocated and made available
for use by other functions. Still, the function tries to return it anyway. What's going to happen?
Only the compiler knows.
If we insist on returning the &i;, we can use static:
int * pointerToStatic() {
static i;
return &i;
}
This says that i is static and thus we allocate it once and we do not want to deallocate it as long
as the code is running.
int main() {
char *s;
populate(&s;);
printf("%s", s); // should print "Memory"
free(s);
return 0;
}
This can also happen when we use delete instead of delete[], or when we do memory
management with wrong combination of memory functions: malloc() with delete or new with
free().
delete pString;
delete [] pStringArray;
In the case of array creation, the new operator behaves slightly differently from the case of
single-object creation. Memory is no longer allocated by operator new. Instead, it's allocated by
operator new[].
Let's look at the process of creating and deleting array objects.
For arrays, a constructor must be called for each object in the array.
string *pStringArray = new string[10];
The code calls operator new[] to allocate memory for 10 string object, then call the default string
constructor for each array element.
In the way, when the delete operator is used on an array, it calls a destructor for each array
element and then calls operator delete[] to deallocate the memory. It calls the string destructor
for each array element, then calls operator delete[] to deallocate the array's memory.
delete [] pstrStringArray;
We have two forms of delete:
delete ptr - frees the memory for an individual object allocated by new.
delete ptr[] - frees the memory for an array of objects allocated by new.
The following example has shows usage of delete and delete[]. When we want delete the pointer
to MyClass, we used delete, and in the destructor which is triggered by the delete myObj does
delete array created on the heap.
#include <iostream>
class MyClass
{
public:
MyClass()
{
std::cout << "default constructor" << std::endl;
}
MyClass(int s):myArray(new double[s])
{
std::cout << "constructor" << std::endl;
for(int i = 0; i < s; ++i) myArray[i] = 0;
}
~MyClass()
{
// this will be called @"delete myObj"
std::cout << "destructor" << std::endl;
delete[] myArray;
}
private:
double *myArray;
};
// Use myClassPtrArray.
// Delete each allocated object.
for (int i = 0; i < 4; i++)
delete myClassPtrArray[i];
struct Student {
const char *name;
int id;
};
int main()
{
struct Student s = {"Park", 12345};
/* allocate */
struct Student* ss = (struct Student*)malloc(sizeof(Student));
/* initialize */
ss->name = "Hong";
ss->id = 67890;
return 0;
}
Note that we can't write in either C or C++.
*ss = {"Lee", 43145};
But in C++, after we define a constructor, we can write:
Student *ss = new Student("Lee", 43145);
The C compiler lays out memory corresponding to functions(arguments, variables) on the stack.
C allows the programmer to allocate additional memory on the heap.
Stack Heap
allocation
malloc is a C standard library function that finds a chunk of free memory of the desired size and
returns a pointer to it.
int *p = malloc(sizeof(int));
deallocation
free marks the memory associated with a specific address as no longer in use. (It keeps track of
how much memory was associated with that address)
free(p);
Memory can be allocated dynamically with the malloc() function, and it can be released using
free() when it's no longer needed.
The malloc() function requires an integer value for its argument to specify the bytes of memory,
and it returns a pointer (void *). to the allocated space. The free() takes a pointer to the space for
its argument. Here is a simple example:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int max = 10;
char *buffer;
buffer = (char*)malloc( (max+1)* sizeof(char));
if(buffer != NULL) {
for(int i = 0; i < max; i++)
buffer[i] = 'a'+ i;
buffer[max] = '\0';
free (buffer);
return 0;
}
else {
printf("Not enough memory\n");
return 1;
}
}
If the malloc() cannot get the space requested, it returns a NULL. In the example, sizeof() is used
to calculate the total amount of bytes to reserve because the number of bytes used to store vary
from system to system.
There is another function which is very similar to the malloc(). It is calloc().
The calloc() takes two integer arguments. These are multiplied together to specify how much
memory to allocate.
The calloc() initializes all the allocated memory space to zero whereas malloc() leaves whatever
values may already be there.
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *ptrc = (int*)calloc(10, sizeof(int));
int *ptrm = (int*)malloc(10);
for (int i = 0; i < 10; i++)
printf("%d calloc: %d malloc: %d\n",i,*ptrc++, *ptrm++);
return 0;
}
Output is;
0 calloc: 0 malloc: -842150451
1 calloc: 0 malloc: -842150451
2 calloc: 0 malloc: -33698355
3 calloc: 0 malloc: 65021
4 calloc: 0 malloc: 1241163875
5 calloc: 0 malloc: 201340432
6 calloc: 0 malloc: 1913376
7 calloc: 0 malloc: 0
8 calloc: 0 malloc: 267805232
9 calloc: 0 malloc: 91
Memory that has been allocated with malloc() or calloc() can be increased by using realloc().
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *ptrc = (int*)calloc(10, sizeof(int));
int *ptrm = (int*)malloc(10);
for (int i = 0; i < 10; i++)
printf("%d calloc: %d malloc: %d\n",i,*(ptrc+i), *(ptrm+i));
void free(void*)
void free(void* heapBlockPointer).
The free() function takes a pointer to a heap block and returns it to the free pool for later reuse.
The pointer passed to free() must be exactly the pointer returned earlier by malloc(), not just a
pointer to somewhere in the block.
Calling free() with the wrong sort of pointer is famous for the particularly ugly sort of crashing
which it causes. The call to free() does not need to give the size of the heap block - the heap
manager will have noted the size in its private data structures. The call to free() just needs to
identify which block to deallocate by its pointer. If a program correctly deallocates all of the
memory it allocates, then every call to malloc() will later be matched by exactly one call to free().
class A
{
char c[100];
int n;
};
char buf1[200];
char buf2[400];
int main()
{
A *pA1, *pA2;
int *pI1, *pI2;
pA1 = new A(); // placing a class in heap
pI1 = new int[10]; // placing an int array in heap
delete pA1;
delete[] pI1;
return 0;
}
The placement new simply uses the address that is passed to it. It doesn't keep track of whether
that location has already been used, and it doesn't search the block for unused memory. This
shifts the burden of memory management to the programmer.
For regular new, the statements
delete pA1;
delete[] pI1;
free up the block of memory. However, as we saw in the example, we did not use delete to free
the memory used by placement new. Actually, it couldn't. The memory specified by buf is static
memory, and delete can be used only for a pointer to heap memory allocated by normal new.
To see a problem of memory management of the previous example, here, a little bit modified
version with a constructor using new to make a pointer to a char array and with a destructor
which frees the memory occupied by the character array:
#include
#include
using namespace std;
class A
{
char c[100];
int n;
char *str;
public:
A()
{
str = new char[10];
}
~A()
{
cout << "~A" << endl;
delete[] str;
}
};
char buf1[200];
char buf2[400];
int main()
{
A *pA1, *pA2;
int *pI1, *pI2;
pA1 = new A(); // placing a class in heap
pI1 = new int[10]; // placing an int array in heap
delete pA1;
delete[] pI1;
return 0;
}
Output is:
~A
Note that the destructor is called at:
delete pA1;
But we need to call another destructor for the object created by:
pA2 = new (buf1) A;
How can we do that?
Here is the solution: add the following line at the end of the main().
pA2->~A();
Then, the output becomes:
~A
~A
We call the destructor explicitly for any object created by placement new. Normally, destructors
are called automatically, and this is one of the rare cases that require an explicit call. An explicit
call to a destructor requires identifying the object to be destroyed.
class MyClass
{
public:
static ptrInstance createInstance();
~MyClass(){std::cout << "Dtor()" << std::endl;};
private:
MyClass() {std::cout << "Ctor()" << std::endl;};
};
ptrInstance MyClass::createInstance()
{
return ptrInstance(new MyClass());
}
void makeMyClass()
{
ptrInstance ptr = MyClass::createInstance();
ptr = MyClass::createInstance();
}
int main()
{
makeMyClass();
return 0;
}
In the example, two instances of MyClass are created, and both of these instances are destroyed
when the ptr goes out of scope. If instead the createInstance() method simply return a MyClass *
type, then the destructor would never get called in the example. The use of smart pointers can
therefore make memory management simpler.
In general, if we have a function that returns a pointer that our clients should delete or if we
expect the client to need the pointer for longer than the life of our object, then we should return
it using a samrt pointer. However, if ownership of the pointer will be retained by our object, then
we can return a standard pointer as below:
static MyClass* createInstance();
instead of
static boost::shared_ptr createInstance();
This means that the same virtual memory address used by two different processes, will be
mapped into two different physical memory addresses. In other words, one process cannot
access the contents of the memory used by the other process, and thus one process corrupting
its memory won't interfere with the contents of the memory of any other process in the system.
This feature is known as memory protection