25)
Dynamic memory allocation
Program 23 | Dynamic memory allocation | Source code - prog23.c |
So far in all the programs on
"static" memory allocation has been used, which
means that for example, if an array is used inside a
program, then it is a "design time" that the
size of the array ( number of elements ) is decided. Eg int
age[10]; Which allocates enough memory to hold 10
integers, if we wanted it to hold more then the source
code would need to be changed, altering the [10] to
however many elements are required. It's not practical in
a lot of cases to keep changing source code and
recompiling. It is especially not practical if you are
writing a game that the exe file is being distributed to
lots of people ! If arrays were the only option, then the only way around this would be to declare arrays with a lot more elements than are required, just to be safe. This is a waste of memory as there will always be a surplus that aren't being used. Therefore, there is another technique that can be used to allocate memory at "run time", this is called "dynamic memory allocation" and can be achieved in 'C' using the malloc function ( memory allocation ). If you're learning C++ and using these pages it is very similar to new in C++. Before the code though, more theory is needed again on how arrays are stored in memory to show what the limitations are. |
Arrays in memory | ||||||||||||||||||||||||||||||||||||||||
As an example,
assume a simple school system where student percentage
grades are stored in memory for an exam for all students.
The school is located on an a remote island and has only
got space for a maximum of 10 students. The following
code snippet could be used to allow a user to enter the
grades for all the students in the school.
It's not important how the grades are entered, they could be retrieved from a file or input from the keyboard like above. One day, little Jimmy from class 1A ( the only class ), is out playing when he trips and falls into a patch of black residue. He returns home to tell his parents and they discover he's struck oil ! The parents report it, and a few weeks later the big oil tycoons are there setting up there rigs and refineries, employing a lot more people than are on the island ( some of which happen to have children and need an education ). They agree that they will upgrade the school on the island free of charge so that it has the capability to house a 100 students. The last thing they're going to think about or want with all the chaos going on is there software which can't cope with more than 10 students. It's only at the last minute after everything is up and running that the problem is discovered, and they're onto the programmer like a flash (HELP!!) - sounds familiar :) They need things changing now and the last thing the programmer wants is to start messing about with code that could be years old and (s)he's forgotten all about, that's probably the reason so many charge shed loads for tech support. The programmer reluctantly digs out the code and makes it so that even if gold is found on the island, it will cope, hopefully :) as below - changes shown in red:
In the meantime the programmer has learned about the break statement as well, which can be used to break out of for and while loops. With this new system and 100 students in the school, it works fine, it's not the most efficient use of memory though, as there are 900 bytes which are allocated but not being used, but at least it works. When arrays are allocated they occupy a continuous area of memory, the compiler only keeps a note of the address of the first element in the array, and the data type of the array ( so that it can calculate the addresses of subsequent elements ). Eg if the line short v[10]; is declared in a program, the program will look for a free continuous block of memory that is 20 bytes long ( the short data type requires 2 bytes of storage for each variable ). Assuming it finds that space starting at memory address 1000, the following can be visualised :
Remember from the last page that each memory address can hold a single byte, which is capable of storing 0 - 255, if a data type that requires more than one byte of storage is used the arrangement is the same as the above for an array. The program though only needs to remember the address of the first element and the size of the data type, from that information it can retrieve any element. Here the base address will be 1000 and the size of each element 2 bytes ( short data type ). The address of any element (i) can be found by the compiler with the following formula addr = base + ( i x data type size ). For example the fourth element ( v[3] ), can be found starting at memory address, 1000 + ( 3 x 2 ) = 1006. Knowing this should highlight another potential problem with arrays - memory fragmentation. It is possible that there is enough free available memory to allocate x amount of bytes, but the memory has become fragmented in a way, in which there is not a continuous block of x bytes available. It would be rare, and was more a problem in the olden days with computers having smaller available memory though. This problem can be avoided by using "linked lists" and various other dynamic data structures, these will be covered later. For now the program below shows a way, using dynamic allocation to allocate just enough memory at "run time", that is required. |
Program code - new parts shown in red |
|
Description of the program code |
The first thing
this program does is ask the user to enter the number of
students there are in the school. This is now needed as
the program will go on to allocate just enough memory to
store those students, so it needs to know in advance just
how much that will be. The actual allocation of memory is
is done on the grades=(unsigned
char*)malloc(numstudents*sizeof(unsigned char)); line. Previously a
pointer variable called grade, has been declared, which
will point to list in memory of bytes (unsigned char's). The malloc function allocates a block of memory, the amount of which is specified in the parameter that's passed to the function. The amount of memory required can be calculated easily by multiplying the number of students the user entered by the size of the data type used to store the grades. Even though, we know that each unsigned char takes up 1 byte of memory, for completeness the sizeof function has been used just to make sure. The (unsigned char *) before the malloc function is called a cast. It isn't needed in Miracle C in this example, but may be required with other compilers. All it does is to convert whatever pointer the malloc function returns to an unsigned char pointer that can be stored in the grades variable. There will be more on casting probably in later pages. In short, this very long line is saying, allocate a continous block of memory of (x bytes) and return a pointer to the address of the start of that block which will be stored in the grades variable. So if the user enters 56 for the number of students, it will allocate a continuous block of memory 56 bytes long and return a pointer to the first address in that block, which is stored in the grades variable. After a malloc function call to allocate memory a program should always check to see that the memory has been allocated. This isn't difficult to do though, as if the call to the malloc functions fails, it returns NULL. There are a few reasons why it may fail, for example, not enough memory, or even not a large enough free available block of memory. Nowadays, this isn't likely though with memory sizes as they are, but it's best to make a check to be absolutely certain. Assuming the block of memory has been allocated okay ( in other words there is a pointer to an address in the grades variable, and not NULL ), then the program can start and fill that memory block up with data. Firstly this program makes a copy of the grades pointer variable in another variable called ptr. This is because later the ptr variable will be updated to scan through the list, as will be seen. We don't want to update the grades variable, as this references the start of the block and is needed later. A for loop is used to loop through all the students, and the data is entered with the scanf function by the user. Notice the difference with the scanf line here from previous examples ? Look at the scanf function at the top of the program, where the number of students is entered, and this one has an ampersand & in front of the variable, whereas the one in the for loop hasn't. The reason is because the variable ptr already contains an address. The scanf function requires that a memory address of where the user entered information will be stored, as a parameter. On the previous page the inclusion of an ampersand in front of a variable name was said to be read as "address of". This is the bit about pointers that can get really confusing. When you reference a variable declared as a pointer ( ie with an astrix before the name ), then when you reference that variable on its own, the value of the variable is assumed. The scanf function doesn't need a value, it needs a memory address to store the input, so the ampersand is used to find the memory address where the value of the variable is stored. The ptr variable above, though, already contains a memory address, initially it is the address of the first byte at the start of the allocated block. So when the user enters a grade this is the address that the grade will be stored in. The line ptr++; simply add's one to this address, moving through each memory address in the list to store subsquent values. This is why the grades variable couldn't be used, because it is important that we can find the start of the block again later, and if the program had changed the value of the grades variable like this, then it would end up with a variable that points to the end of the list, not the start. After all the grades have been entered, the program goes on to display the entered grades for each student in another for loop. The important part is near the end of the printf line in the loop, *(grades+i) . The use of the asterix (*) in front of a pointer variable means that you're after the contents stored at the address it is pointing to. In this example, it will return the contents at grades + i. The address stored in the grades variable isn't changed still by doing this, as it is not in the form grades = . The final and really important new thing about this program is the free(grades); line. The free statement will free up any memory allocated with the malloc function. It takes a pointer to the start of the memory block as the parameter, which is another good reason for not altering the address stored in the grades pointer. If you do not free up dynamically allocated memory at the end of a program in this way, then the memory will remain allocated even though the program has finished ! In programming lingo this is called a memory leak, and is not desired. You want the program to leave the same amount of memory free as there was when it started. If you are using these pages with C++ in mind then the delete member is the same as free in 'C'. |
Summary |
Well believe it or
not that is C programming ! yes, honestly, that is about
everything covered in C that is needed to be known. There
is, of coarse, more, but I am keen to get on to the
stuff, everyone wants to know about, DirectX and
graphics. 'This page though is definately the hardest to get your head around, pointers can be really confusing when learning C, but the best advice of all is not to shy away from using them. When using pointers though you have to be really careful, as accessing areas of memory which your program hasn't allocated can lead to invalid page faults and general protection faults in Windows. So when you get invalid page faults and GPF's in windows you now know that a program has more than likely accessed an area of memory that it shouldn't have, and it's a bug in a program. Accessing an invalid area of memory can be either by writing a value to that area of memory or even reading a value from it ! The next part goes on to look at the final program that will be created with Miracle C, "telling the time". The rest of the programs will require a more advanced compiler. |
Tasks |
No tasks here. |
(c) J.C.Spooner 2001