0% found this document useful (0 votes)
9 views32 pages

ITP Week 9

This document covers dynamic memory allocation in C, focusing on the stack and heap memory segments, and the functions for memory management such as malloc(), calloc(), realloc(), and free(). It explains the differences between static and dynamic arrays, how to return arrays from functions, and the ability to resize dynamic arrays. Additionally, it introduces dynamically allocated structures and 2-D arrays, providing examples and practice assignments for better understanding.

Uploaded by

nigejo5676
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
9 views32 pages

ITP Week 9

This document covers dynamic memory allocation in C, focusing on the stack and heap memory segments, and the functions for memory management such as malloc(), calloc(), realloc(), and free(). It explains the differences between static and dynamic arrays, how to return arrays from functions, and the ability to resize dynamic arrays. Additionally, it introduces dynamically allocated structures and 2-D arrays, providing examples and practice assignments for better understanding.

Uploaded by

nigejo5676
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 32

Dynamic Memory Allocation

In this reading, you will learn about dynamically allocating memory and dynamic memory management.
You will also learn about two different memory partitions in C, the stack, and the heap. Then, you will gain
insight into the different functions provided in the C libraries for dynamic memory management. Finally,
you will be introduced to the differences between static and dynamic arrays and how to return arrays from
functions.

Main Reading Section:

Stack and Heap

Let us say we have written a C program P1. You would typically save it as a .c file, say P1.c. This file resides
on your disk. When you compile the program with a compiler such as gcc, the executable file is created and
stored on the disk, say P1.exe. Now, when you try to execute this file, the OS first loads the executable file
into the RAM, and then the processor executes the program sequentially. This is illustrated in Figure 1.

Figure 1. A program executing in memory

Now, the memory allocated to the program on the RAM consists of various sections. These are partitioned
in the form of segments. A C program executing in memory has four segments, i.e., Stack, Heap, Data, and
Text, as illustrated in Figure 2.

Figure 2. Segments of an executing C program


Stack Memory

The stack segment is the place where all the auto (or local) variables in our functions are allocated memory.
Each function call gets allocated a frame and this frame contains all the local variables of that function.
When the function reaches the end of its execution and the flow returns to the calling function, the frame is
popped off the stack. In other words, the frame is destroyed, and the space occupied by it is released to the
Operating System to be used for other purposes.

A stack can be thought of as a stack of files that need to be attended to. Each new function call places a new
file on the stack and the height of the stack grows. After the file is attended to by the concerned individual
(or function executed by the CPU), the file is removed from the stack releasing space on the pile. At this
point, the CPU does not remember the contents of the popped file (variables of the popped function call
frame) anymore. They are lost.

Let us understand this through a concrete example. Here is a program:

When the above program starts to execute, a frame gets allocated in the stack for the main() function. This
is illustrated in Figure 3. This frame contains the local variable b. When the call to f1() is made, a frame gets
allocated for f1(). This frame contains, among other information, the local variable a. After f1() completes
its execution, when the control goes back to main, the frame for f1() is popped off the stack. When main()
ends, the frame for main() is also popped.

Figure 3. Illustration of the stack frames being allocated and popped as the program is executed

Heap Memory

Heap refers to that portion of memory that is dynamically and explicitly allocated by the programmer. Here,
since the compiler does not allocate memory by itself, the responsibility of freeing up the allocated memory
also lies with the user. We shall learn how to allocate this memory in the sections ahead. This memory is
accessible globally to all functions. For example, if you dynamically allocate 10 bytes of memory inside f1()
in the above code, that memory will not be freed up when the call stack for f1() is popped. It will persist
until the end of the program execution unless freed explicitly.

Dynamic Memory Allocation

We saw that when we declare a local variable in a function, it is lost when the stack frame is popped.
However, if needed, it can be returned to the calling statement via the return statement. Now, consider the
case of arrays. If the array is declared in a function, and we try to return it, it would be returned by reference.
Thus, the calling function would receive a pointer to a location that is no longer reserved for the array and
is now actually free. These location contents can be filled by some other data unpredictably and would lead
to unpredictable behavior.

Another issue with arrays is that their size cannot be altered at run-time. So, we tend to oversize arrays to
accommodate our needs. This has two issues. First, it is not memory efficient as we would ideally want to
allocate exactly the amount of memory that we use. Second, it may be difficult to exactly estimate how
much oversizing would be sufficient. These and many more challenges can be solved by being able to
dynamically allocate and deallocate memory. Here, the term dynamic means the allocation is being done
at run-time, as opposed to static allocation where the memory requirements are known at compile-time.

This feature is known as Dynamic Memory Management.

Dynamic Memory Management provides immense flexibility to the programmer to allocate and use memory
as they wish. However, this comes with the caveat that the programmer needs to ensure that unused
allocated memory is also freed on time. C provides many functions to achieve Dynamic Memory
Management such as malloc(), calloc(), realloc(), and free(). We shall now see the working of these
functions in detail. These functions are included in the stdlib.h header file.

malloc()

malloc() is a function that accepts an integer number of bytes, allocates that amount of memory, and
returns a void pointer to the memory block of the required size. This allocated memory is present in the
heap.

The void pointer is a special type of pointer, which is used since the function does not know the type of data
that would be stored in the allocated memory. So, it is kept as void * by default and can be type-cast into
any type of pointer as per the need. This returned value would typically be stored in a local pointer so that
the newly allocated memory is accessible.

For example, consider Figure 4.


Figure 4. Syntax and semantics of malloc()

Here, we have requested malloc to allocate sizeof(int) number of bytes. The returned pointer (of type void
*) is cast into a pointer to an int (int *) and assigned to the int pointer pt.

Note that if malloc is not able to allocate the required amount of memory, it allocates a NULL pointer. Thus,
it is a good practice to always check the pointer returned by malloc() against a NULL pointer to check
whether the allocation was successful.

The allocated memory need not always be exactly the size of the data type that would be stored there. We
can request an integer multiple of it to simulate an array as well.

For example, consider the following program:


By passing the argument 10 * sizeof(int) to malloc, p becomes a pointer pointing to a dynamically
allocated memory block that can hold 10 integers. If the OS could not allocate the requested amount of
memory, it would return a NULL pointer. In that case, the condition checked in the if block is satisfied, an
appropriate message is displayed to the user, and the program terminates. If the request was processed
successfully, p != NULL evaluates to true and p now points to a valid dynamically allocated block. The
locations can be accessed equivalently using pointer arithmetic or the [] notation. It can be treated as an
array. After performing some operations on the block (assigning and printing values), when the block is no
longer needed, it can be freed using the free() function (to be covered next).

A successful run of the above program produces the following output:

Here, since the requested amount of memory is small(10 integers), the OS would be able to fulfill the
requirement almost always. However, if we modify it to request very large amounts of memory, malloc may
fail to fulfill the request.

For example, on modifying the malloc() call to

the output obtained on our system was:

Thus, it is always recommended to check for the possibility of NULL pointers before dereferencing and
using the dynamically allocated memory.

free()

free() is a function that is used to free a dynamically allocated block of memory. It accepts a single argument,
namely an address pointing to a dynamically allocated block (i.e., a block allocated using malloc, calloc(),
or realloc(). If a block is not freed, that memory becomes blocked for the duration of the execution of the
program. Failure to free can result in heap depletion. This increases the chances of malloc() or calloc()
returning NULL signifying unsuccessful allocation. It is a good practice to always free blocks that are no
longer needed.

calloc()

In the above function, we allocated 10 integers by passing the request size as 10*sizeof(int) to malloc. This
calculation can be cumbersome at times. Also, at this point, the block is filled with garbage(random values).
So, a function calloc() is provided that encapsulates the size calculation and zero initialization while
allocating arrays. Its syntax is:

That is, we pass in the number of elements required and the size of a single element to calloc() while
allocating a block corresponding to an array of elements. calloc() allocates the block, explicitly sets the
elements of the allocated block to be all zeroes, and returns the required pointer.

realloc()
The function realloc() is used when we need to resize a block that has been previously allocated. For
example, consider the following code snippet:

Here, ptr was initially allocated a size equivalent to 5 integers. If, at a later point in time, we wish to increase
the size to 10 integers, we can use realloc() as shown above. realloc() accepts a dynamically allocated
address and a size in bytes. It allocates a block of the new size, copies the contents pointed in the old block
to the new block, and then frees the old block.

Thus, we can see that the above functions can be used to allocate and deallocate memory dynamically and
provide tremendous flexibility to the users.

Static and Dynamic Arrays

Now, we can revisit the drawbacks of (static) arrays that we started the section on Dynamic Memory
Allocation with. We now know how to use both static and dynamic arrays. We have also seen the similarities
and differences between them. The following table encapsulates the differences between them:

We talked about the issue of returning arrays earlier. When we want to return static arrays from functions,
we are unable to do so since they are passed back by reference. If the array was declared in the called
function and returned to the calling function, the reference location returned no longer belongs to the array
on the stack. Thus, the returned address is of no use.

So, this issue can be solved in two ways. The first approach is to pass an empty array to the function,
populate it within the function, and return the populated array to the calling function. Consider the
following example:
Here, in main(), an array a is declared. We want to define a function copy() that copies the contents of a
into a new array b. We achieve it by passing a new empty array of the same size (here b) as well to copy().
copy() copies the contents of a to b and does not return anything. Since arrays are passed by reference,
this has the effect of actually copying a into b. Thus, functions can effectively "return" arrays by accepting
the array to be returned as an additional parameter and mutating it. Here, the scope is the calling function
and not the called function. So, the array will not be destroyed at return.

Another approach would be to dynamically allocate the array on the heap in the called function and, thus,
solve the issue of the array being present on the popped-off stack frames. For example, consider the
following program:
Here, the function copy() accepts only the old array a and its length n as parameters. The new array b is
allocated dynamically inside copy() using malloc(). It is populated with the values of a and is returned as a
pointer to main(). In this way, we are able to achieve behavior similar to "returning arrays", by returning
pointers to dynamically allocated blocks.

Thus, we can see that both approaches of passing in empty arrays as well as dynamically allocating the new
array in the called function can be used to achieve the required functionality of returning an array from a
function. We prefer either approach based on the constraints and needs of the problem required to be
solved.

Another issue that we talked about was the inability to resize static arrays. We can see this issue gets
resolved in dynamic arrays. Assume we have allocated an array of 10 int values using malloc() (or calloc()).
Now, assuming that we now require to store 5 more int variables in the array, we can simply call realloc()
on the old array with the new size and achieve the required resizing functionality.

Thus, we now have the ability to resize dynamic arrays, which we did not have with static arrays.
Reading Summary:

In this reading, you have learned the following:

• The concept of stack and heap

• How to dynamically allocate and deallocate memory using malloc, calloc, realloc, and free

• The difference between static and dynamic arrays

• How to return arrays from functions

• How to resize dynamic arrays


Practice Lab 1 - Dynamic Memory Allocation
Assignment Brief

Practice Lab 1_Question_1

Recall calloc() is a function that allocates memory and initializes it to zero. It is defined in stdlib.h. The
function takes two arguments: the number of elements and the size of each element.

Implement the function my_calloc() that mimics the behavior of calloc(). You may use malloc() and free() in
your implementation, but you should not use calloc() or realloc(). The prototype of the function is: void
*my_calloc(int num, int size); It should return a pointer to void. The function should return NULL if the
allocation fails.

[Hint: malloc() returns NULL if the allocation fails].

For example, my_calloc(2,3) would return a block of 6 bytes initialized to all zeroes. my_calloc(8, sizeof(int))
would return a block that can hold 8 int values, initialized to all 0s.

Practice Lab 1_Question_2

Write a program to read n floating point numbers using malloc and find their average. Read the number of
elements from the user. Use scanf() to read the elements. You should allocate memory for the array using
malloc. Now, calculate the average of the elements and print it. Use free() to free the memory allocated for
the array.

Testcase1

Input:

Enter the number of elements: 5

Enter 5 elements: 1.2 2.3 3.4 4.5 5.6

Output:

Average = 3.4.
More Dynamic Memory Allocation
Topic: Dynamically Allocated Structures and 2-D Arrays

Reading Objective:

In this reading, you will learn about dynamically allocated structures and 2-D arrays. These constructs follow logically from
dynamic memory allocation of primitive types and 1-D arrays.

Main Reading Section:

Dynamically Allocated Structures

We have seen that we can allocate variables and arrays of primitive types dynamically using the malloc() function. We can
do the same for structures using exactly the same syntax.

For example, consider the following program:

The above code allocates a single structure variable dynamically. The malloc() function returns a void pointer, which we
cast to a pointer to a structure of type struct student.

Similarly, we can allocate an array of structures dynamically using the following syntax:

In order to free the memory allocated for a dynamically-allocated structure, we use the free() function. For example,

The above code frees the memory allocated for the structure variable s and the array s_arr. It works for both isolated
structures and arrays of structures.

Let us put together the above lines of code and view it as a complete program. We write a program to dynamically allocate
an array of 10 students, take the input for the records of the students from the user, and print the details of the student
with the highest CGPA.
Here, we first define the struct student with all of its data members. Then, we dynamically allocate an array of 10 students
using malloc() and store the allocated block’s address in a pointer variable studArray. We then take in input for the records
of the students in the first while loop. After that, we declare a temporary struct student variable temp to store a copy of
the student with the greatest CGPA. Now, we linearly search for the student with the greatest CGPA in our studArray using
a while loop. Whenever we encounter a student with CGPA greater than the student currently stored in temp, we
update temp to now store a copy of the new student. In this way, at the end of the loop, temp now stores a copy of the
required student. Now, we print the roll no. of the required student using a printf() call. In this way, we can use dynamically
allocated arrays of structures to write a diverse range of real-world programs.

2-D Arrays

We have seen that we can allocate memory for 1-D arrays dynamically using the malloc() function. We can do the same for
2-D arrays using exactly the same syntax.

A 2-D array is a 1-D array of 1-D arrays. So, when using pointers, they are a pointer to a pointer. For example, consider the
following code snippet:
The above code block allocates a 2-D array a of 3 rows. Each row is a pointer to an integer. We can allocate memory for
each row using the following syntax:

The above code block allocates memory for each row of the 2-D array. The first row is pointed to by a[0], the second row is
pointed to by a[1], and so on. The above code allocates memory for 2 integers in each row. Figure 1 illustrates the resultant
structure of the above code snippets.

Figure 1. 2D array

Now, we can also create a jagged array wherein each row is of different lengths. This can be done with the following code
snippet in place of the previous allocation in the loop:

Figure 2. Jagged 2D array

Figure 2 illustrates the jagged array created. Note that this kind of jagged array was not possible using purely static
multidimensional arrays.

In order to free the memory allocated for a dynamically allocated 2-D array, we use the free() function. Again, we first need
to free the memory allocated for each row and then free the memory allocated for the array itself. The following code
illustrates this:
Thus, we are now able to use dynamic memory allocation with a variety of data types and constructs, allowing us immense
flexibility in writing complex programs. The arrays thus created can be used, accessed, modified, etc., in the same way as
we covered for static arrays and static arrays of structures. The syntax is exactly the same. The only difference is in their
creation and the memory segment in which they are stored.

Reading Summary:

In this reading, you have learned the following:

• The concept of dynamically allocated structures

• The concept of 2-D arrays and jagged arrays

• How to allocate and free memory for dynamically allocated structures and 2-D arrays
Practice Lab 2 - More Dynamic Memory AllocationAssignment Brief
Practice Lab 2_Question_1

Imagine you are running a pizza shop. Now, pizza is a struct that contains

- base - the base of the pizza. It is an int that represents the index of the base in the global array of bases. For
example, if the base is "Thin", then the index is 0.

- topping - an array of toppings of length NUM_TOPPINGS. Each element is 0 or 1 depending on whether the
topping is present or not. For example, if the toppings are "Tomatoes" and "Olives", then the array is [0, 1,
0, 0, 1]

Every time you receive a new order, you need to instantiate a struct order that contains: -

• order_id: the order number

• customer_name: the name of the customer

• customer_phone: the phone number of the customer

• pizza: a reference to the pizza struct that the customer ordered

• Price: the price of the pizza

Price of a pizza is calculated as follows: -

• The base price is the price of the base

• The topping price is the sum of the prices of all the toppings

• The total price is the sum of the base price and the topping price

You need functions to -

• Create a new order: // allocate memory for a new order, instantiate its members, return pointer to order.

order *new_order(int order_id, char *customer_name, char *customer_phone, pizza *pizzas,int num_pizzas);

• Create a new pizza: // allocate memory for a new pizza, instantiate its members, return pointer to pizza.

pizza *new_pizza(int base, int *toppings);

• Calculate the price of a pizza: int calculate_price(pizza *p);

• Print the details of an order: void print_order(order *o);

• Print the details of a pizza: void print_pizza(pizza *p);

• Complete order: // This function should free the memory allocated to the order

void complete_order(order *o);

• Destroy pizza: // This function should free the memory allocated to the pizza

void destroy_pizza(pizza *p);


Lesson 1: Dynamic Memory Allocation

Dynamic Memory Allocation


16 and Linked Lists

Chapter 16, Sections 16.1–16.5.


WHAT TO LEARN

Concepts of dynamic memory allocation and the returned generic pointer.


Allocating memory with the malloc and calloc functions.
Freeing the allocated block with the free function.
Resizing an allocated memory block with realloc.
How negligence leads to memory leaks and dangling pointers.
How 2D arrays can be simulated on-the-fly while a program is running.
Attributes of a linked list and how they overcome the limitations of an array.
Manipulating a linked list with user-defined functions.
Features of the abstract data types—stacks, queues and trees.

16.1 MEMORY ALLOCATION BASICS


Up until now, memory for variables, arrays and structures have been allocated at the time of their
definition. For variables and arrays, definition is synonymous with declaration. The amount of
memory thus allocated is fixed at compile time and can’t be modified at runtime. This inflexibility
becomes a serious limitation when working with arrays. In our quest to ensure that arrays are
large enough to accommodate our needs, we often tend to oversize them. Because of the wastage
resulting from this static allocation of memory, our programs are often not as efficient as they could
otherwise have been.
The solution to this problem lies in dynamically allocating memory at runtime, i.e., as and when the
program needs it. Access to the allocated block is provided by a generic pointer that is automatically
aligned to reflect the correct type of data pointed to. Because a pointer to a contiguous memory
block can also be treated as an array, we can easily manipulate this array. In the course of program
execution, if this memory chunk is found to be under- or over-sized, it can be resized using a special
function (realloc) of the standard library.
Dynamic Memory Allocation and Linked Lists 521

This flexibility comes at a price—the responsibility to free up the allocated memory when it is no
longer required. So far, memory deallocation has not concerned us because the system has a well-
defined mechanism for deallocating memory for variables defined inside and outside a function.
A function on return frees memory used by its local variables, while global and static variables are
automatically deallocated on program termination. For memory allocated dynamically, we must do
this job ourselves. If we fail to behave responsibly, there can be serious runtime problems resulting
from memory leaks.
When memory is allocated at runtime, its size is usually determined either by user input or an
expression evaluated by the running program. This feature is exploited by a special data structure
called a linked list. Unlike an array, a linked list cannot be defined with a predetermined size—
either at compile time or runtime. When an item is added to the list, memory is created for that
item at that moment and a link (in the form of a pointer) is established between this new item and
its immediate predecessor in the list.
In this chapter, we’ll first examine the library functions that dynamically allocate memory and one
function that frees it. Using these functions, we’ll develop the techniques needed to create, modify
and query a linked list that can be expanded and contracted at will.

16.2 THE FUNCTIONS FOR DYNAMIC MEMORY ALLOCATION


When a program needs memory to be allocated, it makes a request to the operating system.
If the allocated memory is later found to be inadequate or excessive, the program can also ask for
a reallocation with a revised size. The C library supports a set of four functions for dynamically
allocating and deallocating memory. The file stdlib.h must be included for the following functions
to work properly:
malloc This function uses a single argument to specify the memory requirement in bytes.
calloc For allocating memory for arrays, calloc is more convenient to use than malloc.
calloc uses two arguments—the number of array elements and the size of each
element.
realloc This is the function to use for resizing a memory block allocated by a prior call to
malloc or calloc.
free Memory allocated by any of the previous functions is deallocated by this function.
How does one access the allocated or reallocated memory? Unlike variables, arrays and structures,
a dynamically allocated memory block has no name. For providing access to this memory block,
all of these functions (barring free) return a generic pointer that points to the base address of
the block. All read-write operations on the block are subsequently carried out using this pointer.
The free function also uses this pointer to deallocate memory.
Because memory allocation can sometimes fail (for reasons that are discussed soon), the three
allocation functions must always be tested for NULL on invocation (even though we won’t always
practice what we have just preached).
522 Computer Fundamentals & C Programming

16.2.1 The Generic Pointer


The pointer returned by the memory allocation functions is generic because a memory block can
be used to store any data type—fundamental, derived or user-defined. Since C89, a void pointer
can be assigned to a pointer of any type with or without using an explicit cast. For instance, the
following statements using the malloc function assign the base address of a block of 12 bytes
to a pointer variable p of type int *:
int *p = (int *) malloc(12); Not recommended
int *p = malloc(12); Recommended
Many programmers and textbooks use the former form, which made sense in the pre-C89 days
when the explicit cast was necessary. However, C89, which introduced the void pointer, dispenses
with the need to use a cast. We’ll, therefore, use malloc, calloc and realloc without the cast (second
form) to avoid cluttering the code, which also makes it easier to maintain.

16.2.2 Error Handling


All of these functions can fail if memory can’t be allocated (or reallocated). Even though a computer
may have a lot of “free” memory, it is not entirely available for dynamic memory allocation.
While memory is needed by the code and data of a program, the latter needs additional memory
while it is running. This additional memory is of two types—the stack and heap. Figure 16.1
replicates Figure 11.3 which shows the memory layout of these segments in a typical UNIX system;
other systems are broadly similar.
The stack is consumed by parameters and local variables used by a function. The stack grows
continuously as one function calls another, and shrinks when the functions return. Stack overflow
commonly occurs when too many recursive function calls are made without encountering the
terminating condition (11.3).

Stack

Heap

Data

Program Code

FIGURE 16.1 Organization of the Stack and Heap in Memory


Lesson 2: More Dynamic Memory Allocation

Chapter 16, Sections 16.3–16.9


Dynamic Memory Allocation and Linked Lists 523

The heap is used for dynamically allocating memory. It grows at the expense of the stack and vice
versa. A block of memory that is freed using the free function is returned to the heap. Failure to
free memory that is no longer required can result in severe heap depletion that may make further
allocation impossible. The memory allocation functions return NULL if they fail to find the
specified amount of memory as a contiguous chunk in the heap. Every time we use these functions,
we must thus check for NULL.
Note: Dynamic memory allocation creates a chunk of contiguous memory. A pointer to this chunk
can be treated as an array except that the latter is a constant pointer unlike a regular pointer which
can be made to point to this block.

16.3 malloc: SPECIFYING MEMORY REQUIREMENT IN BYTES


Prototype: void *malloc(size_t size);
Header File: stdlib.h
Return Type: A generic pointer to the allocated memory on success, NULL on failure.

We begin our discussions on the individual functions with malloc which is most commonly used
for dynamic memory allocation. malloc accepts the size of the requested memory in bytes as an
argument and returns a generic pointer to the base address of the allocated block. As shown in the
following, this generic pointer is suitably aligned to a pointer variable of a specific data type before
the memory chunk can be accessed:
int *p;
p = malloc(4); No cast needed
malloc allocates a chunk of four bytes whose pointer is implicitly converted to int * on assignment.
The allocated space can now be used to store a four-byte int, but the portable and correct technique
would be to use sizeof(int) as the argument to malloc:
p = malloc(sizeof(int));

malloc doesn’t initialize the allocated block, so each byte has a random value. (This is not the case
with calloc.) However, we can dereference the pointer p to assign a value to this block:
*p = 5; Assigns value to unnamed memory segment
Observe that the allocated block can’t be treated as a variable even though it can be manipulated by
its pointer. You must not lose this pointer because it represents the only mechanism that provides
access to the block.. Further, this pointer is also used by the free function to deallocate the block:
free(p); Deallocates memory allocated by malloc
The pointer returned by malloc is also used by the realloc function for resizing the allocated block,
if considered necessary. Both realloc and free are examined soon.
524 Computer Fundamentals & C Programming

16.3.1 malloc.c: An Introductory Program


Before discussing the other features related to malloc, let’s consider Program 16.1 which invokes
malloc three times to return pointers of three different data types. The program doesn’t use the
free function to deallocate the blocks because it is not required here. The annotations make further
discussions on this program unnecessary.

/* malloc.c: Uses malloc without error checking. */

#include <stdio.h>
#include <stdlib.h> /* Needed by malloc, calloc, realloc and free */
int main(void)
{
short *p1;
int *p2;
float *p3;
p1 = malloc(2); /* Non-portable; not recommended */
p2 = malloc(sizeof(int)); /* The right way */
p3 = malloc(sizeof(float)); /* Ditto */
*p1 = 256; *p3 = 123.456;
printf(“*p1 = %hd\n”, *p1);
printf(“*p2 = %d\n”, *p2); /* Prints uninitialized value */
printf(“*p3 = %f\n”, *p3);
exit(0); /* Frees all allocated blocks */
}

PROGRAM 16.1: malloc.c


*p1 = 256
*p2 = 0 Uninitialized value; may not be 0
*p3 = 123.456001

PROGRAM OUTPUT: malloc.c

16.3.2 Error-Checking in malloc


The preceding program assumed that malloc always succeeds in allocating memory, which may
not be the case. The function fails when there is not enough contiguous memory available for
allocation in the heap. We must, therefore, check the pointer returned by malloc for NULL before
using it. If malloc fails, it may not make sense to continue with program execution:
long *p = malloc(sizeof(long));
if (p == NULL) {
fputs(“Memory allocation failed\n”, stderr);
exit(1);
}
Dynamic Memory Allocation and Linked Lists 525

When the total amount of dynamic memory required by a program exceeds the amount available
on the heap, a program must recycle memory. At any instant, adequate memory must be available
for the next allocation. For small programs that eventually terminate, this is not an issue as the
allocated memory is automatically returned to the heap on program termination. However, memory
recycling is essential for server programs that run continuously without ever terminating.
16.3.3 Using malloc to Store an Array
Because array elements are stored contiguously in memory and malloc creates a contiguous block,
it is often used to create space for an array. Thus, the following statement creates a memory block
for storing 10 short values:
short *p;
p = malloc(10 * sizeof(short));
Since the name of an array represents a pointer to the first array element, p can be treated as an
array for navigating and performing pointer arithmetic. We can assign values to the “elements” of
this dynamically created “array” using either pointer or array notation:
Pointer Notation Array Notation
*p = 5; p[0] = 5;
*(p + 1) = 10; p[1] = 10;
*(p + 2) = 15; p[2] = 15;

Even though the calloc function has been specifically designed for allocating memory for arrays
and structures, simple arrays are easily handled with malloc. As the next program demonstrates,
the size of the array can be determined at runtime.
Takeaway: The malloc function makes available a contiguous block of memory that
can be used to store any data type including arrays and structures but without automatic
initialization.

16.3.4 malloc_array.c: An Array-Handling Program Using malloc


Program 16.2 uses malloc to create space for an array, where the number of elements is determined by
user input. Unlike Program 16.1, this one validates the return value (p) of malloc. Because p is used
to browse the array and finally free the allocated block, it is necessary to keep its value unchanged.
For this reason, p is copied to q, which is changed in a loop to assign values to the array elements.
/* malloc_array.c: Uses memory allocated by malloc as an array after
saving the original pointer returned. */
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *p, *q;
short size, i;
526 Computer Fundamentals & C Programming

fputs(“Enter number of array elements: “, stderr);


scanf(“%hd”, &size);
p = malloc(size * sizeof(int));
if (p == NULL) {
fprintf(stderr, “Memory allocation failed.\n”);
exit(1);
}
q = p; /* Saves p for later use */
/* Assigning values to the “array” q ... */
for (i = 0; i < size; i++)
*q++ = i * 10;
/* ... and printing them using p */
for (i = 0; i < size; i++)
printf(“p[%hd] = %6d, Address = %p\n”, i, p[i], (p + i));
free(p); /* Deallocates block, but not necessary here */
exit(0);
}
PROGRAM 16.2: malloc_array.c
Enter number of array elements: 5
p[0] = 0, Address = 0x804b008
p[1] = 10, Address = 0x804b00c 4 bytes more than previous address
p[2] = 20, Address = 0x804b010 ... Ditto ...
p[3] = 30, Address = 0x804b014 ... Ditto ...
p[4] = 40, Address = 0x804b018 ... Ditto ...
PROGRAM OUTPUT: malloc_array.c

Takeaway: malloc doesn’t determine the data type of the dynamically created block. The type
is determined by the user when declaring the pointer to this block.

Caution: It is a common programming error to use the pointer variable assigned by malloc in
a subsequent call to the same function without freeing the previous block.

16.4 free: FREEING MEMORY ALLOCATED BY malloc


Memory allocated dynamically must be freed when it is no longer required. This will happen
automatically when the program terminates, but we often can’t afford to wait for that to happen.
Memory could be in short supply, the reason why we may need to explicitly free it with the free
function.
free takes the pointer returned by malloc, calloc and realloc as its only argument and frees the
block associated with the pointer. The function uses the following prototype:
void free(void *ptr);
Dynamic Memory Allocation and Linked Lists 527

free returns nothing and ptr must have been previously returned by any of the memory allocation
functions. Thus, the memory allocated by
int *p = malloc(sizeof(int) * 10);

is returned to the heap by a call to free, preferably followed by setting the pointer to NULL:
free(p); Pointer lost
p = NULL; Can’t access the freed memory
free doesn’t need to know the size of the block because it is known to the OS which is ultimately
responsible for memory management. Because of its subsequent assignment to NULL, the original
value of the pointer is now lost, so the freed block can no longer be accessed directly.
For programs performing simple tasks that consume insignificant amounts of memory, we need not
use free because the allocated memory will be automatically deallocated on program termination.
But when heap memory is scarce, a program that calls malloc 100 times (possibly in a loop) must
also call free 100 times. This ensures that, at any instant of time, adequate memory is available
on the heap for allocation.

Takeaway: The pointer returned by malloc is used for three purposes: (i) for performing
read/write operations on the allocated block, (ii) for resizing the block with realloc,
(iii) for deallocating the block with free.

Note: free can’t release named memory blocks used by variables, arrays and structures.
The argument used by free must point to the beginning of a block previously allocated dynamically.

16.5 MEMORY MISMANAGEMENT


Dynamic memory allocation is associated with a couple of problems that we have not encountered
previously because all memory management was hitherto handled automatically by the program.
We are now in a different world where we need to address the following issues:
Even though the memory block is freed, it is still accessible because the pointer to it is not lost
(dangling pointer).
The memory block is not freed at all. The problem becomes acute when malloc is repeatedly
used in a loop (memory leak).
Failure to handle a dangling pointer or a memory leak may not (unluckily) affect some of your
programs. If your program misbehaves soon, consider yourself lucky because you can rectify the
mistake before finalizing the code. Let’s now examine these two issues.

16.5.1 The Dangling Pointer


Freeing a memory block with free(p) doesn’t necessarily make it inaccessible because p continues
to point to the block. p is now a dangling pointer. As a safety measure, it should be set to NULL
immediately after invoking free:
528 Computer Fundamentals & C Programming

free(p); p becomes a dangling pointer


p = NULL; p is now a null pointer
Because this memory—partially or totally—may have already been allocated, a subsequent
attempt to access it using p could produce erroneous results or even cause the program to crash.
Setting p to NULL solves the problem easily.

16.5.2 Memory Leaks


A different problem arises when p is made to point to another block without freeing the existing one.
The previous block can’t then be freed (except by program termination) because its pointer
is lost. A block without its pointer creates a memory leak which can turn serious when malloc is
repeatedly used in a loop.
What happens when malloc is used in a function where the pointer to the block is declared
as a local variable? Repeated invocation of the function will progressively deplete the heap unless
arrangements are made to free the blocks outside the function. Figure 16.2 shows the improved
version of the my_strcpy function (13.8.3) shown with the main section.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
char *my_strcpy2(const char *src);
int main(void){
char stg[80], *p;
printf(“Enter string: “);
scanf(“%[^\n]”, stg);
p = my_strcpy2(stg);
printf(“Copied string: %s\n”, p);
free(p); /* Frees memory created in the function */
return 0;
}

char *my_strcpy2(const char *src)


{
char *dest;
short length;
length = strlen(src) + 1; /* One extra slot for NUL character */
dest = malloc(length);
strcpy(dest, src);
return dest; /* Pointer to block now available in main */
}

FIGURE 16.2 Freeing Memory Created in a Function


Dynamic Memory Allocation and Linked Lists 529

Unlike my_strcpy, the my_strcpy2 function uses a single argument—a pointer to the source string.
The function uses malloc to create a correctly-sized memory block by determining the length of
the string first. The copied string is then written to this block and its pointer is returned by the
function. Note that termination of the function doesn’t free the block which is eventually freed in
main. A memory leak is thus prevented because the pointer to the block could be transmitted to the caller.

Takeaway: A memory block created inside a function remains in existence after the function
terminates. If this block has a local variable pointing to it, this pointer variable must be returned
by the function so it can be used in the caller to free the block.

16.6 malloc_2Darray.c: SIMULATING A 2D ARRAY


How does one use malloc to create space for a 2D array? To briefly recall, a 2D array is an array
where each element represents a separate array (10.12). Also, for the array arr[i][j], the non-existent
“element”, arr[i], represents a pointer to the ith row, which contains j columns. Figure 16.3 depicts
the simulation of a 2D array where each element of the array of pointers points to a separate array
(shown on the right). The left-most pointer must, therefore, be defined as a pointer to a pointer.

** 1 2 3 4 5
*

11 22 33 44 55
*

Pointer to Array of Pointers 111 222 333 444 555


*

55 66 77 88 99
*

555 666 777 888 999


*

Array of Pointers Each row allocated by malloc

FIGURE 16.3 Simulating a 2D Array with an Array of Pointers


Program 16.3 uses malloc in two stages for creating space that can be accessed with 2D array
notation: (i) for storing pointers to the rows, (ii) for storing the columns of each row.
The program populates the array with user input, displays them and finally frees the allocated
spaces. Observe that the block used by the array of pointers is freed after the blocks used by the
columns are freed inside a loop.

/* malloc_2Darray.c: Uses malloc to (i) create an array of pointers,


(ii) an array of short integers for each pointer.
Also populates and prints array. */
#include <stdio.h>
#include <stdlib.h>
530 Computer Fundamentals & C Programming

int main(void)
{
short rows, columns, i, j;
short **p; /* Signifies a 2D array p[ ][ ] */

fputs(“Number of rows and columns: “, stderr);


scanf(“%hd %hd”, &rows, &columns);

/* Allocate memory for array of pointers */


p = malloc(sizeof(short) * rows);
if (p == NULL) {
fputs(“Cannot allocate memory for row pointers: “, stderr);
exit(1);
}

/* Allocate memory for columns of each row */


for (i = 0; i < rows; i++) {
if ((p[i] = malloc(sizeof(short) * columns)) == NULL) {
fputs(“Cannot allocate memory for columns: “, stderr);
exit(1);
}
/* Populate the array ... */
fprintf(stderr, “Key in %hd integers for row %hd: “, columns, i);
for (j= 0; j < columns; j++)
scanf(“%hd”, &p[i][j]);
}
/* ... and print the array */
for (i = 0; i < rows; i++) {
for (j= 0; j < columns; j++)
printf(“%5hd “, p[i][j]);
printf(“\n”);
free(p[i]); /* Frees each pointer allocated for columns */
}
free(p); /* Frees pointer for rows */
exit(0);
}

PROGRAM 16.3: malloc_2darray.c


Number of rows and columns: 3 5
Key in 5 integers for row 0: 1 2 3 4 5
Key in 5 integers for row 1: 11 22 33 44 55
Key in 5 integers for row 2: 111 222 333 444 555
1 2 3 4 5
11 22 33 44 55
111 222 333 444 555

PROGRAM OUTPUT: malloc_2darray.c


Dynamic Memory Allocation and Linked Lists 531

16.7 malloc_strings.c: STORING MULTIPLE STRINGS


The standard technique of reading a string from the keyboard is to use scanf with a char array.
For reading a set of strings, however, we can use a 2D char array (13.12). This technique wastes
space if the strings are of unequal size but we didn’t have a better option then—or so it seemed.
But we do have a better option now, one that uses malloc.
A set of strings can be saved in an array of char pointers (13.13), but this technique doesn’t work
with strings input from the keyboard. You can’t use scanf with a char pointer that doesn’t signify
or point to an array. Program 16.4 achieves the best of both worlds by using malloc to dynamically
allocate memory both for the strings and the array of pointers. No space is wasted for storing the
strings which are handled in the following manner:
Each string is temporarily read into an array (temp) of a fixed size.
The length of the string is computed with strlen.
A block of memory is dynamically created with malloc for the string.
The string is finally copied from temp to the block.
The program first uses malloc to create the array of pointers named p_names after knowing the
number of strings that it has to hold. In the first for loop, each string is input, its size ascertained
and space for it created with malloc. In the second for loop, the string is printed and its storage
deallocated. Finally, the space used by the array of pointers is freed.

/* malloc_strings.c: Program to create correctly-sized memory blocks for


storing multiple strings. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FLUSH_BUFFER while (getchar() != ‘\n’) ;

int main(void) {
int i, length, num;
char **p_names; /* Array of pointers for multiple strings */
char temp[80];
fputs(“Number of strings to enter: “, stderr);
scanf(“%d”,&num);
p_names = malloc(num * sizeof(char *)); /* (A) Allocates memory for ... */
/* ... array of char pointers */
for (i = 0; i < num; i++) {
FLUSH_BUFFER
fputs(“Name: “, stderr);
scanf(“%[^\n]”, temp);
length = strlen(temp);

/* Now allocate memory for every string read */


p_names[i] = malloc(length + 1); /* (B) Extra byte for NUL */
532 Computer Fundamentals & C Programming

if (p_names[i] == NULL) {
fputs(“Memory allocation failed\n”, stderr);
exit(1);
}
strcpy(p_names[i], temp); /* Copies string to allocated memory */
}
for (i = 0; i < num; i++) {
printf(“%s\n”, p_names[i]); /* Prints each string */
free(p_names[i]); /* Frees memory allocated in (B) */
}
free(p_names); /* Frees memory allocated in (A) */
exit(0);
}

PROGRAM 16.4: malloc_strings.c


Number of strings to enter: 5
Name: Michelangelo Buonarroti
Name: Leonardo da Vinci
Name: Vincent van Gogh
Name: Rembrandt
Name: Pablo Picasso
Michelangelo Buonarroti
Leonardo da Vinci
Vincent van Gogh
Rembrandt
Pablo Picasso
PROGRAM OUTPUT: malloc_strings.c
Using scanf with the scan set (9.11.1) in a loop, you can key in multi-word strings. The %s specifier
won’t work here even though it works without problems in printf. The program is conceptually
similar to Program 13.11, except that we are now working with a ragged array (containing an
unequal number of columns).

16.8 calloc: ALLOCATING MEMORY FOR ARRAYS AND STRUCTURES


Prototype: void *calloc(size_t num, size_t size);
Header File: stdlib.h
Return Type: A generic pointer to the allocated memory on success, NULL on failure.

Even though malloc can create space for storing arrays and structures, C supports a separate
function—calloc—for dynamically allocating memory for arrays of any type. Unlike malloc,
calloc needs two arguments:
The number of array elements (num).
The size in bytes of each element (size).
Dynamic Memory Allocation and Linked Lists 533

calloc creates a block of num * size bytes and returns a generic pointer to the beginning of the
block. Unlike malloc, calloc initializes the entire block with zeroes, sparing a programmer the
trouble of using a loop for initialization.
Both of the following allocation statements create a block of memory that can hold an int array
of 10 elements:
int *p;
p = malloc(10 * sizeof(int));
p = calloc(10, sizeof(int)); Pointer to block created by malloc lost!
In either case, the size of the block is typically 40 bytes, but all bytes of the block created by calloc
are initialized to zeroes. If initialization is not required, then you may use either of the two functions,
even though the syntax of calloc is more intuitive when used for storing arrays.

Takeaway: Both malloc and calloc can create space for arrays except that (i) the arguments
of calloc are array-oriented, (ii) calloc initializes the array elements to zero.

Program 16.5 uses calloc to create space for an array of structures containing the performance
parameters of sportsmen. The size of this array is determined at runtime by user input. After the
data is keyed in, the details of the sportsmen are displayed.

/* calloc.c: Allocates memory for an array of structures.


Size of array is determined at runtime. */
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
short i = 0, num;
struct cricketer {
char name[30];
short runs;
float average;
} *p; /* Defines pointer to a structure */
fputs(“Number of structures? “, stderr);
scanf(“%hd”, &num);
p = calloc(num, sizeof(struct cricketer));
if (p == NULL) {
fputs(“Cannot allocate memory, quitting ...\n”, stderr);
exit(1);
}
while (i < num) {
fprintf(stderr, “Enter runs, average, name for element %hd: “, i);
scanf(“%hd %f %[^\n]”, &p[i].runs, &p[i].average, p[i].name);
i++;
}
534 Computer Fundamentals & C Programming

fputs(“\nPrinting each structure ...\n”, stderr);


for (i = 0; i < num; i++)
printf(“%20s %6hd %5.2f\n”, p[i].name, p[i].runs, p[i].average);
free(p);
exit(0);
}

PROGRAM 16.5: calloc.c


Number of structures? 4
Enter runs, average, name for element 0: 6996 99.94 Don Bradman
Enter runs, average, name for element 1: 7249 58.45 Walter Hammond
Enter runs, average, name for element 2: 8032 57.78 Garfield Sobers
Enter runs, average, name for element 3: 15921 53.78 Sachin Tendulkar

Printing each structure ...


Don Bradman 6996 99.94
Walter Hammond 7249 58.45
Garfield Sobers 8032 57.78
Sachin Tendulkar 15921 53.78

PROGRAM OUTPUT: calloc.c

Note: Using malloc in tandem with the memset function, you can simulate the effect of calloc.
memset uses three arguments: (i) a pointer to the block, (ii) the initial value, (iii) the number of bytes
to be initialized. The functions have to be used in the following manner to initialize an array of 10 integers
to zero:
p = malloc(sizeof(int) * 10);
memset(p, 0, sizeof(int) * 10); Must include <string.h>
memset assigns the same value to every byte of the block; you can’t use it to set, say, each int array
“element” to 1. That would imply setting every fourth byte to 1. Thus, zero is the only value we can use here.

16.9 realloc: CHANGING SIZE OF ALLOCATED MEMORY BLOCK


Prototype: void *realloc(void *ptr, size_t size);
Header File: stdlib.h
Return Type: A generic pointer to the reallocated memory on success, NULL on failure.

In spite of allocating memory at runtime, it is still possible for the allocation to be either excessive
or inadequate. You would then need to resize the existing memory block with the realloc function.
This function takes the old pointer (ptr) returned by a previous invocation of malloc or calloc as
the first argument and the desired size (size) as the other argument.
realloc returns a pointer to the beginning of the reallocated block, or NULL on error. The following
sequence that also uses malloc enlarges an existing block of 20 bytes to 100:
Dynamic Memory Allocation and Linked Lists 535

int *p, *q;


p = malloc(20); Allocates 20 bytes
...
q = realloc(p, 100) Reallocates 100 bytes
If the requested size (100) is greater than the existing one (20), one of the following things will happen:
realloc will try to enlarge the existing block to 100 bytes of contiguous memory and return
the existing pointer (p = q) if successful.
Otherwise, realloc will attempt to create a new block elsewhere and move the existing data
to the new block. It will return a pointer (q) to this newly created block, free the old block and
set p to NULL. C guarantees that there will be no loss or alteration of data during migration.
If neither action succeeds, realloc will return NULL and leave the existing data intact.
In case q is different from p, you may still want to reassign q to p and continue to use the original
pointer:
p = q;
In case you have overestimated the memory requirements, you can use realloc to shrink the existing
block. The previous reallocation can thus be followed with this call to realloc:
r = realloc(q, 80) Shrinks size to 80 bytes
Program 16.6 uses scanf to accept any number of integers from the keyboard and save them
in a memory block. The space for the first integer is created with malloc. This space is subsequently
extended with realloc to store the remaining integers. The program progressively shows the number
of bytes allocated along with the pointer to each of these blocks. Even though the pointer has not
changed in this sample run, it need not necessarily be so, specially if the block becomes large.

/* realloc.c: Uses realloc to increase the size of allocated memory


every time another integer is read by scanf. */
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *p, temp;
short i = 0, num, newsize;
fputs(“Key in some integers, press [Enter] and [Ctrl-d]: “, stderr);
while (scanf(“%d”, &temp) == 1) {
i++;
if (i == 1) {
/* Allocate memory for 1st element of array */
if ((p = malloc(sizeof(int))) == NULL) {
fputs(“Cannot allocate memory, quitting ...\n”, stderr);
exit(1);
}
printf(“Allocated memory = %2d bytes, Pointer = %p\n”, sizeof(int), p);
*p = temp; /* First integer allocated to memory block */
}
536 Computer Fundamentals & C Programming

else if (i > 1) { /* For subsequent input ... */


newsize = sizeof(int) * i;
p = realloc(p, newsize); /* ... memory reallocated */
if (p == NULL) {
fputs(“Cannot reallocate memory, quitting ...\n”, stderr);
exit(1);
}
*(p + i - 1) = temp; /* Populates reallocated block */
printf(“Reallocated memory = %2d bytes, Pointer = %p\n”, newsize, p);
}
} /* Matching brace for while */

num = i;
if (i > 0) {
fputs(“Printing array elements ...\n”, stderr);
for (i = 0; i < num; i++)
printf(“%d “, p[i]);
}
exit(0);
}

PROGRAM 16.6: realloc.c


Key in some integers, press [Enter] and [Ctrl-d]: 11 222 33 444 55 666
Allocated memory = 4 bytes, Pointer = 0x804b008
Reallocated memory = 8 bytes, Pointer = 0x804b008
Reallocated memory = 12 bytes, Pointer = 0x804b008
Reallocated memory = 16 bytes, Pointer = 0x804b008
Reallocated memory = 20 bytes, Pointer = 0x804b008
Reallocated memory = 24 bytes, Pointer = 0x804b008
[Ctrl-d]
Printing array elements ...
11 222 33 444 55 666

PROGRAM OUTPUT: realloc.c


This program provides the foundation for the remaining discussions in this chapter. The memory
for each input integer is provided “just-in-time,” i.e., when it is needed. Thus, to input n integers,
you need to invoke malloc once and realloc n - 1 times. The program is inefficient because of
multiple allocations, but the “just-in-time” technique illustrates how memory allocation is made
for a linked list which we will now discuss.

16.10 THE LINKED LIST


Program data are often held in one or more lists. A list is simply a sequence of data items held
together by a common access mechanism. An array is a linear list of data items where each item
is identified by a unique array subscript. However, an array has some disadvantages that you are
well aware of:

You might also like