Learn C Programming

Lesson 1 of 9 · Dynamic Memory

Module 14 · Dynamic Memory

Why Dynamic Memory: Sizes You Do Not Know Yet

FreeReading

In this lesson

  • Explain why a size known only at run time needs dynamic memory, and what a capacity constant or a huge static array costs.
  • Write malloc(n * sizeof *marks), check the result for NULL, use the block as an array and give it back with free.
  • Point to the heap on the memory map, and say what a failed allocation looks like on the Playground.

Amara's marks report from the Module 9 project kept the class in int marks[MAX_S][MAX_M], with MAX_S set to 100. A sheet for 101 students got two words back: invalid size.

This term the office will not say how many students there are. The first line of the sheet will say, and it may say 12 or 3,000.

An array's size is fixed before the program is compiled, and this number arrives with the input. This lesson asks for the boxes once the number is known.

Three answers to a size you do not know yet

The first two answers are Module 9's, and the third is this module's.

The answerIn codeWhat it costs
A capacity constant#define MAX_S 100, then int marks[MAX_S];Too small one day: student 101 gets invalid size
A huge static arraystatic int marks[1000000];Still a ceiling, and the code claims a million students to hold thirty
Ask while runningmalloc(n * sizeof *marks)Exactly n boxes; you must check the answer and give the block back

The constant is honest about its limit, and the limit is the problem. The day a school merges two classes, the program refuses real data.

The huge static array costs less than it looks. Module 9 lesson 3 measured a 280 MB static array on the Playground, and it passed using 416 KB. The operating system hands out memory in pages of 4,096 bytes, and counts a page only when the program first touches it. So the real cost is the ceiling that remains, and a program that says "a million" where it means "however many".

malloc asks for exactly n boxes after n is read, with no ceiling in the code. The price is two new duties: check what came back, and give it back when you are done.

So a constant and a static array both fix the ceiling when the program is compiled, and malloc sets the size while it runs.

The heap: the box Module 11 drew dashed

Module 7 lesson 5 promised a third area of memory, and Module 11 lesson 1 drew it as a dashed box marked Module 14. It is the heap: a region of memory a program asks for, piece by piece, while it runs.

Asking is called allocating, and one piece is a block: bytes side by side, like an array with no name. Memory handled this way is dynamic memory, because its size and its lifetime are decided while the program runs.

You reach a block through a pointer that holds its address, and that pointer is an ordinary local. In the picture, marks sits in main's frame on the stack, and the five boxes it points at sit in the heap.

A program's memory with the heap filled in: marks on the stack points at a block in the heap One program's memory, lowest addresses at the bottom higher lower main's frame n: 5 marks grows down a very large gap grows up 90 72 64 88 51 the block malloc returned: 5 ints static area code stack one frame per running call marks holds an address heap blocks asked for while running each one lives until free globals and statics the program's instructions marks is a local on the stack; the five boxes it points at live in the heap.

The heap grows upward as the program asks for more, and the stack grows downward (Module 8 lesson 2). Lesson 6 shows how the C library makes the heap bigger.

A local dies at its function's closing brace. A block stays until the program calls free on it, so a function can allocate a block and return its address. Example 2 does exactly that.

So the pointer is a local on the stack, and the boxes it points at live in the heap until you free them.

malloc, read aloud part by part

malloc (short for "memory allocate") comes from <stdlib.h>. It takes a number of bytes and returns the address of the first byte of a fresh block. When it cannot give the block, it returns NULL.

Asking for n boxes

int *marks = malloc(n * sizeof *marks);
  • #include <stdlib.h> at the top of the file declares malloc and free. Mistake 1 shows what happens without it.
  • malloc(...) takes a size in bytes, a size_t, and returns a void *, C's general address type (Module 11 lesson 1).
  • int *marks = keeps that address. C turns a void * into an int * by itself, so no cast is needed.
  • sizeof *marks is the size of one box that marks points at: 4 bytes for an int. It takes no parentheses, the way Module 13 wrote sizeof line.
  • n * is the count. The count comes first, then the size of one box: n boxes of 4 bytes each.

Why not sizeof(int)? Both give 4 here. If marks later becomes a long long *, sizeof *marks follows it to 8. sizeof(int) stays 4, and the block comes out half size. Parentheses are needed only around a type name.

Here is the whole idiom in Amara's program, which prints the first and the last mark.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int n = 0;
    int *marks = NULL;

    if (scanf("%d", &n) != 1 || n < 1) {
        printf("no marks\n");
        return 0;
    }
    marks = malloc(n * sizeof *marks);
    if (marks == NULL) {
        fprintf(stderr, "out of memory\n");
        return 1;
    }
    for (int i = 0; i < n; i++) {
        scanf("%d", &marks[i]);
    }
    printf("students: %d\n", n);
    printf("first mark: %d, last mark: %d\n", marks[0], marks[n - 1]);
    free(marks);
    return 0;
}
students: 5
first mark: 90, last mark: 51

That output is for the input 5, then 90 72 64 88 51. With n = 5 the call asks for 5 × 4 = 20 bytes. No message at any command line: the Playground, -Wall and -Wall -Wextra all stay silent.

So malloc(n * sizeof *marks) reads aloud as "n boxes, each the size of the box marks points at".

Check the answer: NULL, and what fails on the Playground

When malloc cannot give the block, it returns NULL, the pointer to nothing (Module 11 lesson 2). Writing through NULL is undefined behaviour, so this track checks every allocation on the very next line. In main, the check prints out of memory on stderr and returns 1.

How often does that happen? Less often than you might think. Measured on the live Playground, whose memory limit is 256,000 KB:

What the program didWhat the Playground showed
malloc of 1,024 MB, nothing writtenA pointer, not NULL
malloc of 5 GB, nothing writtenA pointer, and the whole run used 384 KB
malloc of 6 GBNULL
240 MB allocated, one byte written in every pageSuccess, Memory: 246,564 KB
300 MB allocated, one byte written in every pageMemory limit exceeded, and no output at all

So malloc hands out address space, not memory. The limit counts only the pages a program touches, the rule that let Module 9's static array pass. A request far past the limit still gets a pointer, and the run ends when the program writes into too much. NULL came back only for a request the machine refused outright. That line moves with the machine: on Compiler Explorer, 12 GB got a pointer and 16 GB got NULL.

Then why check? Because some requests do fail, and one is easy to make by accident. Zara tries odd inputs first, so she feeds this program -1.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int n = 0;
    int *marks = NULL;

    if (scanf("%d", &n) != 1) {
        return 1;
    }
    marks = malloc(n * sizeof *marks);
    if (marks == NULL) {
        printf("malloc returned NULL for n = %d\n", n);
        return 1;
    }
    printf("got a block for n = %d\n", n);
    free(marks);
    return 0;
}

No message at any command line. Measured on the live Playground, the run printed malloc returned NULL for n = -1, and return 1; showed Runtime error with Exit: 1. sizeof gives an unsigned size_t, so the -1 wraps around to the largest size_t. Times 4, that is 18,446,744,073,709,551,612 bytes.

With the -1 written into the program instead, GCC 12 sees the size while compiling. The Playground shows it in the amber Compile output tab: warning: argument 1 value '18446744073709551612' exceeds maximum object size 9223372036854775807 [-Walloc-size-larger-than=]. Read from the input, the mistake is silent, so the program must check n itself.

That is why Amara's program above tests n < 1 first: for -1 or 0 it prints no marks and never calls malloc. The NULL check stays too, for a machine with less room and a program that keeps asking for months. A program that assumes malloc never fails has not met Zara yet.

So check n before you allocate, and check the pointer after.

Use the block like an array, then free it

Once the check passes, the block is used exactly like an array. marks[i] means *(marks + i) (Module 11 lesson 3): start at the block's address, step i boxes, and use the box there. Indexes run from 0 to n - 1, as in every array, and nothing checks them for you.

When you are done, free(marks) gives the block back to the allocator, the part of the C library that hands out blocks. After that, marks is a dangling pointer (Module 11 lesson 7): it holds an address, but the block is no longer yours. Lesson 2 adds marks = NULL; for a pointer that stays in use; right before return, free alone is enough.

A block that nothing points at any more, and that was never freed, is a leak. A leak changes no output. When a program ends, the operating system takes back every page it had. So on the Playground, a missing free costs nothing you can see.

The habit still matters, because not every program ends. A web server runs for months and allocates for every request. If each request leaks a block, its memory grows until the machine refuses it; lesson 5 measures one such leak. So this track frees every block on every path, the early returns included.

So index the block like an array, and free it on every path out of the program.

Example 1: Kenji's three scores on the heap

The smallest complete use of the heap. Kenji's game keeps his last three scores in a block of three boxes, then prints them.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int *scores = malloc(3 * sizeof *scores);

    if (scores == NULL) {
        fprintf(stderr, "out of memory\n");
        return 1;
    }
    scores[0] = 70;
    scores[1] = 85;
    scores[2] = 91;
    printf("Kenji's scores: %d %d %d\n", scores[0], scores[1], scores[2]);
    free(scores);
    return 0;
}
Kenji's scores: 70 85 91

No message at any command line. A size known in advance would normally be a plain array; this one shows the four steps alone: allocate, check, use, free.

Run in Compiler
Example 2: Amara's class, read by a function

Amara's report now takes any class size. read_marks allocates the block, fills it and returns its address. A local array could not be returned like this, because it dies at the function's closing brace. The report counts the passes and lists every mark below 40.

#include <stdio.h>
#include <stdlib.h>

#define PASS_MARK 40

int *read_marks(int n);
int count_passed(const int *marks, int n);

int main(void)
{
    int n = 0;
    int *marks = NULL;

    if (scanf("%d", &n) != 1 || n < 1) {
        printf("no marks\n");
        return 0;
    }
    marks = read_marks(n);
    if (marks == NULL) {
        fprintf(stderr, "out of memory\n");
        return 1;
    }
    printf("passed: %d of %d\n", count_passed(marks, n), n);
    for (int i = 0; i < n; i++) {
        if (marks[i] < PASS_MARK) {
            printf("needs help: student %d with %d\n", i + 1, marks[i]);
        }
    }
    free(marks);
    return 0;
}

int *read_marks(int n)
{
    int *marks = malloc(n * sizeof *marks);

    if (marks == NULL) {
        return NULL;
    }
    for (int i = 0; i < n; i++) {
        scanf("%d", &marks[i]);
    }
    return marks;
}

int count_passed(const int *marks, int n)
{
    int passed = 0;

    for (int i = 0; i < n; i++) {
        if (marks[i] >= PASS_MARK) {
            passed = passed + 1;
        }
    }
    return passed;
}
passed: 4 of 6
needs help: student 2 with 35
needs help: student 6 with 22

That output is for the input 6, then 72 35 90 41 64 22. read_marks returns NULL when malloc fails, and main reports it; a helper never ends the program itself. count_passed only reads, so it takes a const int * (Module 11 lesson 5).

Run in Compiler
Example 3: Maria's receipt of any length

Maria's receipt prints its total above the items, so every price must be kept until all are read. The count changes with every customer. This is the program a beginner writes first, all in main.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int n = 0;
    int *prices = NULL;
    int total = 0;

    if (scanf("%d", &n) != 1 || n < 1) {
        printf("empty receipt\n");
        return 0;
    }
    prices = malloc(n * sizeof *prices);
    if (prices == NULL) {
        fprintf(stderr, "out of memory\n");
        return 1;
    }
    for (int i = 0; i < n; i++) {
        scanf("%d", &prices[i]);
        total = total + prices[i];
    }
    printf("receipt: %d items, total %d\n", n, total);
    for (int i = 0; i < n; i++) {
        printf("  item %d: %d\n", i + 1, prices[i]);
    }
    free(prices);
    return 0;
}
receipt: 3 items, total 285
  item 1: 65
  item 2: 180
  item 3: 40

That output is for the input 3, then 65 180 40. For 0 it prints empty receipt and allocates nothing. Every way out of main frees the block, or never had one.

Run in Compiler

Where this is used

  • Text editors. GNU Emacs keeps each open file's text in a buffer. It allocates the buffer when the file is opened, sized to the file, and enlarges it as you type.
  • Web servers. nginx gives every request its own memory pool, allocated when the request arrives and freed in one go when it ends.
  • Games. The Doom source code allocates each monster and missile as it appears, with its own allocator, Z_Malloc. No level fixes how many there will be.
  • Compilers. Clang turns each source file into a syntax tree, one node per declaration, statement and expression, allocated as it reads the file.

Common mistakes

1. No #include for stdlib.h.

#include <stdio.h>

int main(void)
{
    int n = 5;
    int *marks = malloc(n * sizeof *marks);

Two warnings on every command line, the Playground included, in its Compile output tab: warning: implicit declaration of function 'malloc' [-Wimplicit-function-declaration] with note: include '<stdlib.h>' or provide a declaration of 'malloc', then warning: incompatible implicit declaration of built-in function 'malloc' [-Wbuiltin-declaration-mismatch]. The same two follow for free. The run still printed 90, but GCC 14 makes the first one an error. Add #include <stdlib.h>. You will forget it because <stdio.h> has been enough since Module 1.

2. malloc(n): bytes, not boxes.

marks = malloc(n);
if (marks == NULL) {
    fprintf(stderr, "out of memory\n");
    return 1;
}

No message at any command line. This block holds n bytes, room for a quarter of the marks. With 5 marks, one run of Amara's full program on Compiler Explorer's GCC 12 still printed the right answer. glibc rounds a small request up (lesson 6), and 20 bytes happened to fit. With 100,000 marks the run died with Program terminated with signal SIGSEGV (11). AddressSanitizer, a checking tool from lesson 5, stops at the second mark: ERROR: AddressSanitizer: heap-buffer-overflow, 0 bytes to the right of 5-byte region. Write malloc(n * sizeof *marks). You will slip because you count marks and malloc counts bytes.

3. Bob's loop runs to n.

for (int i = 0; i <= n; i++) {
    total = total + prices[i];
}

No message at any command line. The last pass reads prices[n], one box past the block: undefined behaviour. One run of Bob's full program on Compiler Explorer's GCC 12, with 3, then 65 180 40, printed total 285. That is right by luck. AddressSanitizer says READ of size 4 and 0 bytes to the right of 12-byte region, the box just after the three. Write i < n. Bob writes <= because "up to n" sounds right.

Brain teaser

Zara tries 0 first. Suppose a program skips the n < 1 test and asks for zero boxes.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int *marks = malloc(0);

    if (marks == NULL) {
        printf("malloc(0) gave NULL\n");
    } else {
        printf("malloc(0) gave a pointer\n");
    }
    free(marks);
    return 0;
}

Which line does the Playground print? Is free(marks) safe after either answer? Then the harder half: may the program store a mark in marks[0]?

C17 allows malloc(0) two different answers (section 7.22.3). Find out which one glibc gives, then ask how many bytes a block of zero bytes holds.

Exercise 1Easy

Amara's class has an unknown number of students this term. Write int *read_marks(int n). It allocates exactly n boxes with malloc, checks the answer for NULL, reads n marks into them and returns the block. Then write void print_report(const int *marks, int n). The judge sees output only, so it cannot see whether your block has exactly n boxes.

Input. A line with n, then n marks separated by spaces.

Output. mean: X.XX, with two digits after the point, and max: M, on two lines. When n is 0, the one line no marks.

Constraints. 0 <= n <= 100000; 0 <= every mark <= 100.

Sample. Input 3, then 90 92 92, gives mean: 91.33 and max: 92 on two lines. The marks add up to 274, and 274 / 3 is 91.333..., printed with two digits.

#include <stdio.h>
#include <stdlib.h>

int *read_marks(int n);
void print_report(const int *marks, int n);

int main(void)
{
    int n = 0;
    int *marks = NULL;

    scanf("%d", &n);
    marks = read_marks(n);
    if (n > 0 && marks == NULL) {
        fprintf(stderr, "out of memory\n");
        return 1;
    }
    print_report(marks, n);
    free(marks);
    return 0;
}

int *read_marks(int n)
{
    /* Ask malloc for exactly n boxes, check the answer for NULL,
       read n marks into the boxes and return the block. */
    return NULL;
}

void print_report(const int *marks, int n)
{
    /* Print "mean: X.XX" and "max: M" on two lines, or "no marks" when n is 0. */
}

Graded as mean-max. The hidden tests include n = 0, and a class of 100,000 where a block of n bytes is far too small.

Run in Compiler
Exercise 2Medium

Zara's puzzle game needs the first n square numbers kept in memory. Write long long *make_squares(int n). For n = 0 it allocates nothing and returns NULL. Otherwise it allocates exactly n long long boxes with malloc, checks the answer, and puts (i + 1) squared in box i. In main, print every box and add it to the sum.

Input. One integer n.

Output. The n squares, one per line, from 1 up, then sum: S. When n is 0, only sum: 0.

Constraints. 0 <= n <= 100000. The boxes are long long because 100,000 squared is 10,000,000,000, far past the largest int.

Sample. Input 3 gives 1, 4, 9 and sum: 14, one per line.

#include <stdio.h>
#include <stdlib.h>

long long *make_squares(int n);

int main(void)
{
    int n = 0;
    long long *squares = NULL;
    long long sum = 0;

    scanf("%d", &n);
    squares = make_squares(n);
    if (n > 0 && squares == NULL) {
        fprintf(stderr, "out of memory\n");
        return 1;
    }
    /* Print box 0 to box n - 1, one per line, and add each one to sum. */
    printf("sum: %lld\n", sum);
    free(squares);
    return 0;
}

long long *make_squares(int n)
{
    /* For n = 0, allocate nothing and return NULL. Otherwise ask malloc
       for exactly n boxes, check for NULL, and put (i + 1) squared in box i. */
    return NULL;
}

Not graded on its own. The output depends on n alone, so a program with no block at all would pass every test.

Run in Compiler

Common doubts

  • Why not read n and then write int marks[n];?

    That is a variable length array, which Module 9 lesson 1 ruled out: C11 made it optional for compilers. It lives in the function's frame on the stack. When there is no room, there is no NULL to check: the program just dies. It also dies at the closing brace, so a function cannot return it.

  • Does sizeof *marks read through a pointer that points nowhere yet?

    No. sizeof never evaluates its operand, except for a variable length array (C17 section 6.5.3.4). It only looks at the type of *marks, which is int. So malloc(n * sizeof *marks) is safe while marks is still NULL.

  • Should I cast the result, as in (int *)malloc(...)?

    Not in C, which converts a void * to any object pointer by itself. You will see the cast in older books and in C++, which requires it. In C it adds nothing, so this track leaves it out.

  • Are the boxes from malloc zero to start with?

    C promises nothing. A fresh block often reads zero and a reused one does not; lesson 2 measures both, and meets calloc, which promises zeros. Write every box before you read it.

Key takeaways

  • A capacity constant is too small one day, and a huge static array still has a ceiling. malloc asks for exactly n boxes once n is known.
  • The heap holds the blocks a program allocates while it runs. The pointer is a local on the stack, and the block lives until free.
  • malloc(n * sizeof *marks), from <stdlib.h>, is the count times the size of one box, with no cast.
  • Check n before malloc and the pointer after it: a -1 asks for 18,446,744,073,709,551,612 bytes and gets NULL.
  • On the Playground a huge request still gets a pointer; touching more than 256,000 KB ends the run with Memory limit exceeded.
  • Index the block like an array, and free it on every path. A program that runs for months keeps every block it never gives back.

Next, Bob grows a block and loses it on the one run where realloc says no. Lesson 2 opens the four calls, malloc, calloc, realloc and free, and measures what each one promises.

End of lesson 1

Mark it done, and your progress moves with you.

Next: malloc, calloc, realloc and free