Kodewick
Lesson 20 of 30Contents

malloc and free

Lesson 20 of 30 · Part 4 · Memory · about 15 minutes

Last lesson asked the heap for memory. This lesson covers malloc and free properly: how many bytes to ask for, what if you can't get them, what's in the memory you get, and when to give it back. Here's a full example first: five days of temperatures on the heap.

main.c
1#include <stdio.h>2#include <stdlib.h>3 4int main(void) {5    int *temps = malloc(5 * sizeof(int));6    if (temps == NULL) {7        printf("Out of memory\n");8        return 1;9    }10    for (int i = 0; i < 5; i++) {11        temps[i] = 20 + i;12    }13    printf("Day one %d degrees, last day %d degrees\n", temps[0], temps[4]);14    free(temps);15    temps = NULL;16    return 0;17}
Output
Day one 20 degrees, last day 24 degrees

Using heap memory always takes these four steps:

StepHow it's written here
Ask: work out the bytes, call mallocmalloc(5 * sizeof(int))
Check: is it NULL?if (temps == NULL)
Use: with indexes, like an arraytemps[i] = 20 + i;
Give back: free it when donefree(temps);

Press Step: past line 5, a "20 bytes" block appears in the heap with an arrow from temps; each time around the loop, one more number lands in the block; past the free, the block turns gray, marked "freed". The last line sets temps to NULL and the arrow disappears too. The next lesson explains why you'd do that.

How many bytes

malloc only knows a number of bytes, not what you plan to put there. To hold n values of some type, write n * sizeof(type). Here sizeof(int) is 4 and sizeof(double) is 8:

main.c
1#include <stdio.h>2#include <stdlib.h>3 4int main(void) {5    int count = 3;6    double *prices = malloc(count * sizeof(double));7    prices[0] = 4.5;8    prices[1] = 12.0;9    prices[2] = 3.25;10    double total = 0;11    for (int i = 0; i < count; i++) {12        total += prices[i];13    }14    printf("%zu bytes, total %.2f\n", count * sizeof(double), total);15    free(prices);16    return 0;17}
Output
24 bytes, total 19.75

(To keep them short, a few examples in this section and the next skip the NULL check. Your own programs shouldn't.)

Writing just the count and forgetting the sizeof is a very common mistake: malloc(3) asks for 3 bytes, not even enough for one int.

main.c
1#include <stdio.h>2#include <stdlib.h>3 4int main(void) {5    int *steps = malloc(3);6    steps[0] = 4200;7    steps[1] = 6100;8    printf("%d\n", steps[1]);9    free(steps);10    return 0;11}
Output
heap-buffer-overflow

What happened: Stopped at line 6. This goes past the end of an array (or a string). Real C would read or overwrite whatever is next in memory.

Real C won't warn you: the extra bytes land on top of something else, and the program may only act strangely later. Our engine stops right on line 6: the block has only 3 bytes, so even one 4-byte int goes past its end. Press "see how it went wrong" and look at the "3 bytes" block on the heap.

Tip: You can also write malloc(3 * sizeof *steps): *steps is an int, so sizeof *steps is 4. If you later change int to double, this line doesn't need to change.

Guess first

Step past line 5. How many bytes does the heap block say?

1#include <stdio.h>2#include <stdlib.h>3 4int main(void) {5    double *weights = malloc(4 * sizeof(double));6    for (int i = 0; i < 4; i++) {7        weights[i] = 60.5 + i;8    }9    printf("%.1f\n", weights[3]);10    free(weights);11    return 0;12}

Checking for NULL

Memory isn't endless. When malloc can't get that much, it returns NULL. Check what you got before you use it:

main.c
1#include <stdio.h>2#include <stdlib.h>3 4int main(void) {5    char *big = malloc(100000000000);6    if (big == NULL) {7        printf("Can't get 100 GB\n");8        return 1;9    }10    big[0] = 'A';11    free(big);12    return 0;13}
Output
Can't get 100 GB

100 GB at once is more than a normal computer can give, so malloc returned NULL, and the program printed one line and stopped. Without the check, big[0] = 'A'; would write through NULL and the program would crash. return 1; tells the system the program didn't finish cleanly.

malloc returns a void *, which means "an address, without saying what's there". In C it can go straight into an int *, a double *, a char * or any other pointer, with nothing extra to write.

What's in the memory you get

malloc only finds a spot; it doesn't care what's in it. Whatever was there before is still there, just like a variable you haven't assigned (lesson 2):

main.c
1#include <stdio.h>2#include <stdlib.h>3 4int main(void) {5    int *counts = malloc(3 * sizeof(int));6    counts[0] = 5;7    printf("%d %d\n", counts[0], counts[1]);8    free(counts);9    return 0;10}
Output
use of uninitialized value

What happened: Stopped at line 7. A variable was used before it was given a value. In real C it would hold garbage, different on every run.

counts[1] was never given a value. Real C would print some unpredictable number, maybe different on every run; our engine stops on line 7 and tells you.

calloc: zeros from the start

For memory that's all zeros, use calloc. It takes two arguments: how many, and how many bytes each. It clears the whole block to 0:

main.c
1#include <stdio.h>2#include <stdlib.h>3 4int main(void) {5    int votes[8] = {2, 0, 1, 2, 2, 0, 1, 2};6    int *counts = calloc(3, sizeof(int));7    for (int i = 0; i < 8; i++) {8        counts[votes[i]]++;9    }10    for (int i = 0; i < 3; i++) {11        printf("Option %d: %d votes\n", i, counts[i]);12    }13    free(counts);14    return 0;15}
Output
Option 0: 2 votes
Option 1: 2 votes
Option 2: 4 votes

Counting starts from 0, so calloc is just right. Press Step: past line 6, the heap block is "12 bytes" with all three cells at 0; after that, each vote adds 1 to one cell.

free it when you're done

free(p) gives back the block p points at, and that memory may later be handed to someone else. A few rules:

  • Every block from malloc or calloc must be freed in the end, once and only once.
  • What you free must be the address malloc or calloc gave you, not the address of an ordinary variable.
  • After free, don't touch what was in the block.
  • free(NULL) does nothing, so it's safe.
main.c
1#include <stdio.h>2#include <stdlib.h>3 4int main(void) {5    int *a = malloc(2 * sizeof(int));6    int *b = malloc(4 * sizeof(int));7    a[0] = 1;8    b[0] = 2;9    printf("%d %d\n", a[0], b[0]);10    free(a);11    free(b);12    return 0;13}
Output
1 2

Press Step: two blocks appear on the heap, one "8 bytes · line 5", one "16 bytes · line 6". Past line 10 the first one turns gray; past line 11, the second does too. When the program ends, no block is left un-freed. What happens when you break these rules is the next lesson, one rule at a time.

Practice

Write the code, then press Check. A hint costs nothing; the answer is there when you need it.

Exercise 1

Average score

Get 4 int values on the heap, check for NULL, put in 78, 85, 90 and 77, print Average: 82.50, then free it.

It should print
Average: 82.50
exercise1.c
1#include <stdio.h>2#include <stdlib.h>3 4int main(void) {5 6    return 0;7}
Output
Press Run or Ctrl+Enter
Exercise 2

Counting dice

rolls holds 10 dice rolls (1 to 6). Use calloc to get 7 int counters (indexes 1 to 6 do the counting), count how many times each number came up, print it as 1: 2, one per line, then free it.

It should print
1: 2
2: 1
3: 2
4: 1
5: 1
6: 3
exercise2.c
1#include <stdio.h>2#include <stdlib.h>3 4int main(void) {5    int rolls[10] = {3, 6, 1, 3, 5, 6, 6, 2, 1, 4};6 7    return 0;8}
Output
Press Run or Ctrl+Enter
Exercise 3

Fix it

This program gets some memory, but goes out of bounds as soon as it uses it. Fix the malloc line so it prints 9.5 7.0 8.5.

It should print
9.5 7.0 8.5
exercise3.c
1#include <stdio.h>2#include <stdlib.h>3 4int main(void) {5    double *ratings = malloc(3);6    if (ratings == NULL) {7        return 1;8    }9    ratings[0] = 9.5;10    ratings[1] = 7.0;11    ratings[2] = 8.5;12    printf("%.1f %.1f %.1f\n", ratings[0], ratings[1], ratings[2]);13    free(ratings);14    return 0;15}
Output
Press Run or Ctrl+Enter

What you learned

  • malloc(n * sizeof(type)) gets room for n values on the heap and returns the block's address; malloc only knows bytes.
  • When it can't get the memory, malloc returns NULL: check before you use it.
  • malloc memory isn't cleared, so assign before you read; for all zeros, use calloc(count, bytes_each).
  • free it when you're done: once per block, only addresses from malloc / calloc, and free(NULL) does nothing.
  • When you step, each heap block shows its size and the line that asked for it; after free it turns gray, marked "freed".