Dynamic memory
So far every variable had a size fixed when you wrote the program, and disappeared when its function returned. Dynamic memory is memory you ask for while the program runs, from an area called the heap. It lasts until you hand it back.
You need it when the size isn't known in advance (a list as long as the user wants) or when data must outlive the function that created it.
malloc and free
#include <stdio.h>
#include <stdlib.h>
int main() {
int n = 5;
int *squares = malloc(n * sizeof *squares);
if (squares == NULL) {
printf("Out of memory\n");
return 1;
}
for (int i = 0; i < n; i++) squares[i] = i * i;
for (int i = 0; i < n; i++) printf("%d ", squares[i]);
printf("\n");
free(squares);
return 0;
}0 1 4 9 16 malloc(bytes)reserves that many bytes and returns a pointer to them, from<stdlib.h>.n * sizeof *squaresis "n of whatever squares points to", which stays right even if you later changeinttolong.mallocreturns NULL when it can't allocate. Check before using the pointer.- The pointer works like an array:
squares[i]. freereturns the memory. Everymallocneeds exactly onefree.
Sizing it at run time
This example's input is 4, then 10 20 30 40:
#include <stdio.h>
#include <stdlib.h>
int main() {
int n;
printf("How many numbers? ");
if (scanf("%d", &n) != 1 || n <= 0) return 1;
double *values = malloc(n * sizeof *values);
if (!values) return 1;
double sum = 0;
for (int i = 0; i < n; i++) {
scanf("%lf", &values[i]);
sum += values[i];
}
printf("\nAverage of %d numbers: %.2f\n", n, sum / n);
free(values);
return 0;
}How many numbers?
Average of 4 numbers: 25.00The array is exactly as long as the user asked for, which a fixed-size array can't promise.
calloc and realloc
#include <stdio.h>
#include <stdlib.h>
int main() {
int *counts = calloc(5, sizeof *counts); // five ints, all zero
if (!counts) return 1;
for (int i = 0; i < 5; i++) printf("%d ", counts[i]);
printf("\n");
int *bigger = realloc(counts, 8 * sizeof *counts); // grow to eight
if (!bigger) { free(counts); return 1; }
counts = bigger;
for (int i = 5; i < 8; i++) counts[i] = i * 10; // the new part is not zeroed
for (int i = 0; i < 8; i++) printf("%d ", counts[i]);
printf("\n");
free(counts);
return 0;
}0 0 0 0 0
0 0 0 0 0 50 60 70 calloc(count, size)allocates and fills with zeros.mallocleaves whatever bytes were there.realloc(p, bytes)resizes a block, keeping its contents. It may move the data, so use the pointer it returns.- Store
realloc's result in a new variable first: if it fails it returns NULL, and writing that over your only pointer would lose the original block.
An array that grows
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int *items;
int size;
int capacity;
} IntList;
int push(IntList *list, int value) {
if (list->size == list->capacity) {
int cap = list->capacity ? list->capacity * 2 : 2;
int *grown = realloc(list->items, cap * sizeof *grown);
if (!grown) return 0;
list->items = grown;
list->capacity = cap;
printf("(grew to %d)\n", cap);
}
list->items[list->size++] = value;
return 1;
}
int main() {
IntList list = {NULL, 0, 0};
for (int i = 1; i <= 5; i++) push(&list, i * i);
for (int i = 0; i < list.size; i++) printf("%d ", list.items[i]);
printf("\nsize %d, capacity %d\n", list.size, list.capacity);
free(list.items);
return 0;
}(grew to 2)
(grew to 4)
(grew to 8)
1 4 9 16 25
size 5, capacity 8Doubling the capacity each time it fills keeps the number of realloc calls small even for long lists. This is how C++'s std::vector works inside. realloc(NULL, bytes) behaves like malloc, which is why the list can start with items = NULL.
Strings on the heap
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *join(const char *first, const char *last) {
size_t len = strlen(first) + 1 + strlen(last) + 1; // a space, and the '\0'
char *s = malloc(len);
if (!s) return NULL;
strcpy(s, first);
strcat(s, " ");
strcat(s, last);
return s; // the caller must free it
}
int main() {
char *name = join("Ada", "Lovelace");
if (!name) return 1;
printf("%s (%zu characters)\n", name, strlen(name));
free(name);
return 0;
}Ada Lovelace (12 characters)A function can return heap memory because it outlives the call. Two details matter: allocate one extra byte for the terminating '\0', and make it clear who frees the result.
Returning the address of a local array is the classic mistake. The array disappears when the function returns, leaving a pointer to memory that's no longer yours:
char *greeting(void) {
char text[] = "hello";
return text; // wrong: text dies when the function returns
}The four classic mistakes
- Memory leak: losing the last pointer to a block without freeing it. The memory stays reserved until the program ends.
- Use after free: reading or writing through a pointer after
free. The memory may already belong to something else. - Double free: calling
freetwice on the same block, which can corrupt the heap. - Off by one: forgetting the
+ 1for a string's'\0', sostrcpywrites past the end.
free(p);
p = NULL; // a habit that turns a later accidental use into an obvious crashTools like Valgrind and the compilers' AddressSanitizer (gcc -fsanitize=address) find these bugs for you.