Unit 12 Dynamic Memory Allocation and Linked List
Unit 12 Dynamic Memory Allocation and Linked List
12.1 Introduction
In the previous unit, you studied about the preprocessor in C. You studied
that the preprocessor is a program that processes the source code before it
passes through the compiler and hence it can be used to make C an easy
and efficient language. In this unit, you will study about the dynamic memory
allocation techniques. You will also study about the linked list that is another
useful application using C.
You can use an array when you want to process data of the same data type
and you know the size of the data. Sometimes you may want to process the
data but you don't know what the size of the data is. An example of this is
when you are reading from a file or keyboard and you want to process the
values. In such a case, an array is not useful because you don't know what
the dimension of the array should be. C has the facility of dynamic memory
allocation. Using this, you can allocate the memory for your storage. The
allocation is done at runtime. When your work is over, you can deallocate
the memory. The allocation of memory is done using three functions: malloc,
calloc, and realloc. These functions return the pointers to void, so it can be
typecast to any data type, thus making the functions generic. These
functions take the input as the size of memory requirement.
Objectives:
After studying this unit, you should be able to:
explain about the dynamic memory allocation
implement dynamic memory allocation functions like -malloc, calloc and
realloc
explain the concept of linked lists
discuss the different operations on linked lists
The program instructions and global and static variables are stored in a
region known as permanent storage area and the local variables are stored
in another area called stack. The memory space that is located between
these two regions is available for dynamic allocation during execution of the
program. This free memory region is called heap. The size of the heap
keeps changing when program is executed due to creation and death of
variables that are local to functions and blocks. Therefore, it is possible to
encounter memory “overflow” during dynamic allocation process.
12.2.1 Allocating Memory with malloc
A problem with many simple programs, including in particular little teaching
programs such as we've been writing so far, is that they tend to use fixed-
size arrays which may or may not be big enough. We have an array of 100
ints for the numbers which the user enters and wishes to find the average of
them, what if the user enters 101 numbers? We have an array of 100 chars
which we pass to getline to receive the user's input, what if the user types a
line of 200 characters? If we're lucky, the relevant parts of the program
check how much of an array they've used, and print an error message or
otherwise gracefully abort before overflowing the array. If we're not so lucky,
a program may sail off the end of an array, overwriting other data and
behaving quite badly. In either case, the user doesn't get his job done. How
can we avoid the restrictions of fixed-size arrays?
The answers all involve the standard library function malloc. Very simply,
malloc returns a pointer to n bytes of memory which we can do anything we
want to with. If we didn't want to read a line of input into a fixed-size array,
we could use malloc, instead. It takes the following form:
ptr=(cast-type *)malloc(byte-size)
where ptr is a pointer of type cast-type. The malloc returns a pointer(of cast-
type) to an area of memory with size byte-size.
Here is an example
#include <stdlib.h>
char *line;
int linelen = 100;
line = (char *)malloc(linelen);
/* incomplete -- malloc's return value not checked */
getline(line, linelen);
The 100 bytes of memory (not all of which are shown) pointed to by line are
those allocated by malloc. (They are brand-new memory, conceptually a bit
different from the memory which the compiler arranges to have allocated
automatically for our conventional variables. The 100 boxes in the figure
don't have a name next to them, because they're not storage for a variable
we've declared.)
As a second example, we might have occasion to allocate a piece of
memory, and to copy a string into it with strcpy:
char *p = (char *)malloc(15);
/* incomplete -- malloc's return value not checked */
strcpy(p, "Hello, world!");
When copying strings, remember that all strings have a terminating \0
character. If you use strlen to count the characters in a string for you, that
count will not include the trailing \0, so you must add one before calling
malloc:
char *somestring, *copy;
...
copy = (char *)malloc(strlen(somestring) + 1); /* +1 for \0 */
/* incomplete -- malloc's return value not checked */
strcpy(copy, somestring);
What if we're not allocating characters, but integers? If we want to allocate
100 ints, how many bytes is that? If we know how big ints are on our
machine (i.e. depending on whether we're using a 16- or 32-bit machine) we
could try to compute it ourselves, but it's much safer and more portable to let
C compute it for us. C has a sizeof operator, which computes the size, in
bytes, of a variable or type. It's just what we need when calling malloc. To
allocate space for 100 ints, we could call
int *ip =(int *)malloc(100 * sizeof(int));
The use of the sizeof operator tends to look like a function call, but it's really
an operator, and it does its work at compile time.
Since we can use array indexing syntax on pointers, we can treat a pointer
variable after a call to malloc almost exactly as if it were an array. In
particular, after the above call to malloc initializes ip to point at storage for
100 ints, we can access ip[0], ip[1], ... up to ip[99]. This way, we can get the
effect of an array even if we don't know until run time how big the “array''
should be. (In a later section we'll see how we might deal with the case
where we're not even sure at the point we begin using it how big an “array''
will eventually have to be.)
Our examples so far have all had a significant omission: they have not
checked malloc's return value. Obviously, no real computer has an infinite
amount of memory available, so there is no guarantee that malloc will be
able to give us as much memory as we ask for. If we call
malloc(100000000), or if we call malloc(10) 10,000,000 times, we're
probably going to run out of memory.
When malloc is unable to allocate the requested memory, it returns a null
pointer. A null pointer, remember, points definitively nowhere. It's a “not a
pointer'' marker; it's not a pointer you can use. Therefore, whenever you call
malloc, it's vital to check the returned pointer before using it! If you call
malloc, and it returns a null pointer, and you go off and use that null pointer
as if it pointed somewhere, your program probably won't last long. Instead, a
program should immediately check for a null pointer, and if it receives one, it
should at the very least print an error message and exit, or perhaps figure
out some way of proceeding without the memory it asked for. But it cannot
go on to use the null pointer it got back from malloc in any way, because
that null pointer by definition points nowhere. (“It cannot use a null pointer in
any way'' means that the program cannot use the * or [] operators on such a
pointer value, or pass it to any function that expects a valid pointer.)
A call to malloc, with an error check, typically looks something like this:
int *ip = (int *)malloc(100 * sizeof(int));
if(ip == NULL)
{
printf("out of memory\n");
exit or return
}
After printing the error message, this code should return to its caller, or exit
from the program entirely; it cannot proceed with the code that would have
used ip.
Of course, in our examples so far, we've still limited ourselves to “fixed size''
regions of memory, because we've been calling malloc with fixed arguments
like 10 or 100. (Our call to getline is still limited to 100-character lines, or
whatever number we set the linelen variable to; our ip variable still points at
only 100 ints.) However, since the sizes are now values which can in
principle be determined at run-time, we've at least moved beyond having to
recompile the program (with a bigger array) to accommodate longer lines,
and with a little more work, we could arrange that the “arrays'' automatically
grew to be as large as required. (For example, we could write something like
getline which could read the longest input line actually seen).
12.2.2 Allocating memory with calloc
calloc is another memory allocation function that is normally used for
requesting memory space at run time for storing derived data types such as
arrays and structures. While malloc allocates a single block of storage
space, calloc allocates multiple blocks of storage, each of the same size,
and then sets all bytes to zero. The general form of calloc is:
ptr=(cast-type *)calloc(n, elem-size);
The above statement allocates contiguous space for n blocks, each of size
elem-size bytes. All bytes are initialized to zero and a pointer to the first byte
of the allocated region is returned. If there is not enough space, a NULL
pointer is returned.
Now we don't even have the pointer to the freed memory any more, and (as
long as we check to see that p is non-NULL before using it), we won't
misuse any memory via the pointer p.
When thinking about malloc, free, and dynamically-allocated memory in
general, remember again the distinction between a pointer and what it
points to. If you call malloc to allocate some memory, and store the pointer
which malloc gives you in a local pointer variable, what happens when the
function containing the local pointer variable returns? If the local pointer
variable has automatic duration (which is the default, unless the variable is
declared static), it will disappear when the function returns. But for the
pointer variable to disappear says nothing about the memory pointed to!
That memory still exists and, as far as malloc and free are concerned, is still
allocated. The only thing that has disappeared is the pointer variable you
had which pointed at the allocated memory. (Furthermore, if it contained the
only copy of the pointer you had, once it disappears, you'll have no way of
freeing the memory, and no way of using it, either. Using memory and
freeing memory both require that you have at least one pointer to the
memory!)
12.2.4 Reallocating Memory Blocks
Sometimes you're not sure at first how much memory you'll need. For
example, if you need to store a series of items you read from the user, and if
the only way to know how many there are is to read them until the user
types some “end'' signal, you'll have no way of knowing, as you begin
reading and storing the first few, how many you'll have seen by the time you
do see that “end'' marker. You might want to allocate room for, say, 100
items, and if the user enters a 101st item before entering the “end'' marker,
you might wish for a way to say ”uh, malloc, remember those 100 items I
asked for? Could I change my mind and have 200 instead?''
In fact, you can do exactly this, with the realloc function. You hand realloc
an old pointer (such as you received from an initial call to malloc) and a new
size, and realloc does what it can to give you a chunk of memory big
enough to hold the new size. For example, if we wanted the ip variable from
an earlier example to point at 200 ints instead of 100, we could try calling
ip = realloc(ip, 200 * sizeof(int));
nalloc = 100;
ip = (int *)malloc(nalloc * sizeof(int)); /* initial allocation */
if(ip == NULL)
{
printf("out of memory\n");
exit(1);
}
nitems = 0;
ip[nitems++] = atoi(line);
}
We use two different variables to keep track of the “array'' pointed to by ip.
nalloc is how many elements we've allocated, and nitems is how many of
them are in use. Whenever we're about to store another item in the “array,''
if nitems >= nalloc, the old “array'' is full, and it's time to call realloc to make
it bigger.
Finally, we might ask what the return type of malloc and realloc is, if they are
able to return pointers to char or pointers to int or (though we haven't seen it
yet) pointers to any other type. The answer is that both of these functions
are declared (in <stdlib.h>) as returning a type we haven't seen, void * (that
Manipal University Jaipur B1639 Page No.: 213
Programming in C Unit 12
is, pointer to void). We haven't really seen type void, either, but what's going
on here is that void * is specially defined as a “generic'' pointer type, which
may be used (strictly speaking, assigned to or from) any pointer type.
Self Assessment Questions
1. The process of allocating memory at run time is known as
_________________.
2. malloc() function returns a pointer to integer. (True/False)
3. For deallocating memory, you can use _________ function.
4. The function that is used to alter the size of a block previously allocated
is __________________.
}
else
printf("The list is empty\n");
}
void main()
{
int n;
int x;
struct node *start = NULL ;
printf("Enter the nodes to be created \n");
scanf("%d",&n);
while ( n -- > 0 )
{
printf( "Enter the data values to be placed in a node\n");
scanf("%d",&x);
start = insert ( start, x );
}
printf("The created list is\n");
printlist ( start );
}
scanf("%d",&x);
start = insert ( start, x );
}
printf("The created list is\n");
printlist ( start );
}
p = p-> link;
}
}
temp1 = temp2;
temp2 = temp2-> link;
}
if(prev == NULL)
p = min -> link;
else
prev -> link = min -> link;
min -> link = NULL;
if( q == NULL)
q = min; /* moves the node with lowest data value in the list
pointed to by p to the list pointed to by q as a first node*/
else
{
temp1 = q;
/* traverses the list pointed to by q to get pointer to its
last node */
while( temp1 -> link != NULL)
temp1 = temp1 -> link;
temp1 -> link = min; /* moves the node with lowest data value in the
list pointed to by p to the list pointed to by q at the end of list pointed by q*/
}
}
return (q);
}
void main()
{
int n;
int x;
struct node *start = NULL ;
printf("Enter the nodes to be created \n");
scanf("%d",&n);
while ( n- > 0 )
{
printf( "Enter the data values to be placed in a
node\n");
scanf("%d",&x);
start = insert ( start,x);
}
printf("The created list is\n");
printlist ( start );
start = sortlist(start);
printf("The sorted list is\n");
printlist ( start );
start = reverse(start);
printf("The reversed list is\n");
printlist ( start );
}
{
int n;
int x;
struct node *start = NULL;
printf("Enter the nodes to be created \n");
scanf("%d",&n);
while ( n-- > 0 )
{
printf( "Enter the data values to be placed in a node\n");
scanf("%d",&x);
start = insert ( start, x );
}
printf(" The list before deletion id\n");
printlist ( start );
printf("% \n Enter the node no \n");
scanf ( " %d",&n);
start = delet (start , n );
printf(" The list after deletion is\n");
printlist ( start );
}
/* a function to delete the specified node*/
struct node *delet ( struct node *p, int node_no )
{
struct node *prev, *curr ;
int i;
if (p == NULL )
{
printf("There is no node to be deleted \n");
}
else
{
if ( node_no > length (p))
{
printf("Error\n");
}
Manipal University Jaipur B1639 Page No.: 226
Programming in C Unit 12
else
{
prev = NULL;
curr = p;
i=1;
while ( i < node_no )
{
prev = curr;
curr = curr-> link;
i = i+1;
}
if ( prev == NULL )
{
p = curr -> link;
free ( curr );
}
else
{
prev -> link = curr -> link ;
free ( curr );
}
}
}
return(p);
}
/* a function to compute the length of a linked list */
int length ( struct node *p )
{
int count = 0 ;
while ( p != NULL )
{
count++;
p = p->link;
}
return ( count ) ;
}
12.5 Summary
The process of allocating memory at run time is known as Dynamic Memory
Allocation. The allocation of memory is done using three functions: malloc,
calloc, and realloc. These functions return the pointers to void, so it can be
typecast to any data type, thus making the functions generic. The memory
space that is used for dynamic allocation during execution of the program is
called heap. When a pointer doesn't point where you think it does, if you
inadvertently access or modify the memory it points to, you can damage
other parts of your program. So safety of pointers is essential in dynamic
memory allocation. Linked list is one of the applications of dynamic memory
allocation.
12.9 Exercises
1. Write a menu driven program to create a linked list of a class of students
and perform the following operations.
i. Write out the contents of a list
ii. Edit the details of a specified student
iii. Count the number of students above a specified age and weight
2. Write a program to insert a node after the specified node in a linked list.
3. Write a program to count the number of nodes in linked list.
4. Write a program to merge two sorted lists.
5. Explain briefly how to represent polynomials using linked lists. Write a
program to add two polynomials.