LABORATORY 09

Dynamic Memory Allocation

Duration: 2 hours Library: stdlib.h Previous: Laboratory 8 PDF handout RO versiunea română

Until now, the size of the data was fixed when the program was written. Dynamic allocation lets you establish it at run time, requesting memory exactly when it is needed and releasing it when it is no longer useful.

1Lab objectives

  • Distinguishing static, automatic, and dynamic allocation
  • Using the malloc, calloc, realloc, and free functions
  • Mandatory verification that allocation succeeded
  • Identifying and preventing memory leaks
  • Using the C++ new and delete operators

2Why dynamic allocation is needed

When declaring an array, the size must be a constant known at compile time. This leads to two opposite problems:

SituationConsequence
An array much larger than needed is declaredmemory wasted unnecessarily
The actual number of elements exceeds the declared sizethe code must be changed and recompiled
The solutionThe size is established at run time, when the number of elements is known, exactly as much as needed is allocated, and at the end the memory is released so it can be reused for another purpose.

3Memory segments

A program's memory is divided into specialized zones, addressed through segment registers (CS, DS, ES, SS):

ZoneWhat it holdsWhen it is allocatedWhen it is released
Code segment (CS)the executable instructionswhen the program loadson termination
Data segment (DS)global and static variablesat load timeon termination
Stack (SS)local variables, parameters, return addresseson entering a blockautomatically, on exit
Heapdynamically allocated dataon calling malloc / newmanually, through free / delete
Code segment - instructions Static data - globals, static HEAP - dynamic allocation grows upward ↑ (malloc / new) grows downward ↓ (function calls) STACK - local variables low addresses high addresses manual allocation automatic allocation
Fig. 1 - Memory layout of a running program

4C allocation functions

FunctionPrototypeEffect
mallocvoid* malloc(size_t n)allocates n bytes, uninitialized
callocvoid* calloc(size_t nr, size_t dim)allocates nr×dim bytes, set to zero
reallocvoid* realloc(void* p, size_t n)resizes an already allocated zone
freevoid free(void* p)releases the indicated zone
the correct pattern of use
int *v = (int *)malloc(n * sizeof(int));   // 1. allocation

if (v == NULL) {                            // 2. mandatory CHECK
    printf("Insufficient memory!\n");
    return 1;
}

/* ... use ... */                           // 3. use

free(v);                                    // 4. release
v = NULL;                                   // 5. avoid accidental use
Checking is not optionalIf the system cannot satisfy the request, malloc returns NULL. Using the result without checking leads to dereferencing a null pointer and an abrupt program stop.
realloc - watch the returned pointerThe function may move the block to a different address. Never write v = realloc(v, ...) directly: if the allocation fails, NULL is returned, and the old address is lost for good. Use a temporary variable.

5Simulator: pointer to the allocated zone

Select the target to see the difference between a valid pointer, one pointing to a different zone, and one set to NULL after release.

Pointer, heap, and the NULL state
Why the pointer is set to NULL after freeAfter release, the pointer still holds the old address, but the zone is no longer ours. A repeated free on the same address corrupts the allocator's internal structures. Assigning v = NULL turns a subtle bug into one that is immediately visible.

6The C++ new and delete operators

OperationCC++
Allocation for a valuep = (int*)malloc(sizeof(int));p = new int;
Allocation with initializationseparate allocation + assignmentp = new int(42);
Allocation for an arrayv = (int*)malloc(n*sizeof(int));v = new int[n];
Releasing a valuefree(p);delete p;
Releasing an arrayfree(v);delete[] v;
Do not mix the two mechanismsMemory obtained with malloc is released with free; memory obtained with new, with delete. Arrays must use delete[] - otherwise the elements' destructors are not called.
The advantages of newIt needs no explicit type conversion, computes the size itself, calls constructors for objects, and on failure throws an exception instead of silently returning NULL.

7Classic mistakes

MistakeCauseEffect
Memory leakmissing freegrowing memory use; long-running programs eventually exhaust the system
Double freereleasing twiceinternal allocator corruption, unpredictable crash
Use after freeaccess after releaserandom data or a crash; a security vulnerability
Buffer overflowwriting past the allocated sizeoverwriting neighboring heap structures
Unchecked allocationmissing the == NULL testdereferencing a null pointer
The golden ruleEvery malloc must have exactly one matching free, and every new exactly one delete. Write them in pairs, right at the moment of allocation, so you do not forget them.

8Source code

dynamic_array.c - size set at run time
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int n;

    printf("How many elements? ");
    if (scanf("%d", &n) != 1 || n <= 0) {
        printf("Invalid value\n");
        return 1;
    }

    int *v = (int *)malloc(n * sizeof(int));   // exact allocation
    if (v == NULL) {                            // mandatory check
        printf("Allocation failed!\n");
        return 1;
    }

    for (int i = 0; i < n; i++) {
        printf("v[%d] = ", i);
        scanf("%d", &v[i]);
    }

    long sum = 0;
    for (int i = 0; i < n; i++) sum += v[i];

    printf("\nSum = %ld, mean = %.3f\n", sum, (double)sum / n);

    free(v);        // release
    v = NULL;       // protection against accidental use

    return 0;
}
resize.c - realloc used correctly
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int capacity = 4, nr = 0;
    int *v = (int *)malloc(capacity * sizeof(int));
    if (!v) return 1;

    printf("Enter numbers (0 = stop):\n");

    int x;
    while (scanf("%d", &x) == 1 && x != 0) {

        if (nr == capacity) {                    // full: double the capacity
            capacity *= 2;

            int *temp = (int *)realloc(v, capacity * sizeof(int));
            if (temp == NULL) {                  // do NOT assign directly to v!
                printf("Resize failed\n");
                free(v);
                return 1;
            }
            v = temp;
            printf("  [capacity increased to %d]\n", capacity);
        }
        v[nr++] = x;
    }

    printf("\nYou entered %d values: ", nr);
    for (int i = 0; i < nr; i++) printf("%d ", v[i]);
    printf("\n");

    free(v);
    return 0;
}
dynamic_matrix.c - a dynamically allocated 2D array
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int rows, cols;

    printf("Rows and columns: ");
    scanf("%d %d", &rows, &cols);

    // 1. an array of pointers to rows
    int **m = (int **)malloc(rows * sizeof(int *));
    if (!m) return 1;

    // 2. one row for each pointer
    for (int i = 0; i < rows; i++) {
        m[i] = (int *)malloc(cols * sizeof(int));
        if (!m[i]) {                          // release what was already allocated
            for (int k = 0; k < i; k++) free(m[k]);
            free(m);
            return 1;
        }
    }

    for (int i = 0; i < rows; i++)
        for (int j = 0; j < cols; j++)
            m[i][j] = (i + 1) * (j + 1);      // the multiplication table

    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) printf("%5d", m[i][j]);
        printf("\n");
    }

    // release in REVERSE order of allocation
    for (int i = 0; i < rows; i++) free(m[i]);
    free(m);

    return 0;
}
new_delete.cpp - the C++ version
#include <iostream>
using namespace std;

int main()
{
    int n;
    cout << "How many elements? ";
    cin >> n;

    int *v = new int[n];              // no conversion, no sizeof

    for (int i = 0; i < n; i++) v[i] = (i + 1) * (i + 1);

    for (int i = 0; i < n; i++) cout << v[i] << " ";
    cout << endl;

    delete[] v;                       // MANDATORY with square brackets

    // allocation with initialization
    int *p = new int(42);
    cout << "*p = " << *p << endl;
    delete p;                         // no brackets, for a single value

    return 0;
}

9Code workshop

In Step by step mode, the panel on the right has a heap section: the blocks allocated with malloc appear there, and you can see exactly when they become "released".

An array whose size is determined at run time
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int n, i, *v;

    printf("How many elements? ");
    scanf("%d", &n);

    v = (int*) malloc(n * sizeof(int));
    if (v == NULL) {
        printf("Allocation failed\n");
        return 1;
    }

    for (i = 0; i < n; i++) v[i] = (i + 1) * (i + 1);

    printf("Squares of the first %d numbers: ", n);
    for (i = 0; i < n; i++) printf("%d ", v[i]);
    printf("\n");

    free(v);
    return 0;
}
Try thisDelete the line free(v); and run again: the program works the same way. That is exactly why memory leaks are dangerous - they are invisible. Now move free(v); before the printing loop and you will get a clear error.
Exercise - realloc and correct release
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int *v, i;

    v = (int*) malloc(3 * sizeof(int));
    for (i = 0; i < 3; i++) v[i] = i + 1;

    /* Grow the zone to 6 elements using realloc,
       then fill elements 3, 4, 5 with the values 4, 5, 6.
       Do not forget to check the result of realloc. */

    for (i = 0; i < 6; i++) printf("%d ", v[i]);
    printf("\n");

    free(v);
    return 0;
}

10Work tasks

  • Dynamically allocate an array whose size is read from the keyboard, and compute the sum and mean.
  • Check what malloc returns for an absurdly large request (for example 10^10 bytes).
  • Compare the contents of zones obtained with malloc and with calloc, immediately after allocation.
  • Implement an array that doubles its capacity through realloc when it fills up.
  • Dynamically allocate a matrix and release it correctly, in reverse order of allocation.
  • Intentionally omit free in a loop and watch memory grow in Task Manager.
  • Rewrite one of the programs in C++, using new and delete[].

11Extended application

ExtensionImplement a singly linked list of integers, with dynamic allocation for each node: insertion at the front and at the back, removal of an element by value, printing the list, and fully releasing the memory. Verify with a global counter that the number of malloc calls equals the number of free calls when the program ends.

12Review questions

13Resources