|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Home : Math & Science : Computer Science Study Guides : Pointers : Why Use Pointers? : Dynamic Memory Allocation
Dynamic Memory Allocation
Your computer science professor has just asked you to write a
program for him (he would do it himself but he's too busy
grading your assignments). The program is supposed to read in
all of his students' grades and then print them back out in
sorted order. Simple, right? You grab your trusty bubble
sort algorithm, write up a function to sort an array of data,
then write a simple little program to read in all of the
numbers, sort them, and print them back out, maybe something
like:
int main()
{
int grades[100], i=0;
do {
printf("Enter grade #%d:\n", i+1);
scanf("%d\n", &grades[i]);
i++;
} while(&grade[i] != -1); /* the last grade is a -1 */
}
Easy, right? You're proud of your program and you head to the
professor, code in hand and smile on face. The only problem
is, when you get to the professor, he looks at your code and he
doesn't have a smile on his face. Why?
There could be many reasons why your professor isn't happy with
the code above. For example, there's not much in the way of
error checking. More importantly though, he's probably a
little wary of that 100 you have there in the code. You
realize that, of course, he's got over 100 students in his
class, so we'll just change this number to 500, allow him to
have up to 500 students. You go home that night, again feeling
very proud of yourself. The next year, though, you get a call
from that professor again, and he's upset. Seems this year he
had an influx of students and your program wasn't robust enough
to handle all of them; you hadn't set aside enough memory and
as such your program was of no more use to him. You think to
yourself, "Back to the drawing board; there must be an easier
way so that I don't have to keep rewriting this program every
time the professor's class size changes." You're in luck,
there is an easier way. Or at least a better one.
Static Memory
Up to this point, the memory we've been using has been
static memory. What does this mean? Static memory is memory that
is set aside automatically by the
compiler for your
program. When you declare a variable, such as the int
arr[100] array we declared in the above program, you're
telling the computer to set aside space for 100 integers. The
computer of course obliges. The problem with this is that the
computer needs to know how much memory to set aside before your
program starts running. When you run your program, the
computer gives it the memory it needs to hold all of the
variables you've declared; in other words, you've statically
allocated memory.
But this method fails in the above case with the professor.
What we'd like to be able to do is to create an array whose
size is specified at run time. This time, the computer doesn't
oblige; it fact, neither does the compiler. If you try to
write code that looks like:
int steve;
scanf("%d\n", &steve);
int arr[steve];
the compiler will refuse to build and executable. The reason
is that at compile time the compiler has absolutely no idea how
big an array arr will need to be. The user could enter any
value he wanted for steve, which means the arr could be
any size at all. Since the compiler needs to know how much
space to tell the computer to set aside, this code won't work.
So, how do we get around this? The answer is dynamic
memory allocation, and for that, we need pointers.
Dynamic Memory Allocation
Dynamic memory allocation is a process that allows us to do
exactly what we're looking to do above, to allocate memory
while our program is running, as opposed to telling the
computer exactly how much we'll need (and for what) ahead of
time.
With dynamic memory allocation, while the program is running,
the program requests more memory from the computer. If there's
enough memory available, the computer will grant the program
the right to use the amount it requests.
Dynamic Memory and Pointers
When we ask the computer for memory dynamically, what do you
think it gives us? That's right, an address. When we ask the
computer for memory, it goes and sees what memory it has
available. Assuming it has enough to give us, the operating
system will set aside the amount of memory we requested and
give us that memory's address so that we can then use it. How
do we store an address? In a pointer.
The functions we'll use to grab dynamic memory return a pointer
to that memory (or if for some reason we couldn't get the
memory we requested, they'll return the NULL value). We can
then use that memory through the pointer just like it was ours
to begin with, setting values in the memory, getting values
from the memory, etc.
As this tutorial is primarily on pointers, we won't delve too
much into memory allocation here as the point of this section
is simply to point out that you can do dynamic allocation.
However, we will still show you a few basics.
Allocating memory
Allocating memory in C primarily revolves around two functions:
malloc() and free(). malloc() is used to allocate
memory (to request it) and free() is used to give it back.
In C++, the operators new and delete are used to
accomplish similar tasks, however due to the additional
complexity surrounding these operators, we will not discuss
them here (refer to the C++ SparkNote for
more
information).
malloc()
So how exactly do we ask the system for memory? With the function
malloc(). There are other functions as well, all part of the
malloc() family, but we'll only be discussing malloc() here as it
is the most common. malloc() takes a single argument, the number of
bytes to allocate, and returns a pointer to the allocated memory if the
allocation was successful, or NULL otherwise. For example, to allocate a
chunk of memory 1024 bytes in length (1KB), we would use the instruction
malloc(1024).
![]()
Figure 3.1: ptr = malloc(1024);
Normally, however, we wouldn't allocate some random number of
bytes; we would want to allocate enough space to hold some
specific data, some number of variables. As such, a commonly
used operator is the sizeof() operator. sizeof() takes
a type as an argument and gives back the size, in bytes, of
that type. It is often used in conjunction with malloc to
allocate enough space to hold a single variable or array of
variables. The sizeof() operator is particularly useful
for programs that must run under more than one operating system
as the length in bytes of a data type may not be the same on
different systems.
Some examples:
So how can we use this in real code? Here's an example.
Remember our unhappy professor? We can easily change our array
grades program such that the size of the array can be set at
run-time (meaning while actually running the program as opposed
to at compile time).
int main()
{
int i=0;
int *grades;
int size;
printf("Enter the number of students:\n");
scanf("%d\n", &size);
grades = malloc(size * sizeof(int));
do {
printf("Enter grade #%d:\n", i+1);
scanf("%d\n", &grades[i]);
i++;
} while(i<size);
}
So what does this do? It reads in the size of the array to
create into the variable size. It then uses malloc to
allocate enough memory to hold that many integers. As the
memory it allocates will be continuous, we can use this memory
just like an array. We store the address of that memory into
grades and the rest program is basically as it was above.
There are still a few key elements missing here. The first, a
very important part of programming, is error detection.
Remember that if we try to dereference a NULL pointer, it will
very often cause something bad to happen, like making our
program crash. If for some reason malloc() cannot allocate
memory, it will return NULL. So there exists the
possibility that if malloc() cannot allocate the requested
memory, the value of NULL will be stored in grades, and
then when we try to access the ith element of grades, we
will have a problem. To prevent problems like these, we need
to check to see if the result of the call to malloc() returns
NULL. If it does, there was an error, and we need to handle
it. How you handle it depends on how you're using the memory,
but in this case we'll just display an error and exit the
program.
int main()
{
int i=0;
int *grades;
int size;
printf("Enter the number of students:\n");
scanf("%d\n", &size);
if ((grades = malloc(size * sizeof(int)) == NULL) {
printf("Error: Unable to allocate memory for array\n");
exit(1);
}
do {
printf("Enter grade #%d:\n", i+1);
scanf("%d\n", &grade[i]);
i++
} while(i<size);
}
The second key element missing here deals with giving back this
memory we've allocated when we're done using it.
free()
Thus far we've only discussed allocating memory. When your
program requests memory and the operating system gives it, the
operating system marks that memory as "in use" and will not
allow any other application to use it (in fact, if another
application does attempt to use it, the operating system will
most likely try to kill that program; remember what happens
when we try to dereference a pointer that doesn't point to
memory we own). If your program never frees up the memory it
requested once it is done using it, no one else will be able to
use it. So, when we're done using memory we've requested, we
need to give it back so other programs can use it. It's that
easy.
To free memory in C we use the function free(). The
free() function takes one argument, a pointer to the memory
we want to free. This memory must have been previously
allocated with free()'s counterpart, malloc(). For
example, if we have an integer pointer int *steve and if
steve points to some memory your program previously
requested, to free it, all we have to do is make the call
free(steve). Easy enough. There are, however, a few
things to be careful of when using free():
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contact Us | Privacy Policy | Terms and Conditions | About
©2006 SparkNotes LLC, All Rights Reserved.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||